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 }