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 }