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