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.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 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 bindThread(); 063 try { 064 startupFailure = false; 065 started = true; 066 super.start(); 067 LOG.info("Starting plugin listeners"); 068 startPluginListeners(); 069 } finally { 070 unbindThread(); 071 } 072 ClassLoader classLoader = getClassLoader(); 073 LOG.info(getLogPrefix()+" ...started." + (classLoader != null ? classLoader.toString() : "")); 074 } catch (Throwable t) { 075 LOG.error(getLogPrefix()+" Failure starting plugin.", t); 076 startupFailure = true; 077 started = true; 078 stop(); 079 if (!supressStartupFailure) { 080 if (t instanceof Error) { 081 throw (Error)t; 082 } else if (t instanceof RuntimeException) { 083 throw (RuntimeException)t; 084 } 085 throw new WorkflowRuntimeException("Failed to startup plugin.", t); 086 } 087 } 088 } 089 090 /** 091 * Stops the plugin. 092 */ 093 public synchronized void stop() { 094 if (!started) { 095 LOG.info(getLogPrefix()+" has already been stopped."); 096 return; 097 } 098 LOG.info(getLogPrefix()+" Stopping..."); 099 bindThread(); 100 try { 101 started = false; 102 stopPluginListeners(); 103 // stop resource loaders of super class 104 super.stop(); 105 } catch (Throwable t) { 106 LOG.error(getLogPrefix()+" Failed when attempting to stop the plugin.", t); 107 } finally { 108 unbindThread(); 109 } 110 resetPlugin(); 111 LOG.info(getLogPrefix()+" ...stopped."); 112 } 113 114 public boolean isStarted() { 115 return started; 116 } 117 118 public void addPluginListener(PluginListener pluginListener) { 119 pluginListeners.add(pluginListener); 120 } 121 122 public void removePluginListener(PluginListener pluginListener) { 123 pluginListeners.remove(pluginListener); 124 } 125 126 protected void startPluginListeners() { 127 for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) { 128 PluginListener listener = (PluginListener) iterator.next(); 129 listener.pluginInitialized(this); 130 } 131 } 132 133 /** 134 * If we fail to stop a plugin listener, try the next one but don't propogate any 135 * exceptions out of this method. Otherwise the plugin ends up dying and can't be 136 * reloaded from a hot deploy. 137 */ 138 protected void stopPluginListeners() { 139 for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) { 140 PluginListener listener = (PluginListener) iterator.next(); 141 try { 142 listener.pluginDestroyed(this); 143 } catch (Throwable t) { 144 LOG.error(getLogPrefix()+" Failed when invoking pluginDestroyed on Plugin Listener '"+listener.getClass().getName()+"'.", t); 145 } 146 } 147 } 148 149 public boolean isSupressStartupFailure() { 150 return supressStartupFailure; 151 } 152 153 public void setSupressStartupFailure(boolean supressStartupFailure) { 154 this.supressStartupFailure = supressStartupFailure; 155 } 156 157 public void bindThread() { 158 ContextClassLoaderBinder.bind(getClassLoader()); 159 } 160 161 public void unbindThread() { 162 ContextClassLoaderBinder.unbind(); 163 } 164 165 /** 166 * Cleanup plugin resources. 167 */ 168 private void resetPlugin() { 169 if (!startupFailure) { 170 setClassLoader(null); 171 } 172 pluginListeners.clear(); 173 } 174 175 private String getLogPrefix() { 176 return toString(); 177 } 178 179 public Config getConfig() { 180 return config; 181 } 182 183 public String toString() { 184 return "[Plugin: " + this.getName() + "]"; 185 } 186 187 }