/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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 Lesser General Public License for more details.
*
* Copyright 2008 Pentaho Corporation. All rights reserved.
*
* Created Dec 19, 2008
* @author aphillips
*/
package org.pentaho.platform.plugin.services.pluginmgr;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.pentaho.platform.api.engine.IContentGeneratorInfo;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.engine.IPlatformPlugin;
import org.pentaho.platform.api.engine.IPluginOperation;
import org.pentaho.platform.api.engine.IPluginProvider;
import org.pentaho.platform.api.engine.PlatformPluginRegistrationException;
import org.pentaho.platform.api.engine.PluginBeanDefinition;
import org.pentaho.platform.api.engine.PluginServiceDefinition;
import org.pentaho.platform.api.repository.ISolutionRepository;
import org.pentaho.platform.engine.core.solution.ContentGeneratorInfo;
import org.pentaho.platform.engine.core.solution.ContentInfo;
import org.pentaho.platform.engine.core.solution.PluginOperation;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.plugin.services.messages.Messages;
import org.pentaho.platform.util.logging.Logger;
import org.pentaho.platform.util.xml.dom4j.XmlDom4JHelper;
import org.pentaho.ui.xul.IMenuCustomization;
import org.pentaho.ui.xul.XulOverlay;
import org.pentaho.ui.xul.IMenuCustomization.CustomizationType;
import org.pentaho.ui.xul.IMenuCustomization.ItemType;
import org.pentaho.ui.xul.impl.DefaultXulOverlay;
import org.pentaho.ui.xul.util.MenuCustomization;
/**
* An implmentation of {@link IPluginProvider} that searches for plugin.xml files in the Pentaho
* system path and instantiates {@link IPlatformPlugin}s from the information in those files.
* @author aphillips
*/
public class SystemPathXmlPluginProvider implements IPluginProvider {
/**
* Gets the list of plugins that this provider class has discovered.
*
* @return an read-only list of plugins
* @see IPluginProvider#getPlugins()
* @throws PlatformPluginRegistrationException if there is a problem preventing the impl from looking for plugins
*/
public List<IPlatformPlugin> getPlugins(IPentahoSession session) throws PlatformPluginRegistrationException {
List<IPlatformPlugin> plugins = new ArrayList<IPlatformPlugin>();
ISolutionRepository repo = PentahoSystem.get(ISolutionRepository.class, session);
if (repo == null) {
throw new PlatformPluginRegistrationException(Messages.getInstance()
.getErrorString("PluginManager.ERROR_0008_CANNOT_GET_REPOSITORY")); //$NON-NLS-1$
}
// look in each of the system setting folders looking for plugin.xml files
String systemPath = PentahoSystem.getApplicationContext().getSolutionPath("system"); //$NON-NLS-1$
File systemDir = new File(systemPath);
if (!systemDir.exists() || !systemDir.isDirectory()) {
throw new PlatformPluginRegistrationException(Messages.getInstance()
.getErrorString("PluginManager.ERROR_0004_CANNOT_FIND_SYSTEM_FOLDER")); //$NON-NLS-1$
}
File kids[] = systemDir.listFiles();
// look at each child to see if it is a folder
for (File kid : kids) {
if (kid.isDirectory()) {
try {
processDirectory(plugins, kid, repo, session);
} catch (Throwable t) {
//don't throw an exception. we need to continue to process any remaining good plugins
String msg = Messages.getInstance().getErrorString(
"SystemPathXmlPluginProvider.ERROR_0001_FAILED_TO_PROCESS_PLUGIN", kid.getAbsolutePath()); //$NON-NLS-1$
Logger.error(getClass().toString(), msg, t);
PluginMessageLogger.add(msg);
}
}
}
return Collections.unmodifiableList(plugins);
}
protected void processDirectory(List<IPlatformPlugin> plugins, File folder, ISolutionRepository repo,
IPentahoSession session) throws PlatformPluginRegistrationException {
// see if there is a plugin.xml file
FilenameFilter filter = new NameFileFilter("plugin.xml", IOCase.SENSITIVE); //$NON-NLS-1$
File kids[] = folder.listFiles(filter);
if (kids == null || kids.length == 0) {
return;
}
boolean hasLib = false;
filter = new NameFileFilter("lib", IOCase.SENSITIVE); //$NON-NLS-1$
kids = folder.listFiles(filter);
if (kids != null && kids.length > 0) {
hasLib = kids[0].exists() && kids[0].isDirectory();
}
// we have found a plugin.xml file
// get the file from the repository
String path = "system" + ISolutionRepository.SEPARATOR + folder.getName() + ISolutionRepository.SEPARATOR + "plugin.xml"; //$NON-NLS-1$ //$NON-NLS-2$
Document doc = null;
try {
doc = repo.getResourceAsDocument(path, ISolutionRepository.ACTION_EXECUTE);
if (doc != null) {
plugins.add(createPlugin(doc, session, folder.getName(), repo, hasLib));
}
} catch (Exception e) {
throw new PlatformPluginRegistrationException(Messages.getInstance().getErrorString(
"PluginManager.ERROR_0005_CANNOT_PROCESS_PLUGIN_XML", path), e); //$NON-NLS-1$
}
if (doc == null) {
throw new PlatformPluginRegistrationException(Messages.getInstance().getErrorString(
"PluginManager.ERROR_0005_CANNOT_PROCESS_PLUGIN_XML", path)); //$NON-NLS-1$
}
}
protected PlatformPlugin createPlugin(Document doc, IPentahoSession session, String folder, ISolutionRepository repo,
boolean hasLib) {
PlatformPlugin plugin = new PlatformPlugin();
processStaticResourcePaths(plugin, doc, session);
processPluginInfo(plugin, doc, folder, session);
processMenuItems(plugin, doc, session);
processContentTypes(plugin, doc, session);
processContentGenerators(plugin, doc, session, folder, repo, hasLib);
processOverlays(plugin, doc, session);
processLifecycleListeners(plugin, doc);
processBeans(plugin, doc);
processWebservices(plugin, doc);
String listenerCount = (StringUtils.isEmpty(plugin.getLifecycleListenerClassname())) ? "0" : "1"; //$NON-NLS-1$//$NON-NLS-2$
String msg = Messages.getInstance().getString(
"SystemPathXmlPluginProvider.PLUGIN_PROVIDES", //$NON-NLS-1$
Integer.toString(plugin.getMenuCustomizations().size()), Integer.toString(plugin.getContentInfos().size()),
Integer.toString(plugin.getContentGenerators().size()), Integer.toString(plugin.getOverlays().size()),
listenerCount);
PluginMessageLogger.add(msg);
plugin.setSourceDescription(folder);
return plugin;
}
protected void processStaticResourcePaths(PlatformPlugin plugin, Document doc, IPentahoSession session) {
List<?> nodes = doc.selectNodes("//static-path"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
if (node != null) {
String url = node.attributeValue("url"); //$NON-NLS-1$
String localFolder = node.attributeValue("localFolder"); //$NON-NLS-1$
plugin.addStaticResourcePath(url, localFolder);
}
}
}
protected void processLifecycleListeners(PlatformPlugin plugin, Document doc) {
Element node = (Element) doc.selectSingleNode("//lifecycle-listener"); //$NON-NLS-1$
if (node != null) {
String classname = node.attributeValue("class"); //$NON-NLS-1$
plugin.setLifecycleListenerClassname(classname);
}
}
protected void processBeans(PlatformPlugin plugin, Document doc) {
List<?> nodes = doc.selectNodes("//bean"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
if (node != null) {
plugin.addBean(new PluginBeanDefinition(node.attributeValue("id"), node.attributeValue("class"))); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
protected void processWebservices(PlatformPlugin plugin, Document doc) {
List<?> nodes = doc.selectNodes("//webservice"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
PluginServiceDefinition pws = new PluginServiceDefinition();
pws.setId(getProperty(node, "id")); //$NON-NLS-1$
String type = getProperty(node, "type"); //$NON-NLS-1$
if(!StringUtils.isEmpty(type)) {
pws.setTypes(type.split(",")); //$NON-NLS-1$
}
pws.setTitle(getProperty(node, "title")); //$NON-NLS-1$
pws.setDescription(getProperty(node, "description")); //$NON-NLS-1$
//TODO: add support for inline service class definition
pws.setServiceBeanId(getProperty(node, "ref")); //$NON-NLS-1$
pws.setServiceClass(getProperty(node, "class")); //$NON-NLS-1$
Collection<String> extraClasses = new ArrayList<String>();
List<?> extraNodes = node.selectNodes("extra"); //$NON-NLS-1$
for (Object extra : extraNodes) {
Element extraElement = (Element) extra;
String extraClass = getProperty(extraElement, "class"); //$NON-NLS-1$
if (extraClasses != null) {
extraClasses.add(extraClass);
}
}
pws.setExtraClasses(extraClasses);
if (pws.getServiceBeanId() == null && pws.getServiceClass() == null) {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.NO_SERVICE_CLASS_FOUND")); //$NON-NLS-1$
} else {
plugin.addWebservice(pws);
}
}
}
protected void processPluginInfo(PlatformPlugin plugin, Document doc, String folder, IPentahoSession session) {
Element node = (Element) doc.selectSingleNode("/plugin"); //$NON-NLS-1$
//"name" is the attribute that unique identifies a plugin. It acts as the plugin ID. For backwards compatibility,
//if name is not provided, name is set to the value of the "title" attribute
//
if (node != null) {
String name = (node.attributeValue("name") != null)?node.attributeValue("name"):node.attributeValue("title"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(StringUtils.isEmpty(name)) {
String msg = Messages.getInstance().getErrorString("SystemPathXmlPluginProvider.ERROR_0002_PLUGIN_INVALID", folder); //$NON-NLS-1$
PluginMessageLogger.add(msg);
Logger.error(getClass().toString(), msg);
}
plugin.setId(name);
PluginMessageLogger.add(Messages.getInstance().getString("SystemPathXmlPluginProvider.DISCOVERED_PLUGIN", name, folder)); //$NON-NLS-1$
IPlatformPlugin.ClassLoaderType loaderType = IPlatformPlugin.ClassLoaderType.DEFAULT;
String loader = node.attributeValue("loader"); //$NON-NLS-1$
if(!StringUtils.isEmpty(loader)) {
loaderType = IPlatformPlugin.ClassLoaderType.valueOf(loader.toUpperCase());
}
plugin.setLoadertype(loaderType);
}
}
protected void processMenuItems(PlatformPlugin plugin, Document doc, IPentahoSession session) {
// look for menu system customizations
List<?> nodes = doc.selectNodes("//menu-item"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
String id = node.attributeValue("id"); //$NON-NLS-1$
String label = node.attributeValue("label"); //$NON-NLS-1$
try {
// create an IMenuCustomization object
String anchorId = node.attributeValue("anchor"); //$NON-NLS-1$
String command = node.attributeValue("command"); //$NON-NLS-1$
CustomizationType customizationType = CustomizationType.valueOf(node.attributeValue("how")); //$NON-NLS-1$
ItemType itemType = ItemType.valueOf(node.attributeValue("type")); //$NON-NLS-1$
IMenuCustomization custom = new MenuCustomization();
custom.setAnchorId(anchorId);
custom.setId(id);
custom.setCommand(command);
custom.setLabel(label);
custom.setCustomizationType(customizationType);
custom.setItemType(itemType);
// store it
// menuCustomizations.add( custom );
plugin.addMenuCustomization(custom);
if (customizationType == CustomizationType.DELETE) {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_MENU_ITEM_DELETE", id)); //$NON-NLS-1$
} else if (customizationType == CustomizationType.REPLACE) {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_MENU_ITEM_REPLACE", id, label)); //$NON-NLS-1$
} else {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_MENU_ITEM_ADDITION", id, label)); //$NON-NLS-1$
}
} catch (Exception e) {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.ERROR_0009_MENU_CUSTOMIZATION_ERROR", id, label)); //$NON-NLS-1$
Logger.error(getClass().toString(), Messages.getInstance().getErrorString(
"PluginManager.ERROR_0009_MENU_CUSTOMIZATION_ERROR", id, label), e); //$NON-NLS-1$
}
}
}
protected void processOverlays(PlatformPlugin plugin, Document doc, IPentahoSession session) {
// look for content types
List<?> nodes = doc.selectNodes("//overlays/overlay"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
String xml = null;
String id = node.attributeValue("id"); //$NON-NLS-1$
String resourceBundleUri = node.attributeValue("resourcebundle"); //$NON-NLS-1$
if (node.elements() != null && node.elements().size() > 0) {
xml = ((Element) node.elements().get(0)).asXML();
}
if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(xml)) {
XulOverlay overlay = new DefaultXulOverlay(id, null, xml, resourceBundleUri);
plugin.addOverlay(overlay);
}
}
}
protected void processContentTypes(PlatformPlugin plugin, Document doc, IPentahoSession session) {
// look for content types
List<?> nodes = doc.selectNodes("//content-type"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
// create an IMenuCustomization object
String title = XmlDom4JHelper.getNodeText("title", node); //$NON-NLS-1$
String extension = node.attributeValue("type"); //$NON-NLS-1$
if (title != null && extension != null) {
String description = XmlDom4JHelper.getNodeText("description", node, ""); //$NON-NLS-1$ //$NON-NLS-2$
String mimeType = node.attributeValue("mime-type", ""); //$NON-NLS-1$ //$NON-NLS-2$
String iconUrl = XmlDom4JHelper.getNodeText("icon-url", node, ""); //$NON-NLS-1$ //$NON-NLS-2$
String metaProviderClass = XmlDom4JHelper.getNodeText("meta-provider", node, ""); //$NON-NLS-1$ //$NON-NLS-2$
ContentInfo contentInfo = new ContentInfo();
contentInfo.setDescription(description);
contentInfo.setTitle(title);
contentInfo.setExtension(extension);
contentInfo.setMimeType(mimeType);
contentInfo.setIconUrl(iconUrl);
List<?> operationNodes = node.selectNodes("operations/operation"); //$NON-NLS-1$
for (Object operationObj : operationNodes) {
Element operationNode = (Element) operationObj;
String id = XmlDom4JHelper.getNodeText("id", operationNode, ""); //$NON-NLS-1$ //$NON-NLS-2$
String command = XmlDom4JHelper.getNodeText("command", operationNode, ""); //$NON-NLS-1$ //$NON-NLS-2$
if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(command)) {
IPluginOperation operation = new PluginOperation(id, command);
contentInfo.addOperation(operation);
}
}
plugin.addContentInfo(contentInfo);
if (!StringUtils.isEmpty(metaProviderClass)) {
plugin.getMetaProviderMap().put(contentInfo.getExtension(), metaProviderClass);
}
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_CONTENT_TYPE_REGISTERED", extension, title)); //$NON-NLS-1$
} else {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_CONTENT_TYPE_NOT_REGISTERED", extension, title)); //$NON-NLS-1$
}
}
}
/*
* Finds propName as either an attribute of the given node or the
* text element of a child element called propName
*/
private static String getProperty(Element node, String propName) {
String propValue = null;
propValue = node.attributeValue(propName);
if (propValue == null) {
propValue = XmlDom4JHelper.getNodeText(propName, node, null);
}
return propValue;
}
protected void processContentGenerators(PlatformPlugin plugin, Document doc, IPentahoSession session, String folder,
ISolutionRepository repo, boolean hasLib) {
// look for content generators
List<?> nodes = doc.selectNodes("//content-generator"); //$NON-NLS-1$
for (Object obj : nodes) {
Element node = (Element) obj;
// create an IMenuCustomization object
String className = getProperty(node, "class"); //$NON-NLS-1$
if (className == null) {
className = XmlDom4JHelper.getNodeText("classname", node, null); //$NON-NLS-1$
}
String fileInfoClassName = XmlDom4JHelper.getNodeText("fileinfo-classname", node, null); //$NON-NLS-1$
String id = node.attributeValue("id"); //$NON-NLS-1$
String type = node.attributeValue("type"); //$NON-NLS-1$
String url = node.attributeValue("url"); //$NON-NLS-1$
String title = getProperty(node, "title"); //$NON-NLS-1$
String description = getProperty(node, "description"); //$NON-NLS-1$
try {
if (id != null && type != null && className != null && title != null) {
try {
IContentGeneratorInfo info = createContentGenerator(plugin, id, title, description, type, url, className,
session, folder);
plugin.addContentGenerator(info);
} catch (Exception e) {
PluginMessageLogger.add(Messages.getInstance().getString(
"PluginManager.USER_CONTENT_GENERATOR_NOT_REGISTERED", id, folder)); //$NON-NLS-1$
}
if (!StringUtils.isEmpty(fileInfoClassName)) {
plugin.getMetaProviderMap().put(type, fileInfoClassName);
}
} else {
PluginMessageLogger
.add(Messages.getInstance().getString("PluginManager.USER_CONTENT_GENERATOR_NOT_REGISTERED", id, folder)); //$NON-NLS-1$
}
} catch (Exception e) {
PluginMessageLogger.add(Messages.getInstance().getString("PluginManager.USER_CONTENT_GENERATOR_NOT_REGISTERED", id, folder)); //$NON-NLS-1$
Logger.error(getClass().toString(), Messages.getInstance().getErrorString(
"PluginManager.ERROR_0006_CANNOT_CREATE_CONTENT_GENERATOR_FACTORY", folder), e); //$NON-NLS-1$
}
}
}
private static IContentGeneratorInfo createContentGenerator(PlatformPlugin plugin, String id, String title,
String description, String type, String url, String className, IPentahoSession session, String location)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ContentGeneratorInfo info = new ContentGeneratorInfo();
info.setId(id);
info.setTitle(title);
info.setDescription(description);
info.setUrl((url != null) ? url : ""); //$NON-NLS-1$
info.setType(type);
info.setClassname(className);
return info;
}
}