blob: 309c2a4d21ee59e280eedf2eb663e9e1a8614224 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nutch.plugin;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.apache.hadoop.conf.Configuration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* The <code>PluginManifestParser</code> parser just parse the manifest file in
* all plugin directories.
*
* @author joa23
*/
public class PluginManifestParser {
private static final String ATTR_NAME = "name";
private static final String ATTR_CLASS = "class";
private static final String ATTR_ID = "id";
public static final Logger LOG = PluginRepository.LOG;
private static final boolean WINDOWS = System.getProperty("os.name")
.startsWith("Windows");
private Configuration conf;
private PluginRepository pluginRepository;
public PluginManifestParser(Configuration conf,
PluginRepository pluginRepository) {
this.conf = conf;
this.pluginRepository = pluginRepository;
}
/**
* Returns a list of all found plugin descriptors.
*
* @param pluginFolders
* folders to search plugins from
* @return A {@link Map} of all found {@link PluginDescriptor}s.
*/
public Map<String, PluginDescriptor> parsePluginFolder(String[] pluginFolders) {
Map<String, PluginDescriptor> map = new HashMap<>();
if (pluginFolders == null) {
throw new IllegalArgumentException("plugin.folders is not defined");
}
for (String name : pluginFolders) {
File directory = getPluginFolder(name);
if (directory == null) {
continue;
}
LOG.info("Plugins: looking in: " + directory.getAbsolutePath());
for (File oneSubFolder : directory.listFiles()) {
if (oneSubFolder.isDirectory()) {
String manifestPath = oneSubFolder.getAbsolutePath() + File.separator
+ "plugin.xml";
try {
LOG.debug("parsing: " + manifestPath);
PluginDescriptor p = parseManifestFile(manifestPath);
map.put(p.getPluginId(), p);
} catch (Exception e) {
LOG.warn("Error while loading plugin `" + manifestPath + "` "
+ e.toString());
}
}
}
}
return map;
}
/**
* Return the named plugin folder. If the name is absolute then it is
* returned. Otherwise, for relative names, the classpath is scanned.
*/
public File getPluginFolder(String name) {
File directory = new File(name);
if (!directory.isAbsolute()) {
URL url = PluginManifestParser.class.getClassLoader().getResource(name);
if (url == null && directory.exists() && directory.isDirectory()
&& directory.listFiles().length > 0) {
return directory; // relative path that is not in the classpath
} else if (url == null) {
LOG.warn("Plugins: directory not found: " + name);
return null;
} else if (!"file".equals(url.getProtocol())) {
LOG.warn("Plugins: not a file: url. Can't load plugins from: " + url);
return null;
}
String path = url.getPath();
if (WINDOWS && path.startsWith("/")) // patch a windows bug
path = path.substring(1);
try {
path = URLDecoder.decode(path, "UTF-8"); // decode the url path
} catch (UnsupportedEncodingException e) {
}
directory = new File(path);
} else if (!directory.exists()) {
LOG.warn("Plugins: directory not found: " + name);
return null;
}
return directory;
}
/**
* @param manifestPath
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws MalformedURLException
*/
private PluginDescriptor parseManifestFile(String pManifestPath)
throws MalformedURLException, SAXException, IOException,
ParserConfigurationException {
Document document = parseXML(new File(pManifestPath).toURI().toURL());
String pPath = new File(pManifestPath).getParent();
return parsePlugin(document, pPath);
}
/**
* @param url
* @return Document
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
* @throws DocumentException
*/
private Document parseXML(URL url) throws SAXException, IOException,
ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(url.openStream());
}
/**
* @param pDocument
* @throws MalformedURLException
*/
private PluginDescriptor parsePlugin(Document pDocument, String pPath)
throws MalformedURLException {
Element rootElement = pDocument.getDocumentElement();
String id = rootElement.getAttribute(ATTR_ID);
String name = rootElement.getAttribute(ATTR_NAME);
String version = rootElement.getAttribute("version");
String providerName = rootElement.getAttribute("provider-name");
String pluginClazz = null;
if (rootElement.getAttribute(ATTR_CLASS).trim().length() > 0) {
pluginClazz = rootElement.getAttribute(ATTR_CLASS);
}
PluginDescriptor pluginDescriptor = new PluginDescriptor(id, version, name,
providerName, pluginClazz, pPath, this.conf);
LOG.debug("plugin: id=" + id + " name=" + name + " version=" + version
+ " provider=" + providerName + "class=" + pluginClazz);
parseExtension(rootElement, pluginDescriptor);
parseExtensionPoints(rootElement, pluginDescriptor);
parseLibraries(rootElement, pluginDescriptor);
parseRequires(rootElement, pluginDescriptor);
return pluginDescriptor;
}
/**
* @param pRootElement
* @param pDescriptor
* @throws MalformedURLException
*/
private void parseRequires(Element pRootElement, PluginDescriptor pDescriptor)
throws MalformedURLException {
NodeList nodelist = pRootElement.getElementsByTagName("requires");
if (nodelist.getLength() > 0) {
Element requires = (Element) nodelist.item(0);
NodeList imports = requires.getElementsByTagName("import");
for (int i = 0; i < imports.getLength(); i++) {
Element anImport = (Element) imports.item(i);
String plugin = anImport.getAttribute("plugin");
if (plugin != null) {
pDescriptor.addDependency(plugin);
}
}
}
}
/**
* @param pRootElement
* @param pDescriptor
* @throws MalformedURLException
*/
private void parseLibraries(Element pRootElement, PluginDescriptor pDescriptor)
throws MalformedURLException {
NodeList nodelist = pRootElement.getElementsByTagName("runtime");
if (nodelist.getLength() > 0) {
Element runtime = (Element) nodelist.item(0);
NodeList libraries = runtime.getElementsByTagName("library");
for (int i = 0; i < libraries.getLength(); i++) {
Element library = (Element) libraries.item(i);
String libName = library.getAttribute(ATTR_NAME);
NodeList list = library.getElementsByTagName("export");
Element exportElement = (Element) list.item(0);
if (exportElement != null)
pDescriptor.addExportedLibRelative(libName);
else
pDescriptor.addNotExportedLibRelative(libName);
}
}
}
/**
* @param rootElement
* @param pluginDescriptor
*/
private void parseExtensionPoints(Element pRootElement,
PluginDescriptor pPluginDescriptor) {
NodeList list = pRootElement.getElementsByTagName("extension-point");
if (list != null) {
for (int i = 0; i < list.getLength(); i++) {
Element oneExtensionPoint = (Element) list.item(i);
String id = oneExtensionPoint.getAttribute(ATTR_ID);
String name = oneExtensionPoint.getAttribute(ATTR_NAME);
String schema = oneExtensionPoint.getAttribute("schema");
ExtensionPoint extensionPoint = new ExtensionPoint(id, name, schema);
pPluginDescriptor.addExtensionPoint(extensionPoint);
}
}
}
/**
* @param rootElement
* @param pluginDescriptor
*/
private void parseExtension(Element pRootElement,
PluginDescriptor pPluginDescriptor) {
NodeList extensions = pRootElement.getElementsByTagName("extension");
if (extensions != null) {
for (int i = 0; i < extensions.getLength(); i++) {
Element oneExtension = (Element) extensions.item(i);
String pointId = oneExtension.getAttribute("point");
NodeList extensionImplementations = oneExtension.getChildNodes();
if (extensionImplementations != null) {
for (int j = 0; j < extensionImplementations.getLength(); j++) {
Node node = extensionImplementations.item(j);
if (!node.getNodeName().equals("implementation")) {
continue;
}
Element oneImplementation = (Element) node;
String id = oneImplementation.getAttribute(ATTR_ID);
String extensionClass = oneImplementation.getAttribute(ATTR_CLASS);
LOG.debug("impl: point=" + pointId + " class=" + extensionClass);
Extension extension = new Extension(pPluginDescriptor, pointId, id,
extensionClass, this.conf, this.pluginRepository);
NodeList parameters = oneImplementation
.getElementsByTagName("parameter");
if (parameters != null) {
for (int k = 0; k < parameters.getLength(); k++) {
Element param = (Element) parameters.item(k);
extension.addAttribute(param.getAttribute(ATTR_NAME),
param.getAttribute("value"));
}
}
pPluginDescriptor.addExtension(extension);
}
}
}
}
}
}