/*
 * Copyright (C) 2007-2009 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.plugin.util.cache;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;

import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginClassLoader;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class loader to be used by Openfire to load classes that live in the Hazelcast plugin,
 * the Openfire core and also classes defined in other plugins. With this new class loader
 * plugins can now make use of hazelcast.<p>
 *
 * However, there is a catch with this class loader. Plugins that define the same class name
 * (i.e. package and class name) will have a problem if they try to send that class through
 * the cluster. Hazelcast will deserialize the class and will use the first class definition
 * found in the list of plugins.<p>
 *
 * The sequence of search for this class loader is first check the hazelcast plugin that
 * includes checking the Openfire core. If not found then try with the other plugins.
 *
 * @author Tom Evans
 * @author Gaston Dombiak
 */
public class ClusterClassLoader extends ClassLoader {
    
    private static Logger logger = LoggerFactory.getLogger(ClusterClassLoader.class);
    
    private static final String HAZELCAST_CONFIG_DIR = JiveGlobals.getProperty(
            "hazelcast.config.xml.directory", JiveGlobals.getHomeDirectory()
                    + "/conf");

    private PluginClassLoader hazelcastClassloader;

    public ClusterClassLoader() {
        Plugin plugin = XMPPServer.getInstance().getPluginManager().getPlugin("hazelcast");
        hazelcastClassloader = XMPPServer.getInstance().getPluginManager().getPluginClassloader(plugin);
        
        // this is meant to allow loading configuration files from outside the plugin JAR file
        File confFolder = new File(HAZELCAST_CONFIG_DIR);
        try {
            logger.debug("Adding conf folder {}", confFolder);
            hazelcastClassloader.addURLFile(confFolder.toURI().toURL());
        } catch (MalformedURLException e) {
            logger.error("Error adding folder {} to classpath {}", HAZELCAST_CONFIG_DIR, e.getMessage());
        }
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return hazelcastClassloader.loadClass(name);
        }
        catch (ClassNotFoundException e) {
            PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
            for (Plugin plugin : pluginManager.getPlugins()) {
                String pluginName = pluginManager.getPluginDirectory(plugin).getName();
                if ("hazelcast".equals(pluginName) || "admin".equals(pluginName)) {
                    continue;
                }
                PluginClassLoader pluginClassloader = pluginManager.getPluginClassloader(plugin);
                try {
                    return pluginClassloader.loadClass(name);
                }
                catch (ClassNotFoundException e1) {
                    // Do nothing. Continue to the next plugin
                }
            }
        }
        throw new ClassNotFoundException(name);
    }

    public URL getResource(String name) {
        URL resource = hazelcastClassloader.getResource(name);
        if (resource == null) {
            PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
            for (Plugin plugin : pluginManager.getPlugins()) {
                String pluginName = pluginManager.getPluginDirectory(plugin).getName();
                if ("hazelcast".equals(pluginName) || "admin".equals(pluginName)) {
                    continue;
                }
                PluginClassLoader pluginClassloader = pluginManager.getPluginClassloader(plugin);
                resource = pluginClassloader.getResource(name);
                if (resource != null) {
                    return resource;
                }
            }
        }
        return resource;
    }

    public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration<URL> answer = null;
        try {
            answer = hazelcastClassloader.getResources(name);
        }
        catch (IOException e) {
            // Ignore
        }
        if (answer == null || !answer.hasMoreElements()) {
            PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
            for (Plugin plugin : pluginManager.getPlugins()) {
                String pluginName = pluginManager.getPluginDirectory(plugin).getName();
                if ("hazelcast".equals(pluginName) || "admin".equals(pluginName)) {
                    continue;
                }
                PluginClassLoader pluginClassloader = pluginManager.getPluginClassloader(plugin);
                try {
                    answer = pluginClassloader.getResources(name);
                }
                catch (IOException e) {
                    // Ignore
                }
                if (answer != null && answer.hasMoreElements()) {
                    return answer;
                }
            }
        }
        return answer;
    }
}
