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 }