com.temenos.interaction.loader.detector.SpringBasedLoaderAction.java Source code

Java tutorial

Introduction

Here is the source code for com.temenos.interaction.loader.detector.SpringBasedLoaderAction.java

Source

package com.temenos.interaction.loader.detector;

/*
 * #%L
 * interaction-dynamic-loader
 * %%
 * Copyright (C) 2012 - 2015 Temenos Holdings N.V.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
import java.io.Closeable;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.reflections.Reflections;
import org.reflections.scanners.AbstractScanner;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.vfs.Vfs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.temenos.interaction.core.command.ChainingCommandController;
import com.temenos.interaction.core.command.CommandController;
import com.temenos.interaction.core.command.InteractionCommand;
import com.temenos.interaction.core.command.SpringContextBasedInteractionCommandController;
import com.temenos.interaction.core.loader.Action;
import com.temenos.interaction.core.loader.FileEvent;
import com.temenos.interaction.loader.classloader.CachingParentLastURLClassloaderFactory;
import com.temenos.interaction.loader.objectcreation.ParameterizedFactory;

/**
 * Loads a CommandController with a set of InteractionCommands from Spring
 * configuration files.
 *
 * The execute method would be typically called after a directory change (for
 * instance, when a user copies a jar file with new InteractionCommands). All
 * jars in the directory are scanned for Spring configuration files matching the
 * pattern "/spring/*-interaction-context.xml". By default, the
 * CommandController with id "commandController", together with the defined
 * InteractionCommand, would be loaded to the top of a provided
 * ChainingCommandController.
 *
 * @author andres
 * @author trojan
 * @author cmclopes
 */
