/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV This program 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, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.extension.parser; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.servoy.extension.ExtensionDependencyDeclaration; import com.servoy.extension.ExtensionUtils.EntryInputStreamRunner; import com.servoy.extension.IExtensionProvider; import com.servoy.extension.LibDependencyDeclaration; import com.servoy.extension.MessageKeeper; import com.servoy.extension.ServoyDependencyDeclaration; import com.servoy.extension.VersionStringUtils; import com.servoy.j2db.util.Debug; /** * Parses the package.xml file (received as a stream) to get the dependency info. * * @author acostescu * @author gboros */ public class ParseDependencyMetadata implements EntryInputStreamRunner<FullDependencyMetadata> { private final String packageName; private final MessageKeeper messages; public ParseDependencyMetadata(String packageName, MessageKeeper messages) { this.packageName = packageName; this.messages = messages; } public FullDependencyMetadata runOnEntryInputStream(InputStream is) { FullDependencyMetadata dmd = null; SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); //$NON-NLS-1$ if (factory != null) { Schema schema = null; try { // prepare to verify that XML adheres to our schema; because of this schema defined default values will be set as well when parsing schema = factory.newSchema(IExtensionProvider.class.getResource(EXPParser.EXTENSION_SCHEMA)); } catch (SAXException ex) { messages.addError("Unable to validate 'package.xml' against the .xsd. Please report this problem to Servoy."); //$NON-NLS-1$ Debug.error("Error compiling 'servoy-extension.xsd'."); //$NON-NLS-1$ } if (schema != null) { try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setSchema(schema); DocumentBuilder db = dbf.newDocumentBuilder(); db.setErrorHandler(new ParseDependencyMetadataErrorHandler(packageName, messages)); Document doc = db.parse(is); // should we use UTF-8 here? Element root = doc.getDocumentElement(); // "servoy-extension" tag root.normalize(); // as this was already validated by schema, we need less null-checks and less structure checks try { String extensionId = root.getElementsByTagName(EXPParser.EXTENSION_ID).item(0).getTextContent(); String extensionName = root.getElementsByTagName(EXPParser.EXTENSION_NAME).item(0).getTextContent(); String version = root.getElementsByTagName(EXPParser.VERSION).item(0).getTextContent(); NodeList dependencies = root.getElementsByTagName(EXPParser.DEPENDENCIES); ServoyDependencyDeclaration sdd = null; List<ExtensionDependencyDeclaration> edds = new ArrayList<ExtensionDependencyDeclaration>(); List<FullLibDependencyDeclaration> ldds = new ArrayList<FullLibDependencyDeclaration>(); if (dependencies != null && dependencies.getLength() == 1) { Element dependenciesNode = (Element)dependencies.item(0); dependencies = dependenciesNode.getElementsByTagName(EXPParser.SERVOY_DEPENDENCY); Element element; if (dependencies != null && dependencies.getLength() == 1) { element = ((Element)dependencies.item(0)); sdd = new ServoyDependencyDeclaration(getMinMaxVersion(element, EXPParser.MIN_VERSION), getMinMaxVersion(element, EXPParser.MAX_VERSION)); } int i = 0; dependencies = dependenciesNode.getElementsByTagName(EXPParser.EXTENSION_DEPENDENCY); while (dependencies != null && dependencies.getLength() > i) { element = ((Element)dependencies.item(i++)); String minVersion = getMinMaxVersion(element, EXPParser.MIN_VERSION); String maxVersion = getMinMaxVersion(element, EXPParser.MAX_VERSION); edds.add(new ExtensionDependencyDeclaration(element.getElementsByTagName(EXPParser.ID).item(0).getTextContent(), minVersion, maxVersion)); } i = 0; dependencies = dependenciesNode.getElementsByTagName(EXPParser.LIB_DEPENDENCY); while (dependencies != null && dependencies.getLength() > i) { element = ((Element)dependencies.item(i++)); String libVersion = element.getElementsByTagName(EXPParser.VERSION).item(0).getTextContent(); String path = element.getElementsByTagName(EXPParser.PATH).item(0).getTextContent(); String minVersion = getMinMaxVersion(element, EXPParser.MIN_VERSION); String maxVersion = getMinMaxVersion(element, EXPParser.MAX_VERSION); if (minVersion == VersionStringUtils.UNBOUNDED && maxVersion == VersionStringUtils.UNBOUNDED) { // no min/max specified for lib so it is a fixed version dependency minVersion = maxVersion = libVersion; } ldds.add(new FullLibDependencyDeclaration(element.getElementsByTagName(EXPParser.ID).item(0).getTextContent(), libVersion, minVersion, maxVersion, path)); } } // check that there are no duplicate dependency/lib declarations Set<String> ids = new HashSet<String>(); for (ExtensionDependencyDeclaration dep : edds) { if (!ids.add(dep.id)) { throw new IllegalArgumentException("multiple extension dependency declarations with id '" + dep.id + "' in '" + //$NON-NLS-1$ //$NON-NLS-2$ packageName + "'"); //$NON-NLS-1$ } } ids.clear(); for (LibDependencyDeclaration dep : ldds) { if (!ids.add(dep.id)) { throw new IllegalArgumentException("multiple lib dependency declarations with id '" + dep.id + "' in '" + //$NON-NLS-1$ //$NON-NLS-2$ packageName + "'"); //$NON-NLS-1$ } } // cache dependency info about this version of the extension dmd = new FullDependencyMetadata(extensionId, version, extensionName, sdd, (edds.size() > 0) ? edds.toArray(new ExtensionDependencyDeclaration[edds.size()]) : null, (ldds.size() > 0) ? ldds.toArray(new FullLibDependencyDeclaration[ldds.size()]) : null); } catch (IllegalArgumentException e) { messages.addError("Incorrect content when parsing 'package.xml' in package '" + packageName + "'. Reason: " + e.getMessage() + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } catch (FactoryConfigurationError e) { messages.addError("Unable to parse 'package.xml'. Please report this problem to Servoy."); //$NON-NLS-1$ Debug.error("Cannot find document builder factory."); //$NON-NLS-1$ } catch (ParserConfigurationException e) { messages.addError("Cannot parse 'package.xml' in package '" + packageName + "'. Reason: " + e.getMessage() + "."); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ Debug.trace("Cannot parse 'package.xml' in package '" + packageName + "'.", e); //$NON-NLS-1$ //$NON-NLS-2$ } catch (SAXException e) { messages.addError("Cannot parse 'package.xml' in package '" + packageName + "'. Reason: " + e.getMessage() + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ Debug.trace("Cannot parse 'package.xml' in package '" + packageName + "'.", e); //$NON-NLS-1$ //$NON-NLS-2$ } catch (IOException e) { messages.addError("Cannot parse 'package.xml' in package '" + packageName + "'. Reason: " + e.getMessage() + "."); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ Debug.trace("Cannot parse 'package.xml' in package '" + packageName + "'.", e); //$NON-NLS-1$//$NON-NLS-2$ } } } else { messages.addError("Unable to validate 'package.xml' against the .xsd. Please report this problem to Servoy."); //$NON-NLS-1$ Debug.error("Cannot find schema factory."); //$NON-NLS-1$ } return dmd; } // gets & creates a (possibly exclusive or unbounded) min or max version string from the element protected String getMinMaxVersion(Element element, String minOrMax) { String minMaxVersion = VersionStringUtils.UNBOUNDED; NodeList verNode = element.getElementsByTagName(minOrMax); if (verNode != null && verNode.getLength() == 1) { minMaxVersion = verNode.item(0).getTextContent(); NamedNodeMap attrs = verNode.item(0).getAttributes(); if (attrs != null) { Node attr = attrs.getNamedItem(EXPParser.INCLUSIVE_MIN_MAX_ATTR); if (attr != null && EXPParser.FALSE_VALUE.equals(attr.getNodeValue())) { minMaxVersion = VersionStringUtils.createExclusiveVersionString(minMaxVersion); } } } return minMaxVersion; } }