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 }