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 }