001/**
002 * Copyright 2005-2016 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.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.CoreConstants;
021import org.kuali.rice.core.api.config.CoreConfigHelper;
022import org.kuali.rice.core.api.config.property.Config;
023import org.kuali.rice.core.api.config.property.ConfigContext;
024import org.kuali.rice.core.api.util.ClassLoaderUtils;
025import org.kuali.rice.core.api.util.ContextClassLoaderBinder;
026import org.kuali.rice.core.api.util.xml.XmlException;
027
028import javax.xml.namespace.QName;
029import java.io.File;
030import java.io.FileNotFoundException;
031import java.io.IOException;
032import java.net.MalformedURLException;
033import java.net.URL;
034import 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 */
043public 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}