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.commons.lang.StringUtils;
019    import org.apache.log4j.Logger;
020    import org.kuali.rice.core.api.CoreConstants;
021    import org.kuali.rice.core.api.config.CoreConfigHelper;
022    import org.kuali.rice.core.api.config.property.Config;
023    import org.kuali.rice.core.api.config.property.ConfigContext;
024    import org.kuali.rice.core.api.util.ClassLoaderUtils;
025    import org.kuali.rice.core.api.util.ContextClassLoaderBinder;
026    import org.kuali.rice.core.api.util.xml.XmlException;
027    
028    import javax.xml.namespace.QName;
029    import java.io.File;
030    import java.io.FileNotFoundException;
031    import java.io.IOException;
032    import java.net.MalformedURLException;
033    import java.net.URL;
034    import java.util.concurrent.Callable;
035    
036    /**
037     * Abstract base PluginLoader implementation.
038     * Delegates to template methods to obtain plugin ClassLoader and plugin config file URL,
039     * then load the config under the plugin ClassLoader, and constructs a Plugin object.
040     *
041     * @author Kuali Rice Team (rice.collab@kuali.org)
042     */
043    public abstract class BasePluginLoader implements PluginLoader {
044        private static final Logger LOG = Logger.getLogger(BasePluginLoader.class);
045    
046        private static final String META_INF_PATH = "META-INF";
047        private static final String PLUGIN_CONFIG_PATH = META_INF_PATH + "/workflow.xml";
048    
049        protected final String simplePluginName;
050        protected String logPrefix;
051    
052        protected final ClassLoader parentClassLoader;
053        protected final Config parentConfig;
054        protected final File sharedPluginDirectory;
055        protected String pluginConfigPath = PLUGIN_CONFIG_PATH;
056    
057        public BasePluginLoader(String simplePluginName, File sharedPluginDirectory, ClassLoader parentClassLoader, Config parentConfig) {
058            this.sharedPluginDirectory = sharedPluginDirectory;
059            if (parentClassLoader == null) {
060                parentClassLoader = ClassLoaderUtils.getDefaultClassLoader();
061            }
062            this.parentClassLoader = parentClassLoader;
063            this.parentConfig = parentConfig;
064            this.simplePluginName = simplePluginName;
065            this.logPrefix = simplePluginName;
066        }
067    
068        protected String getLogPrefix() {
069            return logPrefix;
070        }
071        
072        public String getPluginName() {
073            return simplePluginName;
074        }
075    
076        public void setPluginConfigPath(String pluginConfigPath) {
077            this.pluginConfigPath = pluginConfigPath;
078        }
079    
080        protected String getSimplePluginName() {
081            return simplePluginName;
082        }
083    
084        /**
085         * Template method that subclasses should implement to supply an appropriate
086         * plugin ClassLoader
087         * @return an appropriate PluginClassLoader
088         * @throws IOException if anything goes awry
089         */
090        protected abstract PluginClassLoader createPluginClassLoader() throws IOException;
091        /**
092         * Template method that subclasses should implement to supply an appropriate
093         * URL to the plugin's configuration
094         * @return an appropriate URL to the plugin's configuration
095         * @throws IOException if anything goes awry
096         */
097        protected abstract URL getPluginConfigURL() throws PluginException, IOException;
098    
099        /**
100         * Loads and creates the Plugin.
101         */
102        public Plugin load() throws Exception {
103            final PluginClassLoader classLoader = createPluginClassLoader();
104            LOG.info("Created plugin ClassLoader: " + classLoader);
105            return ContextClassLoaderBinder.doInContextClassLoader(classLoader, new Callable<Plugin>() {
106                public Plugin call() throws IOException {
107                    return loadWithinContextClassLoader(classLoader);
108                }
109            });
110        }
111    
112        public boolean isRemoved() {
113            return false;
114        }
115    
116        /**
117         * Executes loading of the plugin within the current context classloader set to the Plugin's classloader.
118         */
119        protected Plugin loadWithinContextClassLoader(PluginClassLoader classLoader) throws PluginException, IOException {
120            URL url = getPluginConfigURL();
121            PluginConfig pluginConfig = loadPluginConfig(url);
122            QName qPluginName = getPluginName(pluginConfig);
123            classLoader.setConfig(pluginConfig);
124            ConfigContext.init(classLoader, pluginConfig);
125            configureExtraClasspath(classLoader, pluginConfig);
126            this.logPrefix = PluginUtils.getLogPrefix(qPluginName).toString();
127            LOG.info("Constructing plugin '" + simplePluginName + "' with classloader: " + classLoader);
128            Plugin plugin = new Plugin(qPluginName, pluginConfig, classLoader);
129            installResourceLoader(plugin);
130            installPluginListeners(plugin);
131            return plugin;
132        }
133    
134        protected void installResourceLoader(Plugin plugin) {
135            PluginUtils.installResourceLoader(plugin);
136        }
137    
138        protected void installPluginListeners(Plugin plugin) {
139            PluginUtils.installPluginListeners(plugin);
140        }
141    
142        protected void configureExtraClasspath(PluginClassLoader classLoader, PluginConfig config) throws MalformedURLException {
143                    String extraClassesDirs = config.getProperty(Config.EXTRA_CLASSES_DIR);
144                    if (!org.apache.commons.lang.StringUtils.isEmpty(extraClassesDirs)) {
145                            String[] extraClasses = extraClassesDirs.split(",");
146                            for (int index = 0; index < extraClasses.length; index++) {
147                                    File extraClassesDir = new File(extraClasses[index]);
148                                    if (extraClassesDir.exists()) {
149                                            classLoader.addClassesDirectory(extraClassesDir);
150                                    }
151                            }
152                    }
153                    String extraLibDirs = config.getProperty(Config.EXTRA_LIB_DIR);
154                    if (!org.apache.commons.lang.StringUtils.isEmpty(extraLibDirs)) {
155                            String[] extraLibs = extraLibDirs.split(",");
156                            for (int index = 0; index < extraLibs.length; index++) {
157                                    File extraLibDir = new File(extraLibs[index]);
158                                    if (extraLibDir.exists()) {
159                                            classLoader.addLibDirectory(extraLibDir);
160                                    }
161                            }
162                    }
163            }
164    
165    
166        protected QName getPluginName(PluginConfig pluginConfig) {
167            String applicationId = pluginConfig.getProperty(CoreConstants.Config.APPLICATION_ID);
168            QName qPluginName = null;
169            if (StringUtils.isBlank(applicationId)) {
170                    qPluginName = new QName(CoreConfigHelper.getApplicationId(), simplePluginName);
171            } else {
172                    qPluginName = new QName(applicationId, simplePluginName);
173            }
174            return qPluginName;
175        }
176    
177        protected PluginConfig loadPluginConfig(URL url) {
178            PluginConfigParser parser = new PluginConfigParser();
179            try {
180                PluginConfig pluginConfig  = parser.parse(url, parentConfig);
181                pluginConfig.parseConfig();
182                return pluginConfig;
183            } catch (FileNotFoundException e) {
184                throw new PluginException(getLogPrefix() + " Could not locate the plugin config file at path " + url, e);
185            } catch (IOException ioe) {
186                throw new PluginException(getLogPrefix() + " Could not read the plugin config file", ioe);
187            } catch (XmlException ixe) {
188                throw new PluginException(getLogPrefix() + " Could not parse the plugin config file", ixe);
189            }
190        }
191    }