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    }