/******************************************************************************* * Copyright (c) 2008, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMware Inc. - initial contribution *******************************************************************************/ package org.eclipse.virgo.kernel.artifact.plan; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.osgi.framework.Version; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.eclipse.virgo.kernel.artifact.ArtifactSpecification; import org.eclipse.virgo.kernel.artifact.plan.PlanDescriptor.Provisioning; import org.eclipse.virgo.kernel.artifact.plan.internal.PlanReaderEntityResolver; import org.eclipse.virgo.kernel.artifact.plan.internal.PlanReaderErrorHandler; import org.eclipse.virgo.util.common.PropertyPlaceholderResolver; import org.eclipse.virgo.util.osgi.manifest.VersionRange; /** * A reader that takes a URI and transforms it into a {@link PlanDescriptor} metadata artifact * <p /> * * <strong>Concurrent Semantics</strong><br /> * * Threadsafe * */ public final class PlanReader { private static final String TYPE_ATTRIBUTE = "type"; private static final String NAME_ATTRIBUTE = "name"; private static final String VERSION_ATTRIBUTE = "version"; private static final String URI_ATTRIBUTE = "uri"; private static final String SCOPED_ATTRIBUTE = "scoped"; private static final String ATOMIC_ATTRIBUTE = "atomic"; private static final String PROVISIONING_INHERIT_ATTRIBUTE = "inherit"; private static final String PROVISIONING_AUTO_ATTRIBUTE = "auto"; private static final String PROVISIONING_DISABLED_ATTRIBUTE = "disabled"; private static final String ARTIFACT_ELEMENT = "artifact"; private static final String ATTRIBUTE_ELEMENT = "attribute"; private static final String PROPERTY_ELEMENT = "property"; private static final String VALUE_ATTRIBUTE = "value"; private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; private final PropertyPlaceholderResolver resolver = new PropertyPlaceholderResolver(); /** * Creates a {@link PlanDescriptor} meta-data artifact from an {@link InputStream} * * @param inputStream from which the plan is to be read * @return The plan descriptor (meta-data) from the input stream */ public PlanDescriptor read(InputStream inputStream) { try { Document doc = readDocument(inputStream); Element element = doc.getDocumentElement(); return parsePlanElement(element); } catch (Exception e) { throw new RuntimeException("Failed to read plan descriptor", e); } } private Document readDocument(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException { DocumentBuilder builder = createDocumentBuilderFactory().newDocumentBuilder(); builder.setEntityResolver(new PlanReaderEntityResolver()); builder.setErrorHandler(new PlanReaderErrorHandler(LoggerFactory.getLogger(PlanBridge.class))); return builder.parse(inputStream); } private DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); factory.setNamespaceAware(true); factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); return factory; } private PlanDescriptor parsePlanElement(Element element) { String name = element.getAttribute(NAME_ATTRIBUTE); Version version = new Version(element.getAttribute(VERSION_ATTRIBUTE)); boolean scoped = Boolean.parseBoolean(element.getAttribute(SCOPED_ATTRIBUTE)); boolean atomic = Boolean.parseBoolean(element.getAttribute(ATOMIC_ATTRIBUTE)); Provisioning dependencies = parseProvisioningAttribute(element); Properties attributes = parseAttributes(element); List<ArtifactSpecification> artifactSpecifications = parseArtifactElements(element.getElementsByTagName(ARTIFACT_ELEMENT), attributes); return new PlanDescriptor(name, version, scoped, atomic, dependencies, artifactSpecifications); } private Provisioning parseProvisioningAttribute(Element element) { String provisioningAttribute = element.getAttribute("provisioning"); Provisioning provisioning; if (isEmpty(provisioningAttribute) || PROVISIONING_INHERIT_ATTRIBUTE.equals(provisioningAttribute)) { provisioning = Provisioning.INHERIT; } else if (PROVISIONING_AUTO_ATTRIBUTE.equals(provisioningAttribute)) { provisioning = Provisioning.AUTO; } else if (PROVISIONING_DISABLED_ATTRIBUTE.equals(provisioningAttribute)) { provisioning = Provisioning.DISABLED; } else { throw new IllegalArgumentException("Invalid provisioning value '" + provisioningAttribute + "'"); } return provisioning; } private Properties parseAttributes(Element element) { Properties result = new Properties(); NodeList attributeElements = element.getElementsByTagName(ATTRIBUTE_ELEMENT); for (int x = 0; x < attributeElements.getLength(); x++) { Element attribute = (Element) attributeElements.item(x); String name = attribute.getAttribute(NAME_ATTRIBUTE); String value = attribute.getAttribute(VALUE_ATTRIBUTE); result.put(name, value); } return result; } private List<ArtifactSpecification> parseArtifactElements(NodeList artifactElements, Properties attributes) { List<ArtifactSpecification> artifactSpecifications = new ArrayList<ArtifactSpecification>(artifactElements.getLength()); for (int i = 0; i < artifactElements.getLength(); i++) { Element artifactElement = (Element) artifactElements.item(i); String type = replacePlaceholders(artifactElement.getAttribute(TYPE_ATTRIBUTE), attributes); String name = replacePlaceholders(artifactElement.getAttribute(NAME_ATTRIBUTE), attributes); String version = replacePlaceholders(artifactElement.getAttribute(VERSION_ATTRIBUTE), attributes); String uri = replacePlaceholders(artifactElement.getAttribute(URI_ATTRIBUTE), attributes); Map<String, String> properties = parseArtifactProperties(artifactElement, attributes); artifactSpecifications.add(buildArtifactSpecification(type, name, version, uri, properties)); } return artifactSpecifications; } private ArtifactSpecification buildArtifactSpecification(String type, String name, String version, String uriString, Map<String, String> properties) { if (isEmpty(uriString)) { return new ArtifactSpecification(type, name, new VersionRange(version), properties); } else { URI uri; try { uri = new URI(uriString); } catch (URISyntaxException e) { throw new IllegalArgumentException("Invalid URI in plan artifact specification", e); } if (!isEmpty(type)) { throw new IllegalArgumentException("Plan artifact may not specify both URI (" + uriString + ") and type (" + type + ")"); } if (!isEmpty(name)) { throw new IllegalArgumentException("Plan artifact may not specify both URI (" + uriString + ") and name (" + name + ")"); } if (!isEmpty(version)) { throw new IllegalArgumentException("Plan artifact may not specify both URI (" + uriString + ") and version (" + version + ")"); } return new ArtifactSpecification(uri, properties); } } private static boolean isEmpty(String string) { return "".equals(string); } private Map<String, String> parseArtifactProperties(Element artifactElement, Properties attributes) { Map<String, String> result = new HashMap<String, String>(); NodeList propertyElements = artifactElement.getElementsByTagName(PROPERTY_ELEMENT); for (int x = 0; x < propertyElements.getLength(); x++) { Element propertyElement = (Element) propertyElements.item(x); String name = replacePlaceholders(propertyElement.getAttribute(NAME_ATTRIBUTE), attributes); String value = replacePlaceholders(propertyElement.getAttribute(VALUE_ATTRIBUTE), attributes); result.put(name, value); } return result; } private String replacePlaceholders(String value, Properties attributes) { return this.resolver.resolve(value, attributes); } }