001/** 002 * Copyright 2005-2017 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}