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 }