001    /*
002     * Copyright 2005-2008 The Kuali Foundation
003     *
004     *
005     * Licensed under the Educational Community License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     * http://www.opensource.org/licenses/ecl2.php
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.kuali.rice.kew.plugin;
018    
019    import java.util.ArrayList;
020    import java.util.Iterator;
021    import java.util.List;
022    
023    import javax.xml.namespace.QName;
024    
025    import org.apache.log4j.Logger;
026    import org.kuali.rice.core.config.Config;
027    import org.kuali.rice.core.resourceloader.BaseWrappingResourceLoader;
028    import org.kuali.rice.core.resourceloader.ContextClassLoaderBinder;
029    import org.kuali.rice.kew.exception.WorkflowRuntimeException;
030    
031    
032    /**
033     * A KEW Plugin.  A Plugin represents a distinct classloading space living below (as a child) of the core
034     * KEW classloader.  It allows for loading of plugin resources from core components of the system.
035     * Essentially a Plugin is a specialized ResourceLoader with a custom classloader and attached configuration.
036     *
037     * @author Kuali Rice Team (rice.collab@kuali.org)
038     */
039    public class Plugin extends BaseWrappingResourceLoader {
040    
041            private static final Logger LOG = Logger.getLogger(Plugin.class);
042        private Config config;
043        private List<PluginListener> pluginListeners = new ArrayList<PluginListener>();
044    
045        private boolean supressStartupFailure = true;
046        private boolean started = false;
047        private boolean startupFailure = false;
048    
049        public Plugin(QName name, Config config, ClassLoader classLoader) {
050            super(name, classLoader);
051            this.config = config;
052        }
053    
054        /**
055         * Starts the plugin.
056         */
057        public synchronized void start() {
058            if (started) {
059                LOG.info(getLogPrefix()+" has already been started.");
060                return;
061            }
062            LOG.info(getLogPrefix()+" Starting...");
063            try {
064                bindThread();
065                try {
066                    startupFailure = false;
067                    started = true;
068                    super.start();
069                    LOG.info("Starting plugin listeners");
070                    startPluginListeners();
071                } finally {
072                    unbindThread();
073                }
074                ClassLoader classLoader = getClassLoader();
075                LOG.info(getLogPrefix()+" ...started." + (classLoader != null ? classLoader.toString() : ""));
076            } catch (Throwable t) {
077                LOG.error(getLogPrefix()+" Failure starting plugin.", t);
078                startupFailure = true;
079                started = true;
080                stop();
081                if (!supressStartupFailure) {
082                    if (t instanceof Error) {
083                            throw (Error)t;
084                    } else if (t instanceof RuntimeException) {
085                            throw (RuntimeException)t;
086                    }
087                    throw new WorkflowRuntimeException("Failed to startup plugin.", t);
088                }
089            }
090        }
091    
092        /**
093         * Stops the plugin.
094         */
095        public synchronized void stop() {
096            if (!started) {
097                LOG.info(getLogPrefix()+" has already been stopped.");
098                return;
099            }
100            LOG.info(getLogPrefix()+" Stopping...");
101            bindThread();
102            try {
103                started = false;
104                stopPluginListeners();
105                // stop resource loaders of super class
106                super.stop();
107            } catch (Throwable t) {
108                    LOG.error(getLogPrefix()+" Failed when attempting to stop the plugin.", t);
109            } finally {
110                unbindThread();
111            }
112            resetPlugin();
113            LOG.info(getLogPrefix()+" ...stopped.");
114        }
115    
116        public boolean isStarted() {
117            return started;
118        }
119    
120        public void addPluginListener(PluginListener pluginListener) {
121            pluginListeners.add(pluginListener);
122        }
123    
124        public void removePluginListener(PluginListener pluginListener) {
125            pluginListeners.remove(pluginListener);
126        }
127    
128        protected void startPluginListeners() {
129            for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) {
130                PluginListener listener = (PluginListener) iterator.next();
131                listener.pluginInitialized(this);
132            }
133        }
134    
135        /**
136         * If we fail to stop a plugin listener, try the next one but don't propogate any
137         * exceptions out of this method.  Otherwise the plugin ends up dying and can't be
138         * reloaded from a hot deploy.
139         */
140        protected void stopPluginListeners() {
141            for (Iterator iterator = pluginListeners.iterator(); iterator.hasNext();) {
142                PluginListener listener = (PluginListener) iterator.next();
143                try {
144                    listener.pluginDestroyed(this);
145                } catch (Throwable t) {
146                    LOG.error(getLogPrefix()+" Failed when invoking pluginDestroyed on Plugin Listener '"+listener.getClass().getName()+"'.", t);
147                }
148            }
149        }
150    
151        public boolean isSupressStartupFailure() {
152                    return supressStartupFailure;
153            }
154    
155            public void setSupressStartupFailure(boolean supressStartupFailure) {
156                    this.supressStartupFailure = supressStartupFailure;
157            }
158    
159            public void bindThread() {
160                    ContextClassLoaderBinder.bind(getClassLoader());
161        }
162    
163        public void unbindThread() {
164            ContextClassLoaderBinder.unbind();
165        }
166    
167            /**
168         * Cleanup plugin resources.
169         */
170        private void resetPlugin() {
171            if (!startupFailure) {
172                    setClassLoader(null);
173            }
174            pluginListeners.clear();
175        }
176    
177        private String getLogPrefix() {
178            return toString();
179        }
180    
181        public Config getConfig() {
182            return config;
183        }
184    
185        public String toString() {
186            return "[Plugin: " + this.getName() + "]";
187        }
188    
189    }