001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.plugin;
017    
018    import org.apache.log4j.Logger;
019    import org.kuali.rice.core.api.config.property.Config;
020    import org.kuali.rice.core.api.config.property.ConfigContext;
021    import org.kuali.rice.core.api.util.ClassLoaderUtils;
022    import org.kuali.rice.kew.plugin.PluginUtils.PluginZipFileFilter;
023    
024    import java.io.File;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Set;
028    
029    
030    /**
031     * Checks for plugins added to or removed from the configured plugin directories.
032     *
033     * @author Kuali Rice Team (rice.collab@kuali.org)
034     */
035    public class HotDeployer implements Runnable {
036            private static final Logger LOG = Logger.getLogger(HotDeployer.class);
037    
038    
039            private PluginRegistry registry;
040            private File sharedPluginDirectory;
041            private List<String> pluginDirectories;
042    
043            public HotDeployer(PluginRegistry registry, File sharedPluginDirectory, List<String> pluginDirectories) {
044                    this.registry = registry;
045                    this.sharedPluginDirectory = sharedPluginDirectory;
046                    this.pluginDirectories = pluginDirectories;
047            }
048    
049            public synchronized void run() {
050                    try {
051                            LOG.debug("Checking for added and removed plugins...");
052                            Set<PluginEnvironment> removedPlugins = getRemovedPlugins();
053                            for (PluginEnvironment pluginContext : removedPlugins) {
054                                    LOG.info("Detected a removed plugin '" + pluginContext.getPluginName() + "', shutting down plugin.");
055                                    try {
056                                            pluginContext.unload();
057                                            registry.removePluginEnvironment(pluginContext.getPluginName());
058                                    } catch (Exception e) {
059                                            LOG.error("Failed to unload plugin '" + pluginContext.getPluginName() + "'", e);
060                                    }
061                            }
062                            Set<PluginEnvironment> addedPlugins = getAddedPlugins();
063                            for (PluginEnvironment pluginContext : addedPlugins) {
064                                    try {
065                                            LOG.info("Detected a new plugin.  Loading plugin...");
066                                            pluginContext.load();
067                                            LOG.info("...plugin '" + pluginContext.getPluginName() + "' loaded.");                                  
068                                    } catch (Exception e) {
069                                        // see: KULRICE-1184
070                                        String pluginName= "<<unknown>>";
071                                        if (pluginContext.getPlugin() != null) {
072                                            pluginName = String.valueOf(pluginContext.getPluginName());
073                                        }
074                                            LOG.error("Failed to load plugin '" + pluginName + "'", e);
075                                    } finally {
076                                        // whether or not there is a load error, we want to add the environment to the registry, this prevents the registry
077                                        // continuously trying to hot deploy a bad plugin
078                                        registry.addPluginEnvironment(pluginContext);
079                                    }
080                            }
081                    } catch (Exception e) {
082                            LOG.warn("Failed to check for hot deploy.", e);
083                    }
084            }
085    
086            protected Set<PluginEnvironment> getRemovedPlugins() {
087                    Set<PluginEnvironment> removedPlugins = new HashSet<PluginEnvironment>();
088                    for (PluginEnvironment environment : registry.getPluginEnvironments()) {
089                            if (environment.getLoader().isRemoved()) {
090                                    removedPlugins.add(environment);
091                            }
092                    }
093                    return removedPlugins;
094            }
095    
096            protected Set<PluginEnvironment> getAddedPlugins() throws Exception {
097                    Set<PluginEnvironment> addedPlugins = new HashSet<PluginEnvironment>();
098                    Set<File> newPluginZipFiles = new HashSet<File>();
099                    // for now, this implementation should watch the plugin directories for more plugins
100                    // TODO somehow the code which checks for new plugins and which loads plugins initially needs to be
101                    // consolidated, maybe with some sort of set of PluginLocators? or something along those lines?
102                    for (String pluginDirName : pluginDirectories) {
103                            File pluginDir = new File(pluginDirName);
104                            if (pluginDir.exists() && pluginDir.isDirectory()) {
105                                    File[] pluginDirFiles = pluginDir.listFiles(new PluginZipFileFilter());
106                                    for (File pluginZip : pluginDirFiles) {
107                                            int indexOf = pluginZip.getName().lastIndexOf(".zip");
108                                            String pluginName = pluginZip.getName().substring(0, indexOf);
109                                            // check to see if this plugin environment has already been loaded
110                                            List<PluginEnvironment> currentEnvironments = registry.getPluginEnvironments();
111                                            boolean pluginExists = false;
112                                            for (PluginEnvironment environment : currentEnvironments) {
113                                                    if (environment.getPluginName().equals(pluginName)) {
114                                                            pluginExists = true;
115                                                            break;
116                                                    }
117                                            }
118                                            if (!pluginExists) {
119                                                    // make sure the plugin's not in the process of being copied
120                                                    long lastModified1 = pluginZip.lastModified();
121                                                    Thread.sleep(100);
122                                                    long lastModified2 = pluginZip.lastModified();
123                                                    if (lastModified1 == lastModified2) {
124                                                            newPluginZipFiles.add(pluginZip);
125                                                    } else {
126                                                            LOG.warn("Detected that the plugin zip is still being modified, holding off on hot deploy: " + pluginZip.getAbsolutePath());
127                                                    }
128                                            }
129                                    }
130                            }
131                    }
132    
133                    ClassLoader parentClassLoader = ClassLoaderUtils.getDefaultClassLoader();
134                    Config parentConfig = ConfigContext.getCurrentContextConfig();
135                    for (File newPluginZipFile : newPluginZipFiles) {
136                            PluginLoader loader = new ZipFilePluginLoader(newPluginZipFile, sharedPluginDirectory, parentClassLoader, parentConfig);
137                            PluginEnvironment environment = new PluginEnvironment(loader, registry);
138                            addedPlugins.add(environment);
139                    }
140                    return addedPlugins;
141            }
142    
143    }