001/**
002 * Copyright 2005-2017 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 */
016package org.kuali.rice.kew.plugin;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.core.api.config.property.Config;
020import org.kuali.rice.core.api.util.ContextClassLoaderBinder;
021import org.kuali.rice.core.impl.resourceloader.BaseWrappingResourceLoader;
022import org.kuali.rice.kew.api.WorkflowRuntimeException;
023
024import javax.xml.namespace.QName;
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.List;
028import 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 */
037public 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 suppressStartupFailure = 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 (!suppressStartupFailure) {
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 isSuppressStartupFailure() {
154                return suppressStartupFailure;
155        }
156
157        public void setSuppressStartupFailure(boolean suppressStartupFailure) {
158                this.suppressStartupFailure = suppressStartupFailure;
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}