net.sourceforge.vulcan.spring.SpringPluginManager.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.vulcan.spring.SpringPluginManager.java

Source

/*
 * Vulcan Build Manager
 * Copyright (C) 2005-2012 Chris Eldredge
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package net.sourceforge.vulcan.spring;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.vulcan.BuildTool;
import net.sourceforge.vulcan.PluginManager;
import net.sourceforge.vulcan.RepositoryAdaptor;
import net.sourceforge.vulcan.core.ConfigurationStore;
import net.sourceforge.vulcan.core.ProjectNameChangeListener;
import net.sourceforge.vulcan.dto.BuildToolConfigDto;
import net.sourceforge.vulcan.dto.ComponentVersionDto;
import net.sourceforge.vulcan.dto.PluginConfigDto;
import net.sourceforge.vulcan.dto.PluginMetaDataDto;
import net.sourceforge.vulcan.dto.ProjectConfigDto;
import net.sourceforge.vulcan.dto.RepositoryAdaptorConfigDto;
import net.sourceforge.vulcan.event.ErrorEvent;
import net.sourceforge.vulcan.event.EventHandler;
import net.sourceforge.vulcan.exception.ConfigException;
import net.sourceforge.vulcan.exception.DuplicatePluginIdException;
import net.sourceforge.vulcan.exception.PluginLoadFailureException;
import net.sourceforge.vulcan.exception.PluginNotConfigurableException;
import net.sourceforge.vulcan.exception.PluginNotFoundException;
import net.sourceforge.vulcan.exception.StoreException;
import net.sourceforge.vulcan.integration.BuildManagerObserverPlugin;
import net.sourceforge.vulcan.integration.BuildToolPlugin;
import net.sourceforge.vulcan.integration.ConfigurablePlugin;
import net.sourceforge.vulcan.integration.Plugin;
import net.sourceforge.vulcan.integration.RepositoryAdaptorPlugin;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

public class SpringPluginManager implements PluginManager, ApplicationContextAware, ProjectNameChangeListener {
    final static Log log = LogFactory.getLog(SpringPluginManager.class);

    ApplicationContext ctx;

    ConfigurationStore configurationStore;
    EventHandler eventHandler;
    BuildEventPluginPublisher buildEventPluginPublisher;
    DelegatingResourceBundleMessageSource messageSource;
    SpringBeanXmlEncoder.FactoryExpert factoryExpert;

    String pluginBeanName;

    boolean importBundledPlugins;
    String bundledPluginResourcesPattern;

    boolean initialized;

    final Map<String, PluginState> plugins = new HashMap<String, PluginState>();

    class PluginState {
        FileSystemXmlApplicationContext context;
        ClassLoader classLoader;
        PluginMetaDataDto pluginConfig;
        Plugin plugin;

        PluginState(PluginMetaDataDto pluginConfig, String contextUrl, String beanName, ApplicationContext ctx)
                throws Exception {
            final ClassLoader tmpLoader = Thread.currentThread().getContextClassLoader();

            this.pluginConfig = pluginConfig;
            this.classLoader = URLClassLoader.newInstance(this.pluginConfig.getClassPath(), tmpLoader);

            try {
                Thread.currentThread().setContextClassLoader(this.classLoader);

                this.context = new VulcanPluginFileSystemXmlApplicationContext(contextUrl, ctx, this);
            } catch (Exception e) {
                this.classLoader = null;
                throw e;
            } finally {
                Thread.currentThread().setContextClassLoader(tmpLoader);
            }

            this.plugin = (Plugin) context.getBean(beanName);
        }

        public void destroy() {
            context.close();
            context = null;
            classLoader = null;
            plugin = null;
        }

        public String getId() {
            return plugin.getId();
        }
    }

    static class VulcanPluginFileSystemXmlApplicationContext extends FileSystemXmlApplicationContext {
        private final PluginState pluginState;

        VulcanPluginFileSystemXmlApplicationContext(String url, ApplicationContext parentContext,
                PluginState state) {
            super(new String[] { url }, false, parentContext);

            this.pluginState = state;

            addParentPropertyResourceConfigurers(parentContext);

            addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
                @Override
                public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
                        throws BeansException {
                    beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
                        @Override
                        public Object postProcessBeforeInitialization(Object bean, String beanName)
                                throws BeansException {
                            if (bean instanceof ResourceBundleMessageSource) {
                                ((ResourceBundleMessageSource) bean).setBundleClassLoader(pluginState.classLoader);
                            }
                            return bean;
                        }

                        @Override
                        public Object postProcessAfterInitialization(Object bean, String beanName)
                                throws BeansException {
                            return bean;
                        }
                    });
                }
            });

            refresh();
        }

        /**
         * Attempt to load a resource relative to the plugin directory first.
         * If the resource does not exist there, fall back to the default behavior.
         */
        @Override
        protected Resource getResourceByPath(String path) {
            final File file = new File(this.pluginState.pluginConfig.getDirectory(), path);

            if (file.exists()) {
                return new FileSystemResource(file);
            }

            return super.getResourceByPath(path);
        }

        @SuppressWarnings("unchecked")
        private void addParentPropertyResourceConfigurers(ApplicationContext parentContext) {
            if (!(parentContext instanceof AbstractApplicationContext)) {
                return;
            }
            final Collection<PropertyResourceConfigurer> beans = parentContext
                    .getBeansOfType(PropertyResourceConfigurer.class).values();

            for (PropertyResourceConfigurer bean : beans) {
                addBeanFactoryPostProcessor(bean);
            }
        }
    }

    @Override
    public void init() {
        initialized = true;

        importBundledPlugins();

        final PluginMetaDataDto[] plugins = configurationStore.getPluginConfigs();

        for (int i = 0; i < plugins.length; i++) {
            try {
                createPlugin(plugins[i], false);
            } catch (PluginLoadFailureException e) {
                final ErrorEvent errorEvent = new ErrorEvent(this, "PluginManager.load.failed",
                        new Object[] { plugins[i].getId(), e.getMessage() }, e);
                eventHandler.reportEvent(errorEvent);
            }
        }
    }

    public void shutdown() {
        while (plugins.size() > 0) {
            final String id = plugins.keySet().iterator().next();
            destroyPlugin(id);
        }
    }

    @Override
    public synchronized void importPluginZip(InputStream in) throws StoreException, PluginLoadFailureException {
        final PluginMetaDataDto pluginConfig = configurationStore.extractPlugin(in);
        createPlugin(pluginConfig, true);
    }

    @Override
    public synchronized void removePlugin(String id) throws StoreException, PluginNotFoundException {
        destroyPlugin(id);

        configurationStore.deletePlugin(id);
    }

    @Override
    public synchronized File getPluginDirectory(String id) throws PluginNotFoundException {
        return findPluginState(id).pluginConfig.getDirectory();
    }

    @Override
    public synchronized PluginConfigDto getPluginConfigInfo(String id)
            throws PluginNotConfigurableException, PluginNotFoundException {
        final PluginState state = findPluginState(id);

        if (state.plugin instanceof ConfigurablePlugin) {
            final PluginConfigDto configuration = ((ConfigurablePlugin) state.plugin).getConfiguration();
            configuration.setApplicationContext(state.context);
            return configuration;
        }
        throw new PluginNotConfigurableException();
    }

    @Override
    public void configurePlugin(PluginConfigDto pluginConfig) throws PluginNotFoundException {
        final PluginState state = findPluginState(pluginConfig.getPluginId());

        if (state.plugin instanceof ConfigurablePlugin) {
            ((ConfigurablePlugin) state.plugin).setConfiguration(pluginConfig);
        }
    }

    @Override
    public Object createObject(String id, String className)
            throws PluginNotFoundException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        final PluginState state = findPluginState(id);

        final Object object = state.classLoader.loadClass(className).newInstance();
        if (object instanceof ApplicationContextAware) {
            ((ApplicationContextAware) object).setApplicationContext(state.context);
        }

        return object;
    }

    @Override
    @SuppressWarnings("unchecked")
    public Enum<?> createEnum(String id, String className, String enumName)
            throws ClassNotFoundException, PluginNotFoundException {
        final PluginState state = findPluginState(id);

        @SuppressWarnings("rawtypes")
        Class c = state.classLoader.loadClass(className);

        return Enum.valueOf(c, enumName);
    }

    @Override
    @SuppressWarnings("unchecked")
    public synchronized <T extends Plugin> List<T> getPlugins(Class<T> type) {
        final List<T> list = new ArrayList<T>();

        for (PluginState state : plugins.values()) {
            if (type.isAssignableFrom(state.plugin.getClass())) {
                list.add((T) state.plugin);
            }
        }

        Collections.sort(list, new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        return list;
    }

    /**
     * Convenience method for JSP integration.
     */
    public List<BuildToolPlugin> getBuildToolPlugins() {
        return getPlugins(BuildToolPlugin.class);
    }

    /**
     * Convenience method for JSP integration.
     */
    public List<RepositoryAdaptorPlugin> getRepositoryPlugins() {
        return getPlugins(RepositoryAdaptorPlugin.class);
    }

    /**
     * Convenience method for JSP integration.
     */
    public List<BuildManagerObserverPlugin> getObserverPlugins() {
        return getPlugins(BuildManagerObserverPlugin.class);
    }

    @Override
    public synchronized List<ComponentVersionDto> getPluginVersions() {
        final ArrayList<ComponentVersionDto> versions = new ArrayList<ComponentVersionDto>();

        for (PluginState p : plugins.values()) {
            final ComponentVersionDto v = new ComponentVersionDto(ComponentVersionDto.ComponentType.Plugin);

            v.setId(p.pluginConfig.getId());
            v.setVersion(p.pluginConfig.getVersion());
            v.setName(p.plugin.getName());

            versions.add(v);
        }

        Collections.sort(versions, new Comparator<ComponentVersionDto>() {
            @Override
            public int compare(ComponentVersionDto o1, ComponentVersionDto o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        return versions;
    }

    @Override
    public RepositoryAdaptor createRepositoryAdaptor(String id, ProjectConfigDto projectConfig)
            throws PluginNotFoundException, ConfigException {
        final PluginState state = findPluginState(id);

        final RepositoryAdaptorPlugin plugin = (RepositoryAdaptorPlugin) state.plugin;

        return plugin.createInstance(projectConfig);
    }

    @Override
    public BuildTool createBuildTool(String id, BuildToolConfigDto buildToolConfig)
            throws PluginNotFoundException, ConfigException {
        final PluginState state = findPluginState(id);

        final BuildToolPlugin plugin = (BuildToolPlugin) state.plugin;

        return plugin.createInstance(buildToolConfig);
    }

    @Override
    public RepositoryAdaptorConfigDto getRepositoryAdaptorDefaultConfig(String id) throws PluginNotFoundException {
        final PluginState state = findPluginState(id);

        final RepositoryAdaptorConfigDto defaultConfig = ((RepositoryAdaptorPlugin) state.plugin)
                .getDefaultConfig();
        defaultConfig.setApplicationContext(state.context);

        return defaultConfig;
    }

    @Override
    public BuildToolConfigDto getBuildToolDefaultConfig(String id) throws PluginNotFoundException {
        final PluginState state = findPluginState(id);

        final BuildToolConfigDto defaultConfig = ((BuildToolPlugin) state.plugin).getDefaultConfig();
        defaultConfig.setApplicationContext(state.context);

        return defaultConfig;
    }

    @Override
    public void projectNameChanged(String oldName, String newName) {
        for (PluginState state : plugins.values()) {
            if (state.plugin instanceof ProjectNameChangeListener) {
                ((ProjectNameChangeListener) state.plugin).projectNameChanged(oldName, newName);
            }
        }
    }

    public ConfigurationStore getConfigurationStore() {
        return configurationStore;
    }

    public void setConfigurationStore(ConfigurationStore store) {
        this.configurationStore = store;
    }

    public String getPluginBeanName() {
        return pluginBeanName;
    }

    public void setPluginBeanName(String pluginBeanName) {
        this.pluginBeanName = pluginBeanName;
    }

    public EventHandler getEventHandler() {
        return eventHandler;
    }

    public void setEventHandler(EventHandler eventHandler) {
        this.eventHandler = eventHandler;
    }

    public DelegatingResourceBundleMessageSource getMessageSource() {
        return messageSource;
    }

    public void setMessageSource(DelegatingResourceBundleMessageSource messageSource) {
        this.messageSource = messageSource;
    }

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

    public BuildEventPluginPublisher getBuildEventPluginPublisher() {
        return buildEventPluginPublisher;
    }

    public void setBuildEventPluginPublisher(BuildEventPluginPublisher buildEventPluginPublisher) {
        this.buildEventPluginPublisher = buildEventPluginPublisher;
    }

    public boolean isImportBundledPlugins() {
        return importBundledPlugins;
    }

    public void setImportBundledPlugins(boolean importBundledPlugins) {
        this.importBundledPlugins = importBundledPlugins;
    }

    public String getBundledPluginResourcesPattern() {
        return bundledPluginResourcesPattern;
    }

    public void setBundledPluginResourcesPattern(String bundledPluginResourcesPattern) {
        this.bundledPluginResourcesPattern = bundledPluginResourcesPattern;
    }

    public SpringBeanXmlEncoder.FactoryExpert getFactoryExpert() {
        return factoryExpert;
    }

    public void setFactoryExpert(SpringBeanXmlEncoder.FactoryExpert factoryExpert) {
        this.factoryExpert = factoryExpert;
    }

    synchronized void createPlugin(PluginMetaDataDto plugin, boolean deleteOnFailure)
            throws PluginLoadFailureException {
        final String id = plugin.getId();

        try {
            final String contextUrl = new File(plugin.getDirectory(), "vulcan-plugin.xml").toURI().toURL()
                    .toString();
            final PluginState state = new PluginState(plugin, contextUrl, pluginBeanName, ctx);

            if (plugins.containsKey(id)) {
                destroyPlugin(id);
            }
            plugins.put(id, state);
            factoryExpert.registerPlugin(state.classLoader, state.getId());

            messageSource.addDelegate(state.context);
            if (state.plugin instanceof BuildManagerObserverPlugin) {
                buildEventPluginPublisher.add((BuildManagerObserverPlugin) state.plugin);
            }
        } catch (Exception e) {
            if (deleteOnFailure) {
                try {
                    configurationStore.deletePlugin(id);
                } catch (Exception e1) {
                    eventHandler.reportEvent(
                            new ErrorEvent(this, "PluginManager.delete.failed", new Object[] { id }, e1));
                }
            }
            throw new PluginLoadFailureException(e.getMessage(), e);
        }
    }

    synchronized PluginState findPluginState(final String id) throws PluginNotFoundException {
        final PluginState state = plugins.get(id);
        if (state == null) {
            throw new PluginNotFoundException(id);
        }
        return state;
    }

    private void destroyPlugin(String id) {
        final PluginState state = plugins.remove(id);
        if (state != null) {
            if (state.plugin instanceof BuildManagerObserverPlugin) {
                buildEventPluginPublisher.remove((BuildManagerObserverPlugin) state.plugin);
            }
            messageSource.removeDelegate(state.context);
            state.destroy();
        }
    }

    private void importBundledPlugins() {
        if (!importBundledPlugins) {
            return;
        }

        final Resource[] resources;

        try {
            resources = ctx.getResources(bundledPluginResourcesPattern);
        } catch (IOException e) {
            log.error("Failure looking for bundled plugins", e);
            return;
        }

        for (Resource r : resources) {
            try {
                configurationStore.extractPlugin(r.getInputStream());
            } catch (DuplicatePluginIdException ignore) {
                // This happens when plugin is up to date.
            } catch (Exception e) {
                ErrorEvent errorEvent = new ErrorEvent(this, "PluginManager.load.failed",
                        new Object[] { r.getFilename(), e.getMessage() }, e);
                eventHandler.reportEvent(errorEvent);
            }
        }
    }
}