/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2009 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id:$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.loader;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;
import org.ow2.util.url.URLUtils;
import org.ow2.util.xmlconfig.XMLConfiguration;
import org.ow2.util.xmlconfig.XMLConfigurationException;
import org.ow2.util.xmlconfig.properties.SystemPropertyResolver;



/**
 * ClassLoader that allows to filter the classes or resources that are loaded.<br>
 * It is extending URLClassLoader as for example Tomcat is looking for getURLs() method, etc.
 * @author Florent Benoit
 */
public class FilteringClassLoader extends URLClassLoader {

    /**
     * Name of the XML file.
     */
    public static final String XML_FILE = "classloader-default-filtering.xml";

    /**
     * Mapping file of the XML file.
     */
    public static final String MAPPING_FILE = "classloader-default-filtering-mapping.xml";


    /**
     * System property for disabling the Filtering.
     */
    private static final String DISABLE_FILTERING_PROPERTY_NAME = "jonas-disable-filtering-class-loader";

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(FilteringClassLoader.class);


    /**
     * Enable filtering.
     */
    private boolean enabled = true;

    /**
     * The default filtering cannot be applied. This is used for submodules of EAR as the filtering is occuring at the EAR level.
     */
    private boolean disableDefaultFiltering = false;


    /**
     * List of default filters.
     */
    private FilteringClassLoaderFilters defaultFilters = null;


    /**
     * Build a new filtering classloader by using the given parent classloader.
     * @param parentClassLoader the given parent classloader
     */
    public FilteringClassLoader(final ClassLoader parentClassLoader) {
        this(parentClassLoader, false);
    }


    /**
     * Build a new filtering classloader by using the given parent classloader.
     * @param parentClassLoader the given parent classloader
     * @param disableDefaultFiltering flag for disabling the default filters (for example for modules inside an EAR)
     */
    public FilteringClassLoader(final ClassLoader parentClassLoader, final boolean disableDefaultFiltering) {
        super(new URL[0], parentClassLoader);
        this.disableDefaultFiltering = disableDefaultFiltering;

        // Allow to disable this filtering
        if (Boolean.getBoolean(DISABLE_FILTERING_PROPERTY_NAME)) {
            this.enabled = false;
        }

        // Load default filters if option is enabled
        if (enabled && !this.disableDefaultFiltering) {
            loadDefaultFilters();
        }

    }

    /**
     * Load the default filters.
     */
    protected void loadDefaultFilters() {

        // Get XML file
        File xmlConfigFile = new File(JProp.getConfDir() + File.separator + XML_FILE);
        if (!xmlConfigFile.exists()) {
            logger.warn("Cannot find the file named '" + xmlConfigFile
                    + "' for filtering classloader resources. Check your JONAS_BASE/conf folder.");
            return;
        }

        URL xmlconfigFileURL = URLUtils.fileToURL(xmlConfigFile);

        // Parse with XML config
        XMLConfiguration xmlConfiguration = new XMLConfiguration(MAPPING_FILE);

        // Set the source configuration
        xmlConfiguration.addConfigurationFile(xmlconfigFileURL);

        // Resolve properties as system properties
        xmlConfiguration.setPropertyResolver(new SystemPropertyResolver());

        // Use a map for some properties
        Map<String, Object> contextualInstances = new HashMap<String, Object>();

        // Sets the contextual instances
        xmlConfiguration.setContextualInstances(contextualInstances);

        // Start configuration of this object
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(FilteringClassLoader.class.getClassLoader());
            xmlConfiguration.configure(this);
        } catch (XMLConfigurationException e) {
            throw new IllegalArgumentException("Cannot configure the Filters inside the Filtering Classloader", e);
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }

    }

    /**
     * Loads the class if it is not filtered.
     * @param name the name of the class.
     * @param resolve if true, resolve the class
     * @return The resulting Class object
     * @throws ClassNotFoundException If the class was not found or if it is
     *         filtered
     */
    @Override
    public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {

        // If the class filtering is enabled and that the class is filtered, throws a CNFE exception
        if (enabled && isFiltered(name)) {
            throw new ClassNotFoundException("The class '" + name
                    + "' is a filtered class so it cannot be found in the Application Server classloader."
                    + " The class should be added into the application classloader.");
        }

        return super.loadClass(name, resolve);
    }

    /**
     * Find the given resource specified by its name.
     * @param name the resource name
     * @return the URL if the resource has been found, else null
     */
    @Override
    public URL getResource(final String name) {
        // If the resource filtering is enabled and that the resource is filtered, return null
        if (enabled && isFiltered(name)) {
            logger.debug("The resource ''{0}'' has been asked but as it is a filtered resource, return null", name);
            return null;
        }

        return super.getResource(name);
    }

    /**
     * Detects if the given class/resource should be filtered or not.
     * @param name the name of the class
     * @return true if it should be filtered, else false
     */
    protected boolean isFiltered(final String name) {
        if (name == null || defaultFilters == null) {
            return false;
        }

        // Finding a matching pattern
        List<String> filters = defaultFilters.getFilters();
        if (filters != null) {
            for (String pattern : filters) {
                if (name.matches(pattern)) {
                    return true;
                }
            }
        }

        // Not filtered !
        return false;
    }

    /**
     * @return the default filters object
     */
    public FilteringClassLoaderFilters getFilteringClassLoaderFilters() {
        return defaultFilters;
    }


    /**
     * Sets the default filters.
     * @param defaultFilters the default filters object
     */
    public void setFilteringClassLoaderFilters(final FilteringClassLoaderFilters defaultFilters) {
        this.defaultFilters = defaultFilters;
    }


}
