/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.plugin.internal.descriptorparser; import com.google.common.base.Preconditions; import com.qcadoo.plugin.api.Module; import com.qcadoo.plugin.api.ModuleFactory; import com.qcadoo.plugin.api.PluginState; import com.qcadoo.plugin.internal.DefaultPlugin.Builder; import com.qcadoo.plugin.internal.PluginException; import com.qcadoo.plugin.internal.api.InternalPlugin; import com.qcadoo.plugin.internal.api.ModuleFactoryAccessor; import com.qcadoo.plugin.internal.api.PluginDescriptorParser; import com.qcadoo.plugin.internal.api.PluginDescriptorResolver; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; import org.jdom.Element; import org.jdom.input.DOMBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.util.ResourceUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.UnknownHostException; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static com.google.common.base.Preconditions.checkNotNull; @Service public class DefaultPluginDescriptorParser implements PluginDescriptorParser { private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginDescriptorParser.class); @Autowired private ModuleFactoryAccessor moduleFactoryAccessor; @Autowired private PluginDescriptorResolver pluginDescriptorResolver; private DocumentBuilder documentBuilder; @Value("#{plugin.pluginsTmpPath}") private String pluginsTmpPath; @Value("#{plugin.descriptors}") private String descriptor; private final PathMatcher matcher = new AntPathMatcher(); public DefaultPluginDescriptorParser() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { URL url = new URL("http://www.qcadoo.com"); url.openConnection(); factory.setValidating(true); } catch (UnknownHostException e) { factory.setValidating(false); } catch (IOException e) { factory.setValidating(false); } factory.setValidating(false); factory.setNamespaceAware(true); factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); documentBuilder = factory.newDocumentBuilder(); documentBuilder.setErrorHandler(new com.qcadoo.plugin.api.errorhandler.ValidationErrorHandler()); } catch (ParserConfigurationException e) { throw new IllegalStateException("Error while parsing plugin xml schema", e); } } @Override public InternalPlugin parse(final Resource resource) { try { LOG.info("Parsing descriptor for:" + resource); boolean ignoreModules = false; URL url = ResourceUtils.extractJarFileURL(resource.getURL()); return parse(resource.getInputStream(), ignoreModules, FilenameUtils.getName(url.toString())); } catch (IOException e) { throw new PluginException(e.getMessage(), e); } catch (Exception e) { throw new PluginException(e.getMessage(), e); } } @Override public InternalPlugin parse(final File file) { JarFile jarFile = null; try { LOG.info("Parsing descriptor for:" + file.getAbsolutePath()); boolean ignoreModules = true; jarFile = new JarFile(file); JarEntry descriptorEntry = findDescriptorEntry(jarFile.entries(), file.getAbsolutePath()); return parse(jarFile.getInputStream(descriptorEntry), ignoreModules, file.getName()); } catch (IOException e) { throw new PluginException("Plugin descriptor " + descriptor + " not found in " + file.getAbsolutePath(), e); } catch (Exception e) { throw new PluginException(e.getMessage(), e); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException e) { throw new PluginException("Plugin descriptor " + descriptor + " not found in " + file.getAbsolutePath(), e); } } } } private JarEntry findDescriptorEntry(final Enumeration<JarEntry> jarEntries, final String fileName) { while (jarEntries.hasMoreElements()) { JarEntry jarEntry = jarEntries.nextElement(); if (matcher.match(descriptor, jarEntry.getName())) { return jarEntry; } } throw new PluginException("Plugin descriptor " + descriptor + " not found in " + fileName); } private InternalPlugin parse(final InputStream inputStream, final boolean ignoreModules, final String fileName) throws IOException { try { Document document = documentBuilder.parse(inputStream); Node root = document.getDocumentElement(); Builder pluginBuilder = parsePluginNode(root, ignoreModules); InternalPlugin plugin = pluginBuilder.withFileName(fileName).build(); LOG.info("Parse complete"); return plugin; } catch (SAXException e) { throw new PluginException(e.getMessage(), e); } } @Override public Set<InternalPlugin> loadPlugins() { Map<String, InternalPlugin> loadedplugins = new HashMap<String, InternalPlugin>(); Resource[] resources = pluginDescriptorResolver.getDescriptors(); for (Resource resource : resources) { InternalPlugin plugin = parse(resource); if (loadedplugins.containsKey(plugin.getIdentifier())) { throw new PluginException("Duplicated plugin identifier: " + plugin.getIdentifier()); } loadedplugins.put(plugin.getIdentifier(), plugin); } return new HashSet<InternalPlugin>(loadedplugins.values()); } @Override public Set<InternalPlugin> getTemporaryPlugins() { Set<InternalPlugin> plugins = new HashSet<InternalPlugin>(); if (pluginsTmpPath == null || pluginsTmpPath.trim().isEmpty()) { return plugins; } File pluginsTmpFile = new File(pluginsTmpPath); if (!pluginsTmpFile.exists()) { LOG.warn("Plugins temporary directory does not exist: " + pluginsTmpPath); return plugins; } try { FilenameFilter jarsFilter = new WildcardFileFilter("*.jar"); if (!pluginsTmpFile.exists()) { throw new IOException(); } File[] pluginJars = pluginsTmpFile.listFiles(jarsFilter); for (int i = 0; i < pluginJars.length; ++i) { File jarRes = pluginJars[i]; InternalPlugin plugin = parse(jarRes); plugin.changeStateTo(PluginState.TEMPORARY); plugins.add(plugin); } return plugins; } catch (IOException e) { throw new IllegalStateException("Failed to reading plugins from " + pluginsTmpPath, e); } } private Builder parsePluginNode(final Node pluginNode, final boolean ignoreModules) { Preconditions.checkState("plugin".equals(pluginNode.getNodeName()), "Wrong plugin description root tag"); String pluginIdentifier = getStringAttribute(pluginNode, "plugin"); checkNotNull(pluginIdentifier, "No plugin identifier"); LOG.info("Parsing plugin " + pluginIdentifier); Builder pluginBuilder = new Builder(pluginIdentifier, moduleFactoryAccessor.getModuleFactories()); String pluginVersionStr = getStringAttribute(pluginNode, "version"); checkNotNull(pluginVersionStr, "No plugin version"); pluginBuilder.withVersion(pluginVersionStr); String isSystemPluginStr = getStringAttribute(pluginNode, "system"); if (isSystemPluginStr != null && Boolean.parseBoolean(isSystemPluginStr)) { pluginBuilder.asSystem(); } String pluginGroup = getStringAttribute(pluginNode, "group"); pluginBuilder.withGroup(pluginGroup); for (Node child : getChildNodes(pluginNode)) { if ("information".equals(child.getNodeName())) { addPluginInformation(child, pluginBuilder); } else if ("dependencies".equals(child.getNodeName())) { addDependenciesInformation(child, pluginBuilder); } else if ("modules".equals(child.getNodeName())) { if (!ignoreModules) { addModules(child, pluginBuilder, pluginIdentifier); } } else if ("features".equals(child.getNodeName())) { addFeturesInformation(child, pluginBuilder); } else { throw new IllegalStateException("Wrong plugin tag: " + child.getNodeName()); } } return pluginBuilder; } private void addPluginInformation(final Node informationsNode, final Builder pluginBuilder) { for (Node child : getChildNodes(informationsNode)) { if ("name".equals(child.getNodeName())) { pluginBuilder.withName(getTextContent(child)); } else if ("description".equals(child.getNodeName())) { pluginBuilder.withDescription(getTextContent(child)); } else if ("vendor".equals(child.getNodeName())) { addPluginVendorInformation(child, pluginBuilder); } else if ("license".equals(child.getNodeName())) { pluginBuilder.withLicense(getTextContent(child)); } else { throw new IllegalStateException("Wrong plugin information tag: " + child.getNodeName()); } } } private void addPluginVendorInformation(final Node vendorInformationsNode, final Builder pluginBuilder) { for (Node child : getChildNodes(vendorInformationsNode)) { if ("name".equals(child.getNodeName())) { pluginBuilder.withVendor(getTextContent(child)); } else if ("url".equals(child.getNodeName())) { pluginBuilder.withVendorUrl(getTextContent(child)); } else { throw new IllegalStateException("Wrong plugin vendor tag: " + child.getNodeName()); } } } private void addDependenciesInformation(final Node dependenciesNode, final Builder pluginBuilder) { for (Node child : getChildNodes(dependenciesNode)) { if ("dependency".equals(child.getNodeName())) { addDependencyInformation(child, pluginBuilder); } else { throw new IllegalStateException("Wrong plugin dependency tag: " + child.getNodeName()); } } } private void addDependencyInformation(final Node dependencyNode, final Builder pluginBuilder) { String dependencyPluginIdentifier = null; String dependencyPluginVersion = null; for (Node child : getChildNodes(dependencyNode)) { if ("plugin".equals(child.getNodeName())) { dependencyPluginIdentifier = getTextContent(child); } else if ("version".equals(child.getNodeName())) { dependencyPluginVersion = getTextContent(child); } else { throw new IllegalStateException("Wrong plugin dependency tag: " + child.getNodeName()); } } checkNotNull(dependencyPluginIdentifier, "No plugin dependency identifier"); pluginBuilder.withDependency(dependencyPluginIdentifier, dependencyPluginVersion); } private void addFeturesInformation(final Node featuresNode, final Builder pluginBuilder) { for (Node child : getChildNodes(featuresNode)) { addFeatureInformation(child, pluginBuilder); } } private void addFeatureInformation(final Node featureNode, final Builder pluginBuilder) { String featureName = featureNode.getNodeName(); String systemName = getStringAttribute(featureNode, "system"); pluginBuilder.withFeature(featureName, systemName); } private void addModules(final Node modulesNode, final Builder pluginBuilder, final String pluginIdentifier) { for (Node child : getChildNodes(modulesNode)) { ModuleFactory<?> moduleFactory = moduleFactoryAccessor.getModuleFactory(child.getLocalName()); LOG.info("Parsing module " + child.getLocalName() + " for plugin " + pluginIdentifier); Module module = moduleFactory.parse(pluginIdentifier, convertNodeToJdomElement(child)); checkNotNull(module, "Module for " + child.getLocalName() + " is null"); pluginBuilder.withModule(moduleFactory, module); } } private Element convertNodeToJdomElement(final Node child) { return new DOMBuilder().build((org.w3c.dom.Element) child); } private List<Node> getChildNodes(final Node node) { List<Node> result = new LinkedList<Node>(); NodeList childNodes = node.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeType() != Node.ELEMENT_NODE) { continue; } result.add(child); } return result; } private String getTextContent(final Node node) { String result = node.getTextContent(); if (result != null) { result = result.trim(); if (result.isEmpty()) { return null; } return result; } return null; } private String getStringAttribute(final Node node, final String name) { if (node != null && node.getAttributes() != null) { Node attribute = node.getAttributes().getNamedItem(name); if (attribute != null) { return attribute.getNodeValue(); } } return null; } public void setModuleFactoryAccessor(final ModuleFactoryAccessor moduleFactoryAccessor) { this.moduleFactoryAccessor = moduleFactoryAccessor; } public void setPluginDescriptorResolver(final PluginDescriptorResolver pluginDescriptorResolver) { this.pluginDescriptorResolver = pluginDescriptorResolver; } }