/*
* PluginLoader.java
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2006 Per Cederberg. All rights reserved.
*/
package org.liquidsite.app.plugin;
import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import org.java.plugin.ObjectFactory;
import org.java.plugin.PluginLifecycleException;
import org.java.plugin.PluginManager;
import org.java.plugin.registry.Extension;
import org.java.plugin.registry.ExtensionPoint;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.util.ExtendedProperties;
import org.liquidsite.app.template.PluginBean;
import org.liquidsite.app.template.TemplateException;
import org.liquidsite.util.log.Log;
/**
* A plugin loader and manager. This class handles the initialization
* and loading of the application plugins.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
public class PluginLoader {
/**
* The class logger.
*/
private static final Log LOG = new Log(PluginLoader.class);
/**
* The core plugin identifier.
*/
private static final String CORE_PLUGIN_ID = "core";
/**
* The core plugin manifest location.
*/
private static final String CORE_PLUGIN_MANIFEST = "core-plugin.xml";
/**
* The plugin manager used.
*/
private static PluginManager manager = null;
/**
* Creates a new plugin manager.
*
* @param dir the plugin work directory
*/
private static void initialize(File dir) {
ExtendedProperties properties = new ExtendedProperties();
ObjectFactory factory;
LOG.info("initializing plugin loader for " + dir);
properties.setProperty("org.java.plugin.PathResolver",
"org.java.plugin.standard.ShadingPathResolver");
properties.setProperty("shadowFolder", dir.getAbsolutePath());
properties.setProperty("unpackMode", "always");
factory = ObjectFactory.newInstance(properties);
manager = factory.createManager();
try {
manager.getPathResolver().configure(properties);
} catch (Exception e) {
LOG.error("plugin configuration error", e);
}
}
/**
* Initializes the plugin loader. All the plugins found in the
* specified directory will automatically be loaded and
* initialized.
*
* @param dir the plugin directory
*
* @throws PluginException if some plugin failed to load or
* initialize correctly
*/
public void startup(File dir) throws PluginException {
Iterator iter;
PluginDescriptor desc;
ExtensionPoint point;
String msg;
// Initialize plugin manager
if (manager == null) {
initialize(new File(dir, "tmp"));
}
try {
manager.publishPlugins(getLocations(dir));
} catch (MalformedURLException e) {
msg = "invalid plugin URL";
LOG.error(msg, e);
throw new PluginException(msg, e);
} catch (Exception e) {
msg = "plugin startup error";
LOG.error(msg + ": " + e.toString());
throw new PluginException(msg, e);
}
// Load extension points
desc = manager.getRegistry().getPluginDescriptor(CORE_PLUGIN_ID);
point = desc.getExtensionPoint("TemplateBean");
iter = point.getConnectedExtensions().iterator();
while (iter.hasNext()) {
loadTemplateBean((Extension) iter.next());
}
}
/**
* Shuts down the plugin loader and deinitializes all loaded
* plugins.
*/
public void shutdown() {
PluginBean.removeAll();
try {
manager.shutdown();
} catch (Exception e) {
LOG.error("plugin shutdown error", e);
}
manager = null;
}
/**
* Finds all the plugin locations from the specified directory.
* The core plugin location will be added to the array returned.
*
* @param dir the plugin directory
*
* @return the array of plugin locations
*
* @throws MalformedURLException if some plugin URL couldn't be
* created
*/
private PluginManager.PluginLocation[] getLocations(File dir)
throws MalformedURLException {
PluginManager.PluginLocation[] locations;
File[] files;
files = dir.listFiles(new PluginFilter());
locations = new PluginManager.PluginLocation[1 + files.length];
locations[0] = getCoreLocation();
for (int i = 0; i < files.length; i++) {
LOG.info("found plugin " + files[i].getName());
locations[i + 1] = new StandardZipPluginLocation(files[i]);
}
return locations;
}
/**
* Returns the location of the core plugin. The core plugin is used to
* define the extension points available for normal plugins.
*
* @return the location of the core plugin
*/
private StandardPluginLocation getCoreLocation() {
URL url;
url = getClass().getResource(CORE_PLUGIN_MANIFEST);
return new StandardPluginLocation(url, url);
}
/**
* Loads a template bean extension.
*
* @param ext the template bean extension
*
* @throws PluginException if the extension couldn't be loaded
*/
private void loadTemplateBean(Extension ext) throws PluginException {
PluginDescriptor desc;
String name;
String className;
Class cls;
String msg;
desc = ext.getDeclaringPluginDescriptor();
name = ext.getParameter("name").valueAsString();
className = ext.getParameter("class").valueAsString();
LOG.info("loading plugin template bean '" + name + "' as " +
className + " from plugin " + desc.getId() +
", version " + desc.getVersion());
if (!manager.isPluginActivated(desc)) {
try {
manager.activatePlugin(desc.getId());
} catch (PluginLifecycleException e) {
msg = "failed to activate plugin " + desc.getId();
LOG.error(msg, e);
throw new PluginException(msg, e);
}
}
try {
cls = manager.getPluginClassLoader(desc).loadClass(className);
} catch (ClassNotFoundException e) {
msg = "failed to load class '" + className + "' in plugin " +
desc.getId();
LOG.error(msg, e);
throw new PluginException(msg, e);
}
try {
PluginBean.add(name, cls);
} catch (TemplateException e) {
msg = "failed to plugin mapping '" + name + "' in plugin " +
desc.getId();
LOG.error(msg, e);
throw new PluginException(msg, e);
}
}
/**
* A simple plugin filename filter. This filter accepts all zip
* files.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
class PluginFilter implements FilenameFilter {
/**
* Checks if a file is accepted by the filter.
*
* @param dir the file directory
* @param name the file name
*
* @return true if the file is accepted, or
* false otherwise
*/
public boolean accept(File dir, String name) {
return name.endsWith(".jar") || name.endsWith(".zip");
}
}
}