001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.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/ecl1.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 edu.iu.uis.eden.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:
027: import javax.xml.namespace.QName;
028:
029: import org.kuali.bus.services.KSBServiceLocator;
030: import org.kuali.rice.config.Config;
031: import org.kuali.rice.core.Core;
032: import org.kuali.rice.resourceloader.ResourceLoader;
033: import org.quartz.CronTrigger;
034: import org.quartz.JobDetail;
035: import org.quartz.SimpleTrigger;
036: import org.quartz.Trigger;
037:
038: import edu.emory.mathcs.backport.java.util.concurrent.Executors;
039: import edu.emory.mathcs.backport.java.util.concurrent.ScheduledExecutorService;
040: import edu.emory.mathcs.backport.java.util.concurrent.ScheduledFuture;
041: import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
042: import edu.iu.uis.eden.EdenConstants;
043: import edu.iu.uis.eden.mail.DailyEmailJob;
044: import edu.iu.uis.eden.plugin.PluginUtils.PluginZipFileFilter;
045: import edu.iu.uis.eden.util.ClassLoaderUtils;
046:
047: /**
048: * A PluginRegistry implementation which loads plugins from the file system on the server.
049: *
050: * @author Eric Westfall
051: */
052: public class ServerPluginRegistry extends BasePluginRegistry {
053:
054: private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
055: .getLogger(ServerPluginRegistry.class);
056:
057: private List<String> pluginDirectories = new ArrayList<String>();
058: private File sharedPluginDirectory;
059: //consider removing this from here and using the super class to get it.
060: private Plugin institutionalPlugin;
061: private Reloader reloader;
062: private HotDeployer hotDeployer;
063:
064: private ScheduledExecutorService scheduledExecutor;
065: private ScheduledFuture reloaderFuture;
066: private ScheduledFuture hotDeployerFuture;
067:
068: public ServerPluginRegistry() {
069: super (new QName(Core.getCurrentContextConfig()
070: .getMessageEntity(),
071: ResourceLoader.PLUGIN_REGISTRY_LOADER_NAME));
072: }
073:
074: public void start() throws Exception {
075: scheduledExecutor = Executors.newScheduledThreadPool(2);
076: sharedPluginDirectory = loadSharedPlugin();
077: reloader = new Reloader();
078: hotDeployer = new HotDeployer(PluginUtils.getPluginRegistry(),
079: sharedPluginDirectory, pluginDirectories);
080: loadPlugins(sharedPluginDirectory);
081: // TODO make the delay configurable
082: // TODO if we are running in dev.mode than the thread pool isn't started which causes the scheduling below to fail
083: // just disable hot deploy and reload in dev.mode for now, but better solution needed. Probably just use our own
084: // thread pool here instead
085: if (!Core.getCurrentContextConfig().getDevMode()) {
086: this .reloaderFuture = scheduledExecutor
087: .scheduleWithFixedDelay(reloader, 5, 5,
088: TimeUnit.SECONDS);
089: this .hotDeployerFuture = scheduledExecutor
090: .scheduleWithFixedDelay(hotDeployer, 5, 5,
091: TimeUnit.SECONDS);
092: }
093: super .start();
094: }
095:
096: public void stop() throws Exception {
097: stopReloader();
098: stopHotDeployer();
099: reloader = null;
100: hotDeployer = null;
101:
102: // cleanup reference to institutional plugin
103: institutionalPlugin = null;
104: if (scheduledExecutor != null) {
105: scheduledExecutor.shutdownNow();
106: scheduledExecutor = null;
107: }
108: super .stop();
109: }
110:
111: protected void stopReloader() {
112: if (reloaderFuture != null) {
113: if (!reloaderFuture.cancel(true)) {
114: LOG.warn("Failed to cancel the plugin reloader.");
115: }
116: reloaderFuture = null;
117: }
118: }
119:
120: protected void stopHotDeployer() {
121: if (hotDeployerFuture != null) {
122: if (!hotDeployerFuture.cancel(true)) {
123: LOG.warn("Failed to cancel the hot deployer.");
124: }
125: hotDeployerFuture = null;
126: }
127: }
128:
129: protected void loadPlugins(File sharedPluginDirectory) {
130: Map<String, File> pluginLocations = new TreeMap<String, File>(
131: new PluginNameComparator(PluginUtils
132: .getInstitutionalPluginName()));
133: PluginZipFileFilter pluginFilter = new PluginZipFileFilter();
134: //PluginDirectoryFilter pluginFilter = new PluginDirectoryFilter(sharedPluginDirectory);
135: Set<File> visitedFiles = new HashSet<File>();
136: for (String pluginDir : pluginDirectories) {
137: LOG.info("Reading plugins from " + pluginDir);
138: File file = new File(pluginDir);
139: if (visitedFiles.contains(file)) {
140: LOG.info("Skipping visited directory: " + pluginDir);
141: continue;
142: }
143: visitedFiles.add(file);
144: if (!file.exists() || !file.isDirectory()) {
145: LOG.warn(file.getAbsoluteFile()
146: + " is not a valid plugin directory.");
147: continue;
148: }
149: File[] pluginZips = file.listFiles(pluginFilter);
150: for (int i = 0; i < pluginZips.length; i++) {
151: File pluginZip = pluginZips[i];
152: int indexOf = pluginZip.getName().lastIndexOf(".zip");
153: String pluginName = pluginZip.getName().substring(0,
154: indexOf);
155: if (pluginLocations.containsKey(pluginName)) {
156: LOG
157: .warn("There already exists an installed plugin with the name '"
158: + pluginName
159: + "', ignoring plugin "
160: + pluginZip.getAbsolutePath());
161: continue;
162: }
163: pluginLocations.put(pluginName, pluginZip);
164: }
165: }
166: Plugin institutionalPlugin = null;
167: for (String pluginName : pluginLocations.keySet()) {
168: File pluginZipFile = pluginLocations.get(pluginName);
169: // now execute the loading of the plugins
170: boolean isInstitutionalPlugin = PluginUtils
171: .isInstitutionalPlugin(pluginName);
172: try {
173: LOG.info("Loading "
174: + (isInstitutionalPlugin ? "Institutional "
175: : "") + "plugin '" + pluginName + "'");
176: ClassLoader parentClassLoader = ClassLoaderUtils
177: .getDefaultClassLoader();
178: Config parentConfig = Core.getCurrentContextConfig();
179: if (institutionalPlugin != null) {
180: parentClassLoader = institutionalPlugin
181: .getClassLoader();
182: parentConfig = institutionalPlugin.getConfig();
183: }
184: ZipFilePluginLoader loader = new ZipFilePluginLoader(
185: pluginZipFile, sharedPluginDirectory,
186: parentClassLoader, parentConfig,
187: isInstitutionalPlugin);
188: PluginEnvironment environment = new PluginEnvironment(
189: loader, this );
190: environment.load();
191: // TODO consider moving this inside either the loader or the environment? Because this will need to be able
192: // to be reset if the institutional plugin is "hot deployed"
193: if (isInstitutionalPlugin) {
194: setInstitutionalPlugin(environment.getPlugin());
195: }
196: addPluginEnvironment(environment);
197: } catch (Exception e) {
198: LOG.error("Failed to read workflow plugin '"
199: + pluginName + "'", e);
200: if (isInstitutionalPlugin) {
201: throw new PluginException(
202: "Failed to load the institutional plugin with name '"
203: + pluginName + "'.", e);
204: }
205: }
206: }
207: }
208:
209: @Override
210: public void addPluginEnvironment(PluginEnvironment pluginEnvironment) {
211: super .addPluginEnvironment(pluginEnvironment);
212: reloader.addReloadable(pluginEnvironment);
213: }
214:
215: @Override
216: public PluginEnvironment removePluginEnvironment(QName pluginName) {
217: PluginEnvironment environment = super
218: .removePluginEnvironment(pluginName);
219: reloader.removeReloadable(environment);
220: return environment;
221: }
222:
223: public File loadSharedPlugin() {
224: return PluginUtils.findSharedDirectory(pluginDirectories);
225: }
226:
227: public void setPluginDirectories(List<String> pluginDirectories) {
228: this .pluginDirectories = pluginDirectories;
229: }
230:
231: public void setSharedPluginDirectory(File sharedPluginDirectory) {
232: this .sharedPluginDirectory = sharedPluginDirectory;
233: }
234:
235: public Plugin getInstitutionalPlugin() {
236: return institutionalPlugin;
237: }
238:
239: public void setInstitutionalPlugin(Plugin institutionalPlugin) {
240: this .institutionalPlugin = institutionalPlugin;
241: }
242:
243: protected HotDeployer getHotDeployer() {
244: return hotDeployer;
245: }
246:
247: protected Reloader getReloader() {
248: return reloader;
249: }
250:
251: }
|