001    /*
002     * Copyright 2005-2007 The Kuali Foundation
003     *
004     *
005     * Licensed under the Educational Community License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     * http://www.opensource.org/licenses/ecl2.php
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.kuali.rice.kew.plugin;
018    
019    import java.io.File;
020    import java.util.ArrayList;
021    import java.util.HashSet;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    import java.util.TreeMap;
026    import java.util.concurrent.Executors;
027    import java.util.concurrent.ScheduledExecutorService;
028    import java.util.concurrent.ScheduledFuture;
029    import java.util.concurrent.ThreadFactory;
030    import java.util.concurrent.TimeUnit;
031    
032    import javax.xml.namespace.QName;
033    
034    import org.kuali.rice.core.config.Config;
035    import org.kuali.rice.core.config.ConfigContext;
036    import org.kuali.rice.core.resourceloader.ResourceLoader;
037    import org.kuali.rice.core.util.ClassLoaderUtils;
038    import org.kuali.rice.kew.plugin.PluginUtils.PluginZipFileFilter;
039    
040    /**
041     * A PluginRegistry implementation which loads plugins from the file system on the server.
042     *
043     * @author Kuali Rice Team (rice.collab@kuali.org)
044     */
045    public class ServerPluginRegistry extends BasePluginRegistry {
046    
047            private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ServerPluginRegistry.class);
048    
049            private List<String> pluginDirectories = new ArrayList<String>();
050            private File sharedPluginDirectory;
051            private Reloader reloader;
052            private HotDeployer hotDeployer;
053    
054            private ScheduledExecutorService scheduledExecutor;
055            private ScheduledFuture reloaderFuture;
056            private ScheduledFuture hotDeployerFuture;
057    
058    
059            public ServerPluginRegistry() {
060                    super(new QName(ConfigContext.getCurrentContextConfig().getServiceNamespace(), ResourceLoader.PLUGIN_REGISTRY_LOADER_NAME));
061            }
062    
063            public void start() throws Exception {
064                    LOG.info("Starting server Plugin Registry...");
065                    scheduledExecutor = Executors.newScheduledThreadPool(2, new KEWThreadFactory());
066                    sharedPluginDirectory = loadSharedPlugin();
067                    reloader = new Reloader();
068                    hotDeployer = new HotDeployer(PluginUtils.getPluginRegistry(), sharedPluginDirectory, pluginDirectories);
069                    loadPlugins(sharedPluginDirectory);
070                    // TODO make the delay configurable
071                    this.reloaderFuture = scheduledExecutor.scheduleWithFixedDelay(reloader, 5, 5, TimeUnit.SECONDS);
072                    this.hotDeployerFuture = scheduledExecutor.scheduleWithFixedDelay(hotDeployer, 5, 5, TimeUnit.SECONDS);
073                    super.start();
074                    LOG.info("...server Plugin Registry successfully started.");
075            }
076    
077            public void stop() throws Exception {
078                    LOG.info("Stopping server Plugin Registry...");
079                    stopReloader();
080                    stopHotDeployer();
081                    reloader = null;
082                    hotDeployer = null;
083    
084                    if (scheduledExecutor != null) {
085                            scheduledExecutor.shutdownNow();
086                            scheduledExecutor = null;
087                    }
088                    super.stop();
089                    LOG.info("...server Plugin Registry successfully stopped.");
090            }
091    
092            protected void stopReloader() {
093                    if (reloaderFuture != null) {
094                            if (!reloaderFuture.cancel(true)) {
095                                    LOG.warn("Failed to cancel the plugin reloader.");
096                            }
097                            reloaderFuture = null;
098                    }
099            }
100    
101            protected void stopHotDeployer() {
102                    if (hotDeployerFuture != null) {
103                            if (!hotDeployerFuture.cancel(true)) {
104                                    LOG.warn("Failed to cancel the hot deployer.");
105                            }
106                            hotDeployerFuture = null;
107                    }
108            }
109    
110            protected void loadPlugins(File sharedPluginDirectory) {
111            Map<String, File> pluginLocations = new TreeMap<String, File>(new PluginNameComparator());
112                    PluginZipFileFilter pluginFilter = new PluginZipFileFilter();
113            //PluginDirectoryFilter pluginFilter = new PluginDirectoryFilter(sharedPluginDirectory);
114            Set<File> visitedFiles = new HashSet<File>();
115            for (String pluginDir : pluginDirectories) {
116                LOG.info("Reading plugins from " + pluginDir);
117                File file = new File(pluginDir);
118                if (visitedFiles.contains(file)) {
119                    LOG.info("Skipping visited directory: " + pluginDir);
120                    continue;
121                }
122                visitedFiles.add(file);
123                if (!file.exists() || !file.isDirectory()) {
124                    LOG.warn(file.getAbsoluteFile()+" is not a valid plugin directory.");
125                    continue;
126                }
127                File[] pluginZips = file.listFiles(pluginFilter);
128                for (int i = 0; i < pluginZips.length; i++) {
129                    File pluginZip = pluginZips[i];
130                    int indexOf = pluginZip.getName().lastIndexOf(".zip");
131                    String pluginName = pluginZip.getName().substring(0, indexOf);
132                    if (pluginLocations.containsKey(pluginName)) {
133                            LOG.warn("There already exists an installed plugin with the name '"+ pluginName + "', ignoring plugin " + pluginZip.getAbsolutePath());
134                            continue;
135                    }
136                    pluginLocations.put(pluginName, pluginZip);
137                }
138            }
139            for (String pluginName : pluginLocations.keySet()) {
140                    File pluginZipFile = pluginLocations.get(pluginName);
141                    try {
142                            LOG.info("Loading plugin '" + pluginName + "'");
143                            ClassLoader parentClassLoader = ClassLoaderUtils.getDefaultClassLoader();
144                            Config parentConfig = ConfigContext.getCurrentContextConfig();
145                            ZipFilePluginLoader loader = new ZipFilePluginLoader(pluginZipFile,
146                                            sharedPluginDirectory,
147                                            parentClassLoader,
148                                            parentConfig);
149                            PluginEnvironment environment = new PluginEnvironment(loader, this);
150                            try {
151                                environment.load();
152                            } finally {
153                                // regardless of whether the plugin loads or not, let's add it to the environment
154                                addPluginEnvironment(environment);
155                            }                       
156                    } catch (Exception e) {
157                            LOG.error("Failed to read workflow plugin '"+pluginName+"'", e);
158                    }
159            }
160        }
161    
162            @Override
163            public void addPluginEnvironment(PluginEnvironment pluginEnvironment) {
164                    super.addPluginEnvironment(pluginEnvironment);
165                    reloader.addReloadable(pluginEnvironment);
166            }
167    
168            @Override
169            public PluginEnvironment removePluginEnvironment(String pluginName) {
170                    PluginEnvironment environment = super.removePluginEnvironment(pluginName);
171                    reloader.removeReloadable(environment);
172                    return environment;
173            }
174    
175            public File loadSharedPlugin() {
176                    return PluginUtils.findSharedDirectory(pluginDirectories);
177            }
178    
179            public void setPluginDirectories(List<String> pluginDirectories) {
180                    this.pluginDirectories = pluginDirectories;
181            }
182    
183            public void setSharedPluginDirectory(File sharedPluginDirectory) {
184                    this.sharedPluginDirectory = sharedPluginDirectory;
185            }
186    
187            protected HotDeployer getHotDeployer() {
188                    return hotDeployer;
189            }
190    
191            protected Reloader getReloader() {
192                    return reloader;
193            }
194            
195            private static class KEWThreadFactory implements ThreadFactory {
196                    
197                    private ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
198                    
199                    public Thread newThread(Runnable runnable) {
200                            Thread thread = defaultThreadFactory.newThread(runnable);
201                            thread.setName("ServerPluginRegistry-" + thread.getName());
202                            return thread;
203                }
204            }
205    
206    }