public class SpringBasedLoaderAction implements Action<FileEvent<File>>, ApplicationContextAware, InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringBasedLoaderAction.class);

    public static final String DEFAULT_COMMAND_CONTROLLER_BEAN_NAME = "commandController";

    List<String> configPatterns = new ArrayList();
    private ApplicationContext currentContext = null;
    private ApplicationContext parentContext = null;
    private boolean useCurrentContextAsParent = false;
    private List<String> configLocationsPatterns = new ArrayList(
            Arrays.asList(new String[] { "classpath:/spring*/*-interaction-context.xml" }));
    private Collection<? extends Action<ApplicationContext>> listeners = new ArrayList();
    ParameterizedFactory<FileEvent<File>, ClassLoader> classloaderFactory = new CachingParentLastURLClassloaderFactory();
    private String commandControllerBeanName = DEFAULT_COMMAND_CONTROLLER_BEAN_NAME;
    private ChainingCommandController parentChainingCommandController = null;
    private CommandController previouslyAddedCommandController = null;
    private ApplicationContext previousAppCtx = null;

    @Override
    public void execute(FileEvent<File> dirEvent) {
        LOGGER.debug("Creation of new Spring ApplicationContext based CommandController triggerred by change in",
                dirEvent.getResource().getAbsolutePath());

        Collection<File> jars = FileUtils.listFiles(dirEvent.getResource(), new String[] { "jar" }, true);
        Set<URL> urls = new HashSet();
        for (File f : jars) {
            try {
                LOGGER.trace("Adding {} to list of URLs to create ApplicationContext from", f.toURI().toURL());
                urls.add(f.toURI().toURL());
            } catch (MalformedURLException ex) {
                // kindly ignore and log
            }
        }
        Reflections reflectionHelper = new Reflections(
                new ConfigurationBuilder().addClassLoader(classloaderFactory.getForObject(dirEvent))
                        .addScanners(new ResourcesScanner()).addUrls(urls));

        Set<String> resources = new HashSet();

        for (String locationPattern : configLocationsPatterns) {
            String regex = convertWildcardToRegex(locationPattern);
            resources.addAll(reflectionHelper.getResources(Pattern.compile(regex)));
        }

        if (!resources.isEmpty()) {
            // if resources are empty just clean up the previous ApplicationContext and leave!
            LOGGER.debug("Detected potential Spring config files to load");
            ClassPathXmlApplicationContext context;
            if (parentContext != null) {
                context = new ClassPathXmlApplicationContext(parentContext);
            } else {
                context = new ClassPathXmlApplicationContext();
            }

            context.setConfigLocations(configLocationsPatterns.toArray(new String[] {}));

            ClassLoader childClassLoader = classloaderFactory.getForObject(dirEvent);
            context.setClassLoader(childClassLoader);
            context.refresh();

            CommandController cc = null;

            try {
                cc = context.getBean(commandControllerBeanName, CommandController.class);
                LOGGER.debug("Detected pre-configured CommandController in added config files");
            } catch (BeansException ex) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("No detected pre-configured CommandController in added config files.", ex);
                }
                Map<String, InteractionCommand> commands = context.getBeansOfType(InteractionCommand.class);
                if (!commands.isEmpty()) {
                    LOGGER.debug("Adding new commands");
                    SpringContextBasedInteractionCommandController scbcc = new SpringContextBasedInteractionCommandController();
                    scbcc.setApplicationContext(context);
                    cc = scbcc;
                } else {
                    LOGGER.debug("No commands detected to be added");
                }
            }

            if (parentChainingCommandController != null) {
                List<CommandController> newCommandControllers = new ArrayList<CommandController>(
                        parentChainingCommandController.getCommandControllers());

                // "unload" the previously loaded CommandController
                if (previouslyAddedCommandController != null) {
                    LOGGER.debug("Removing previously added instance of CommandController");
                    newCommandControllers.remove(previouslyAddedCommandController);
                }

                // if there is a new CommandController on the Spring file, add it on top of the chain
                if (cc != null) {
                    LOGGER.debug("Adding newly created CommandController to ChainingCommandController");
                    newCommandControllers.add(0, cc);
                    parentChainingCommandController.setCommandControllers(newCommandControllers);
                    previouslyAddedCommandController = cc;
                } else {
                    previouslyAddedCommandController = null;
                }
            } else {
                LOGGER.debug(
                        "No ChainingCommandController set to add newly created CommandController to - skipping action");
            }

            if (previousAppCtx != null) {
                if (previousAppCtx instanceof Closeable) {
                    try {
                        ((Closeable) previousAppCtx).close();
                    } catch (Exception ex) {
                        LOGGER.error("Error closing the ApplicationContext.", ex);
                    }
                }
                previousAppCtx = context;
            }
        } else {
            LOGGER.debug("No Spring config files detected in the JARs scanned");
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        currentContext = ac;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (parentContext == null && currentContext != null && useCurrentContextAsParent) {
            parentContext = currentContext;
        }
    }

    /**
     * @return the listeners
     */
    public Collection<? extends Action<ApplicationContext>> getListeners() {
        return listeners;
    }

    /**
     * @param listeners the listeners to set
     */
    public void setListeners(Collection<? extends Action<ApplicationContext>> listeners) {
        this.listeners = new ArrayList(listeners);
    }

    /**
     * @return the classloaderFactory
     */
    public ParameterizedFactory<FileEvent<File>, ClassLoader> getClassloaderFactory() {
        return classloaderFactory;
    }

    /**
     * @param classloaderFactory the classloaderFactory to set
     */
    public void setClassloaderFactory(ParameterizedFactory<FileEvent<File>, ClassLoader> classloaderFactory) {
        this.classloaderFactory = classloaderFactory;
    }

    /**
     * @return the parentContext
     */
    public ApplicationContext getParentContext() {
        return parentContext;
    }

    /**
     * @param parentContext the parentContext to set
     */
    public void setParentContext(ApplicationContext parentContext) {
        this.parentContext = parentContext;
    }

    /**
     * @return the useCurrentContextAsParent
     */
    public boolean isUseCurrentContextAsParent() {
        return useCurrentContextAsParent;
    }

    /**
     * @param useCurrentContextAsParent the useCurrentContextAsParent to set
     */
    public void setUseCurrentContextAsParent(boolean useCurrentContextAsParent) {
        this.useCurrentContextAsParent = useCurrentContextAsParent;
    }

    /**
     * @return the configLocationsPatterns
     */
    public List<String> getConfigLocationsPatterns() {
        return configLocationsPatterns;
    }

    /**
     * @param configLocationsPatterns the configLocationsPatterns to set
     */
    public void setConfigLocationsPatterns(List<String> configLocationsPatterns) {
        this.configLocationsPatterns = configLocationsPatterns;
    }

    /**
     * @return the commandControllerBeanName
     */
    public String getCommandControllerBeanName() {
        return commandControllerBeanName;
    }

    /**
     * @param commandControllerBeanName the commandControllerBeanName to set
     */
    public void setCommandControllerBeanName(String commandControllerBeanName) {
        this.commandControllerBeanName = commandControllerBeanName;
    }

    /**
     * @return the parentChainingCommandController
     */
    public ChainingCommandController getParentChainingCommandController() {
        return parentChainingCommandController;
    }

    /**
     * @param parentChainingCommandController the
     * parentChainingCommandController to set
     */
    public void setParentChainingCommandController(ChainingCommandController parentChainingCommandController) {
        this.parentChainingCommandController = parentChainingCommandController;
    }

    public static class CurrentThreadClassLoaderFactory
            implements ParameterizedFactory<FileEvent<File>, ClassLoader> {

        public CurrentThreadClassLoaderFactory() {
        }

        @Override
        public ClassLoader getForObject(FileEvent<File> param) {

            return Thread.currentThread().getContextClassLoader();

        }
    }

    private String convertWildcardToRegex(String wildcardPattern) {
        wildcardPattern = wildcardPattern.substring(wildcardPattern.indexOf(':') + 1);
        wildcardPattern = wildcardPattern.substring(wildcardPattern.lastIndexOf("/") + 1);
        return wildcardPattern.replaceAll("\\*", "[^/]*");
    }

    public static class ResourcesScanner extends AbstractScanner {

        public boolean acceptsInput(String file) {
            return !file.endsWith(".class"); //not a class
        }

        @Override
        public Object scan(Vfs.File file, Object classObject) {
            getStore().put(file.getName(), file.getRelativePath());
            return classObject;
        }

        public void scan(Object cls) {
            throw new UnsupportedOperationException(); //shouldn't get here
        }
    }

}