View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.plugin;
17  
18  import org.kuali.rice.core.api.config.CoreConfigHelper;
19  import org.kuali.rice.core.api.config.property.Config;
20  import org.kuali.rice.core.api.config.property.ConfigContext;
21  import org.kuali.rice.core.api.resourceloader.ResourceLoader;
22  import org.kuali.rice.core.api.util.ClassLoaderUtils;
23  import org.kuali.rice.kew.plugin.PluginUtils.PluginZipFileFilter;
24  
25  import javax.xml.namespace.QName;
26  import java.io.File;
27  import java.util.ArrayList;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.TreeMap;
33  import java.util.concurrent.Executors;
34  import java.util.concurrent.ScheduledExecutorService;
35  import java.util.concurrent.ScheduledFuture;
36  import java.util.concurrent.ThreadFactory;
37  import java.util.concurrent.TimeUnit;
38  
39  /**
40   * A PluginRegistry implementation which loads plugins from the file system on the server.
41   *
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  public class ServerPluginRegistry extends BasePluginRegistry {
45  
46  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ServerPluginRegistry.class);
47  
48  	private List<String> pluginDirectories = new ArrayList<String>();
49  	private File sharedPluginDirectory;
50  	private Reloader reloader;
51  	private HotDeployer hotDeployer;
52  
53  	private ScheduledExecutorService scheduledExecutor;
54  	private ScheduledFuture<?> reloaderFuture;
55  	private ScheduledFuture<?> hotDeployerFuture;
56  
57  
58  	public ServerPluginRegistry() {
59  		super(new QName(CoreConfigHelper.getApplicationId(), ResourceLoader.PLUGIN_REGISTRY_LOADER_NAME));
60  	}
61  
62  	public void start() throws Exception {
63          if (!isStarted()) {
64  		    LOG.info("Starting server Plugin Registry...");
65  		    scheduledExecutor = Executors.newScheduledThreadPool(2, new KEWThreadFactory());
66  		    sharedPluginDirectory = loadSharedPlugin();
67  		    reloader = new Reloader();
68  		    hotDeployer = new HotDeployer(this, sharedPluginDirectory, pluginDirectories);
69  		    loadPlugins(sharedPluginDirectory);
70  		    this.reloaderFuture = scheduledExecutor.scheduleWithFixedDelay(reloader, 5, 5, TimeUnit.SECONDS);
71  		    this.hotDeployerFuture = scheduledExecutor.scheduleWithFixedDelay(hotDeployer, 5, 5, TimeUnit.SECONDS);
72  		    super.start();
73  		    LOG.info("...server Plugin Registry successfully started.");
74          }
75  	}
76  
77  	public void stop() throws Exception {
78          if (isStarted()) {
79  		    LOG.info("Stopping server Plugin Registry...");
80  		    stopReloader();
81  		    stopHotDeployer();
82  		    reloader = null;
83  		    hotDeployer = null;
84  
85  		    if (scheduledExecutor != null) {
86  			    scheduledExecutor.shutdownNow();
87  			    scheduledExecutor = null;
88  		    }
89  		    super.stop();
90  		    LOG.info("...server Plugin Registry successfully stopped.");
91          }
92  	}
93  
94  	protected void stopReloader() {
95  		if (reloaderFuture != null) {
96  			if (!reloaderFuture.cancel(true)) {
97  				LOG.warn("Failed to cancel the plugin reloader.");
98  			}
99  			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 }