package no.kantega.publishing.spring;

import no.kantega.publishing.api.plugin.OpenAksessPlugin;
import org.kantega.jexmec.PluginClassLoaderProvider;
import org.kantega.jexmec.PluginLoader;
import org.kantega.jexmec.PluginManager;
import org.kantega.jexmec.ServiceLocator;
import org.kantega.jexmec.manager.DefaultPluginManager;
import org.kantega.jexmec.spring.SpringPluginLoader;
import org.kantega.jexmec.spring.SpringServiceLocator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.context.*;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.XmlWebApplicationContext;

import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.List;


/**
 */
public class PluginManagerFactory extends AbstractFactoryBean implements ApplicationContextAware, ServletContextAware, ApplicationListener {

    private ApplicationContext applicationContext;
    private ServletContext servletContext;
    private List<PluginLoader<OpenAksessPlugin>> pluginLoaders;
    private List<PluginClassLoaderProvider> pluginClassLoaderProviders;
    private List<BeanFactoryPostProcessor> postProcessors;
    private Class servicesClass;
    private Class pluginClass;

    public Class getObjectType() {
        return PluginManager.class;
    }

    public boolean isSingleton() {
        return true;
    }

    public Object createInstance() throws Exception {
        ServiceLocator serviceLocator = new SpringServiceLocator(applicationContext, servicesClass);
        SpringPluginLoader<OpenAksessPlugin> spring = new SpringPluginLoader<OpenAksessPlugin>() {

            @Override
            public List<OpenAksessPlugin> loadPlugins(Class<OpenAksessPlugin> pluginClass, ClassLoader classLoader, ServiceLocator serviceLocator) {
                checkWrongSpringXmlLocation(classLoader);
                return super.loadPlugins(pluginClass, classLoader, serviceLocator);
            }

            private void checkWrongSpringXmlLocation(ClassLoader classLoader) {
                String contextLocation = "META-INF/services/" +pluginClass.getName() +"/spring.xml";
                String contextLocationWithSlashInsteadOfDot = "META-INF/services/" +pluginClass.getName().replace('.','/') +"/spring.xml";

                try {
                    final Enumeration<URL> resources = classLoader.getResources(contextLocationWithSlashInsteadOfDot);

                    if(resources.hasMoreElements()) {
                        throw new IllegalArgumentException("We found a spring.xml file located at " + resources.nextElement() +". " +
                                "OpenAksess expects to find the spring.xml files in 'META-INF/services/" + contextLocation +"'" +
                                ", NOT '" + contextLocationWithSlashInsteadOfDot +"'. Please put spring.xml at the currect location.");
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            protected ConfigurableApplicationContext
            createApplicationContext(URL url, ClassLoader loader) {
                final String resourceBases = System.getProperty("resourceBases");
                final XmlWebApplicationContext context = resourceBases == null ? new XmlWebApplicationContext() : new DevXmlWebApplicationContext(resourceBases);
                context.setClassLoader(loader);
                context.setConfigLocation(url.toExternalForm());
                context.setServletContext(servletContext);

                return context;
            }

        };
        if (postProcessors != null) {
            spring.setBeanFactoryPostProcessors(postProcessors);
        }

        pluginClass = OpenAksessPlugin.class;
        final DefaultPluginManager manager = new DefaultPluginManager(pluginClass);
        manager.addServiceLocator(serviceLocator);
        manager.addPluginClassLoader(applicationContext.getClassLoader());
        if(pluginClassLoaderProviders != null) {
            for (PluginClassLoaderProvider provider : pluginClassLoaderProviders) {
                manager.addPluginClassLoaderProvider(provider);
            }
        }
        manager.addPluginLoader(spring);

        return manager;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public void setPostProcessors(List<BeanFactoryPostProcessor> postProcessors) {
        this.postProcessors = postProcessors;
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextRefreshedEvent && event.getSource() == applicationContext) {
            try {
                DefaultPluginManager manager = (DefaultPluginManager) getObject();
                manager.start();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public void setServicesClass(Class servicesClass) {
        this.servicesClass = servicesClass;
    }

    public void setPluginClass(Class pluginClass) {
        this.pluginClass = pluginClass;
    }

    public void setPluginClassLoaderProviders(List<PluginClassLoaderProvider> pluginClassLoaderProviders) {
        this.pluginClassLoaderProviders = pluginClassLoaderProviders;
    }

    /**
     * Check resources on resourceBases first
     */
    private class DevXmlWebApplicationContext extends XmlWebApplicationContext {
        private final String[] resourceBases;

        public DevXmlWebApplicationContext(String resourceBases) {
            this.resourceBases = resourceBases.split(File.pathSeparator);
        }

        @Override
        public Resource getResource(String location) {
            if (location.startsWith(CLASSPATH_URL_PREFIX)) {
                for(String base : resourceBases) {
                    final FileSystemResource resource = new FileSystemResource(new File(base, location.substring(CLASSPATH_URL_PREFIX.length())));
                    if(resource.exists()) {
                        return resource;
                    }
                }
            }
            return super.getResource(location);
        }
    }
}

@@ -120,33 +120,9 @@
 mec.
-PluginClassLoaderProvider
+*
 ;%0Aim
@@ -141,24 +141,31 @@
 tega.jexmec.
+events.
 PluginLoader
@@ -166,93 +166,25 @@
 Load
-er;%0Aimport org.kantega.jexmec.PluginManager;%0Aimport org.kantega.jexmec.ServiceLocator
+ingExceptionEvent
 ;%0Aim
@@ -5307,24 +5307,940 @@
     
-manager.start();
+PluginManagerListener%3COpenAksessPlugin%3E listener = new PluginManagerListener%3COpenAksessPlugin%3E() %7B%0A                    @Override%0A                    public void pluginLoadingFailedWithException(PluginLoadingExceptionEvent%3COpenAksessPlugin%3E event) %7B%0A                        String msg = %22PluginLoader %22 + event.getPluginLoader() + %22 threw exception loading plugins from class loader %22 + event.getClassLoader() + %22 provided by %22 + event.getProvider() + %22. Exception message was %22 + event.getThrowable().getMessage();%0A                        logger.error(msg, event.getThrowable());%0A                        throw new RuntimeException(msg, event.getThrowable());%0A                    %7D%0A                %7D;%0A                manager.addPluginManagerListener(listener);%0A%0A                try %7B%0A                    manager.start();%0A                %7D finally %7B%0A                    manager.removePluginManagerListener(listener);%0A                %7D
 %0A   

Add a listener that will rethrow exceptions occurring during startup of plugins.