001 /** 002 * Copyright 2005-2013 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.util.ContextClassLoaderBinder; 021 import org.kuali.rice.core.impl.resourceloader.BaseWrappingResourceLoader; 022 import org.kuali.rice.kew.api.WorkflowRuntimeException; 023 024 import javax.xml.namespace.QName; 025 import java.util.ArrayList; 026 import java.util.Iterator; 027 import java.util.List; 028 import java.util.concurrent.Callable; 029 030 /** 031 * A KEW Plugin. A Plugin represents a distinct classloading space living below (as a child) of the core 032 * KEW classloader. It allows for loading of plugin resources from core components of the system. 033 * Essentially a Plugin is a specialized ResourceLoader with a custom classloader and attached configuration. 034 * 035 * @author Kuali Rice Team (rice.collab@kuali.org) 036 */ 037 public class Plugin extends BaseWrappingResourceLoader { 038 039 private static final Logger LOG = Logger.getLogger(Plugin.class); 040 private Config config; 041 private List<PluginListener> pluginListeners = new ArrayList<PluginListener>(); 042 043 private boolean supressStartupFailure = true; 044 private boolean started = false; 045 private boolean startupFailure = false; 046 047 public Plugin(QName name, Config config, ClassLoader classLoader) { 048 super(name, classLoader); 049 this.config = config; 050 } 051 052 /** 053 * Starts the plugin. 054 */ 055 public synchronized void start() { 056 if (started) { 057 LOG.info(getLogPrefix()+" has already been started."); 058 return; 059 } 060 LOG.info(getLogPrefix()+" Starting..."); 061 try { 062 ContextClassLoaderBinder.doInContextClassLoader(getClassLoader(), new Callable() { 063 @Override 064 public Object call() throws Exception { 065 startupFailure = false; 066 started = true; 067 Plugin.super.start(); 068 LOG.info("Starting plugin listeners"); 069 startPluginListeners(); 070 return null; 071 } 072 }); 073 ClassLoader classLoader = getClassLoader(); 074 LOG.info(getLogPrefix()+" ...started." + (classLoader != null ? classLoader.toString() : "")); 075 } catch (Throwable t) { 076 LOG.error(getLogPrefix()+" Failure starting plugin.", t); 077 startupFailure = true; 078 started = true; 079 stop(); 080 if (!supressStartupFailure) { 081 if (t instanceof Error) { 082 throw (Error)t; 083 } else if (t instanceof RuntimeException) { 084 throw (RuntimeException)t; 085 } 086 throw new WorkflowRuntimeException("Failed to startup plugin.", t); 087 } 088 } 089 } 090 091 /** 092 * Stops the plugin. 093 */ 094 public synchronized void stop() { 095 if (!started) { 096 LOG.info(getLogPrefix()+" has already been stopped."); 097 return; 098 } 099 LOG.info(getLogPrefix()+" Stopping..."); 100 try { 101 ContextClassLoaderBinder.doInContextClassLoader(getClassLoader(), new Callable() { 102 @Override 103 public Object call() throws Exception { 104 started = false; 105 stopPluginListeners(); 106 // stop resource loaders of super class 107 Plugin.super.stop(); 108 return null; 109 } 110 }); 111 } catch (Throwable t) { 112 LOG.error(getLogPrefix()+" Failed when attempting to stop the plugin.", t); 113 } 114 resetPlugin(); 115 LOG.info(getLogPrefix()+" ...stopped."); 116 } 117 118 public boolean isStarted() { 119 return started; 120 } 121 122 public void addPluginListener(PluginListener pluginListener) { 123 pluginListeners.add(pluginListener); 124 } 125 126 public void removePluginListener(PluginListener pluginListener) { 127 pluginListeners.remove(pluginListener); 128 } 129 130 protected void startPluginListeners() { 131 for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) { 132 PluginListener listener = (PluginListener) iterator.next(); 133 listener.pluginInitialized(this); 134 } 135 } 136 137 /** 138 * If we fail to stop a plugin listener, try the next one but don't propogate any 139 * exceptions out of this method. Otherwise the plugin ends up dying and can't be 140 * reloaded from a hot deploy. 141 */ 142 protected void stopPluginListeners() { 143 for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) { 144 PluginListener listener = (PluginListener) iterator.next(); 145 try { 146 listener.pluginDestroyed(this); 147 } catch (Throwable t) { 148 LOG.error(getLogPrefix()+" Failed when invoking pluginDestroyed on Plugin Listener '"+listener.getClass().getName()+"'.", t); 149 } 150 } 151 } 152 153 public boolean isSupressStartupFailure() { 154 return supressStartupFailure; 155 } 156 157 public void setSupressStartupFailure(boolean supressStartupFailure) { 158 this.supressStartupFailure = supressStartupFailure; 159 } 160 161 /** 162 * Cleanup plugin resources. 163 */ 164 private void resetPlugin() { 165 if (!startupFailure) { 166 setClassLoader(null); 167 } 168 pluginListeners.clear(); 169 } 170 171 private String getLogPrefix() { 172 return toString(); 173 } 174 175 public Config getConfig() { 176 return config; 177 } 178 179 public String toString() { 180 return "[Plugin: " + this.getName() + "]"; 181 } 182 183 }