| /* |
| * 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.felix.utils.repository; |
| |
| import java.io.InputStream; |
| import java.io.Writer; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.xml.stream.Location; |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLOutputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.stream.XMLStreamWriter; |
| |
| import org.apache.felix.utils.resource.CapabilityImpl; |
| import org.apache.felix.utils.resource.RequirementImpl; |
| import org.apache.felix.utils.resource.ResourceImpl; |
| import org.apache.felix.utils.resource.SimpleFilter; |
| import org.apache.felix.utils.version.VersionTable; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Version; |
| import org.osgi.resource.Capability; |
| import org.osgi.resource.Requirement; |
| import org.osgi.resource.Resource; |
| import org.osgi.service.repository.ContentNamespace; |
| |
| import static javax.xml.stream.XMLStreamConstants.CHARACTERS; |
| import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; |
| import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; |
| |
| /** |
| * Repository XML xml based on StaX |
| */ |
| public final class StaxParser { |
| |
| public static final String REPOSITORY = "repository"; |
| public static final String REPO_NAME = "name"; |
| public static final String INCREMENT = "increment"; |
| public static final String REFERRAL = "referral"; |
| public static final String DEPTH = "depth"; |
| public static final String URL = "url"; |
| public static final String RESOURCE = "resource"; |
| public static final String CAPABILITY = "capability"; |
| public static final String REQUIREMENT = "requirement"; |
| public static final String NAMESPACE = "namespace"; |
| public static final String ATTRIBUTE = "attribute"; |
| public static final String DIRECTIVE = "directive"; |
| public static final String NAME = "name"; |
| public static final String VALUE = "value"; |
| public static final String TYPE = "type"; |
| |
| public static final String REPOSITORY_NAMESPACE = "http://www.osgi.org/xmlns/repository/v1.0.0"; |
| |
| static XMLInputFactory inputFactory; |
| static XMLOutputFactory outputFactory; |
| |
| private StaxParser() { |
| } |
| |
| public static class Referral { |
| public String url; |
| public int depth = Integer.MAX_VALUE; |
| } |
| |
| public static class XmlRepository { |
| public String name; |
| public long increment; |
| public List<Referral> referrals = new ArrayList<>(); |
| public List<Resource> resources = new ArrayList<>(); |
| } |
| |
| public static void write(XmlRepository repository, Writer os) throws XMLStreamException { |
| XMLStreamWriter writer = getOutputFactory().createXMLStreamWriter(os); |
| try { |
| writer.writeStartDocument(); |
| writer.setDefaultNamespace(REPOSITORY_NAMESPACE); |
| // repository element |
| writer.writeStartElement(REPOSITORY_NAMESPACE, REPOSITORY); |
| writer.writeAttribute("xmlns", REPOSITORY_NAMESPACE); |
| writer.writeAttribute(REPO_NAME, repository.name); |
| writer.writeAttribute(INCREMENT, Long.toString(repository.increment)); |
| // referrals |
| for (Referral referral : repository.referrals) { |
| writer.writeStartElement(REPOSITORY_NAMESPACE, REFERRAL); |
| writer.writeAttribute(DEPTH, Integer.toString(referral.depth)); |
| writer.writeAttribute(URL, referral.url); |
| writer.writeEndElement(); |
| } |
| // resources |
| for (Resource resource : repository.resources) { |
| writer.writeStartElement(REPOSITORY_NAMESPACE, RESOURCE); |
| for (Capability cap : resource.getCapabilities(null)) { |
| writeClause(writer, CAPABILITY, cap.getNamespace(), cap.getDirectives(), cap.getAttributes()); |
| } |
| for (Requirement req : resource.getRequirements(null)) { |
| writeClause(writer, REQUIREMENT, req.getNamespace(), req.getDirectives(), req.getAttributes()); |
| } |
| writer.writeEndElement(); |
| } |
| writer.writeEndDocument(); |
| writer.flush(); |
| } finally { |
| writer.close(); |
| } |
| } |
| |
| private static void writeClause(XMLStreamWriter writer, String element, String namespace, Map<String, String> directives, Map<String, Object> attributes) throws XMLStreamException { |
| writer.writeStartElement(REPOSITORY_NAMESPACE, element); |
| writer.writeAttribute(NAMESPACE, namespace); |
| for (Map.Entry<String, String> dir : directives.entrySet()) { |
| writer.writeStartElement(REPOSITORY_NAMESPACE, DIRECTIVE); |
| writer.writeAttribute(NAME, dir.getKey()); |
| writer.writeAttribute(VALUE, dir.getValue()); |
| writer.writeEndElement(); |
| } |
| for (Map.Entry<String, Object> att : attributes.entrySet()) { |
| String key = att.getKey(); |
| Object val = att.getValue(); |
| writer.writeStartElement(REPOSITORY_NAMESPACE, ATTRIBUTE); |
| writer.writeAttribute(NAME, key); |
| if (val instanceof Version) { |
| writer.writeAttribute(TYPE, "Version"); |
| } else if (val instanceof Long) { |
| writer.writeAttribute(TYPE, "Long"); |
| } else if (val instanceof Double) { |
| writer.writeAttribute(TYPE, "Double"); |
| } else if (val instanceof Iterable) { |
| Iterable<?> it = (Iterable<?>) att.getValue(); |
| String scalar = null; |
| for (Object o : it) { |
| String ts; |
| if (o instanceof String) { |
| ts = "String"; |
| } else if (o instanceof Long) { |
| ts = "Long"; |
| } else if (o instanceof Double) { |
| ts = "Double"; |
| } else if (o instanceof Version) { |
| ts = "Version"; |
| } else { |
| throw new IllegalArgumentException("Unsupported scalar type: " + o); |
| } |
| if (scalar == null) { |
| scalar = ts; |
| } else if (!scalar.equals(ts)) { |
| throw new IllegalArgumentException("Unconsistent list type for attribute " + key); |
| } |
| } |
| writer.writeAttribute(TYPE, "List<" + scalar + ">"); |
| StringBuilder sb = new StringBuilder(); |
| boolean first = true; |
| for (Object o : it) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append(","); |
| } |
| sb.append(o.toString().replace(",", "\\,")); |
| } |
| val = sb.toString(); |
| } |
| writer.writeAttribute(VALUE, val.toString()); |
| writer.writeEndElement(); |
| } |
| writer.writeEndElement(); |
| } |
| |
| public static XmlRepository parse(InputStream is) throws XMLStreamException { |
| return parse(null, is, null); |
| } |
| |
| public static XmlRepository parse(URI repositoryUrl, InputStream is) throws XMLStreamException { |
| return parse(repositoryUrl, is, null); |
| } |
| |
| public static XmlRepository parse(URI repositoryUrl, InputStream is, XmlRepository previous) throws XMLStreamException { |
| XMLStreamReader reader = getInputFactory().createXMLStreamReader(is); |
| try { |
| int event = reader.nextTag(); |
| if (event != START_ELEMENT || !REPOSITORY.equals(reader.getLocalName())) { |
| throw new IllegalStateException("Expected element 'repository' at the root of the document"); |
| } |
| XmlRepository repo = new XmlRepository(); |
| for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) { |
| String attrName = reader.getAttributeLocalName(i); |
| String attrValue = reader.getAttributeValue(i); |
| switch (attrName) { |
| case REPO_NAME: |
| repo.name = attrValue; |
| break; |
| case INCREMENT: |
| repo.increment = Long.parseLong(attrValue); |
| break; |
| default: |
| throw new IllegalStateException("Unexpected attribute '" + attrName + "'"); |
| } |
| } |
| if (previous != null && repo.increment == previous.increment) { |
| return previous; |
| } |
| while ((event = reader.nextTag()) == START_ELEMENT) { |
| String element = reader.getLocalName(); |
| switch (element) { |
| case REFERRAL: |
| Referral referral = new Referral(); |
| for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) { |
| String attrName = reader.getAttributeLocalName(i); |
| String attrValue = reader.getAttributeValue(i); |
| switch (attrName) { |
| case DEPTH: |
| referral.depth = Integer.parseInt(attrValue); |
| break; |
| case URL: |
| referral.url = attrValue; |
| break; |
| default: |
| throw new IllegalStateException("Unexpected attribute '" + attrName + "'"); |
| } |
| } |
| if (referral.url == null) { |
| throw new IllegalStateException("Expected attribute '" + URL + "'"); |
| } |
| repo.referrals.add(referral); |
| sanityCheckEndElement(reader, reader.nextTag(), REFERRAL); |
| break; |
| case RESOURCE: |
| repo.resources.add(parseResource(repositoryUrl, reader)); |
| break; |
| default: |
| throw new IllegalStateException("Unsupported element '" + element + "'. Expected 'referral' or 'resource'"); |
| } |
| } |
| // Sanity check |
| sanityCheckEndElement(reader, event, REPOSITORY); |
| return repo; |
| } finally { |
| reader.close(); |
| } |
| } |
| |
| private static void sanityCheckEndElement(XMLStreamReader reader, int event, String element) { |
| if (event != END_ELEMENT || !element.equals(reader.getLocalName())) { |
| throw new IllegalStateException("Unexpected state while finishing element " + element); |
| } |
| } |
| |
| private static ResourceImpl parseResource(URI repositoryUrl, XMLStreamReader reader) { |
| try { |
| if (reader.getAttributeCount() > 0) { |
| throw new IllegalStateException("Unexpected attribute '" + reader.getAttributeLocalName(0) + "'"); |
| } |
| ResourceImpl resource = new ResourceImpl(); |
| int event; |
| while ((event = reader.nextTag()) == START_ELEMENT) { |
| String element = reader.getLocalName(); |
| switch (element) { |
| case CAPABILITY: |
| CapabilityImpl cap = parseCapability(reader, resource); |
| // Resolve relative resource urls now |
| if (repositoryUrl != null && ContentNamespace.CONTENT_NAMESPACE.equals(cap.getNamespace())) { |
| Object url = cap.getAttributes().get(ContentNamespace.CAPABILITY_URL_ATTRIBUTE); |
| if (url instanceof String) { |
| url = repositoryUrl.resolve(url.toString()).toString(); |
| cap.getAttributes().put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, url); |
| } |
| } |
| resource.addCapability(cap); |
| break; |
| case REQUIREMENT: |
| resource.addRequirement(parseRequirement(reader, resource)); |
| break; |
| default: |
| while ((event = reader.next()) != END_ELEMENT) { |
| switch (event) { |
| case START_ELEMENT: |
| throw new IllegalStateException("Unexpected element '" + reader.getLocalName() + "' inside 'resource' element"); |
| case CHARACTERS: |
| throw new IllegalStateException("Unexpected text inside 'resource' element"); |
| default: |
| break; |
| } |
| } |
| break; |
| } |
| } |
| // Sanity check |
| sanityCheckEndElement(reader, event, RESOURCE); |
| return resource; |
| } catch (Exception e) { |
| Location loc = reader.getLocation(); |
| if (loc != null) { |
| throw new IllegalStateException("Error while parsing resource at line " + loc.getLineNumber() + " and column " + loc.getColumnNumber(), e); |
| } else { |
| throw new IllegalStateException("Error while parsing resource", e); |
| } |
| } |
| } |
| |
| private static CapabilityImpl parseCapability(XMLStreamReader reader, ResourceImpl resource) throws XMLStreamException { |
| String[] namespace = new String[1]; |
| Map<String, String> directives = new HashMap<>(); |
| Map<String, Object> attributes = new HashMap<>(); |
| parseClause(reader, namespace, directives, attributes); |
| sanityCheckEndElement(reader, reader.getEventType(), CAPABILITY); |
| return new CapabilityImpl(resource, namespace[0], directives, attributes); |
| } |
| |
| private static RequirementImpl parseRequirement(XMLStreamReader reader, ResourceImpl resource) throws XMLStreamException { |
| String[] namespace = new String[1]; |
| Map<String, String> directives = new HashMap<>(); |
| Map<String, Object> attributes = new HashMap<>(); |
| parseClause(reader, namespace, directives, attributes); |
| sanityCheckEndElement(reader, reader.getEventType(), REQUIREMENT); |
| String filterStr = directives.get(Constants.FILTER_DIRECTIVE); |
| SimpleFilter sf = (filterStr != null) |
| ? SimpleFilter.parse(filterStr) |
| : SimpleFilter.convert(attributes); |
| return new RequirementImpl(resource, namespace[0], directives, attributes, sf); |
| } |
| |
| private static void parseClause(XMLStreamReader reader, String[] namespace, Map<String, String> directives, Map<String, Object> attributes) throws XMLStreamException { |
| namespace[0] = null; |
| for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) { |
| String name = reader.getAttributeLocalName(i); |
| String value = reader.getAttributeValue(i); |
| if (NAMESPACE.equals(name)) { |
| namespace[0] = value; |
| } else { |
| throw new IllegalStateException("Unexpected attribute: '" + name + "'. Expected 'namespace'"); |
| } |
| } |
| if (namespace[0] == null) { |
| throw new IllegalStateException("Expected attribute 'namespace'"); |
| } |
| while (reader.nextTag() == START_ELEMENT) { |
| String element = reader.getLocalName(); |
| switch (element) { |
| case DIRECTIVE: { |
| String name = null; |
| String value = null; |
| for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) { |
| String attName = reader.getAttributeLocalName(i); |
| String attValue = reader.getAttributeValue(i); |
| switch (attName) { |
| case NAME: |
| name = attValue; |
| break; |
| case VALUE: |
| value = attValue; |
| break; |
| default: |
| throw new IllegalStateException("Unexpected attribute: '" + attName + "'. Expected 'name', or 'value'."); |
| } |
| } |
| if (name == null || value == null) { |
| throw new IllegalStateException("Expected attribute 'name' and 'value'"); |
| } |
| directives.put(name, value); |
| sanityCheckEndElement(reader, reader.nextTag(), DIRECTIVE); |
| break; |
| } |
| case ATTRIBUTE: { |
| String name = null; |
| String value = null; |
| String type = "String"; |
| for (int i = 0, nb = reader.getAttributeCount(); i < nb; i++) { |
| String attName = reader.getAttributeLocalName(i); |
| String attValue = reader.getAttributeValue(i); |
| switch (attName) { |
| case NAME: |
| name = attValue; |
| break; |
| case VALUE: |
| value = attValue; |
| break; |
| case TYPE: |
| type = attValue; |
| break; |
| default: |
| throw new IllegalStateException("Unexpected attribute: '" + attName + "'. Expected 'name', 'value' or 'type'."); |
| } |
| } |
| if (name == null || value == null) { |
| throw new IllegalStateException("Expected attribute 'name' and 'value'"); |
| } |
| attributes.put(name, parseAttribute(value, type)); |
| sanityCheckEndElement(reader, reader.nextTag(), ATTRIBUTE); |
| break; |
| } |
| default: |
| throw new IllegalStateException("Unexpected element: '" + element + ". Expected 'directive' or 'attribute'"); |
| } |
| } |
| } |
| |
| private static Object parseAttribute(String value, String type) { |
| if ("String".equals(type)) { |
| return value; |
| } else if ("Version".equals(type)) { |
| return VersionTable.getVersion(value); |
| } else if ("Long".equals(type)) { |
| return Long.parseLong(value.trim()); |
| } else if ("Double".equals(type)) { |
| return Double.parseDouble(value.trim()); |
| } else if (type.startsWith("List<") && type.endsWith(">")) { |
| type = type.substring("List<".length(), type.length() - 1); |
| List<Object> list = new ArrayList<>(); |
| for (String s : value.split(",")) { |
| list.add(parseAttribute(s.trim(), type)); |
| } |
| return list; |
| } else { |
| throw new IllegalStateException("Unexpected type: '" + type + "'"); |
| } |
| } |
| |
| private static synchronized XMLInputFactory getInputFactory() { |
| if (StaxParser.inputFactory == null) { |
| XMLInputFactory factory = XMLInputFactory.newInstance(); |
| factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true); |
| StaxParser.inputFactory = factory; |
| } |
| return StaxParser.inputFactory; |
| } |
| |
| private static synchronized XMLOutputFactory getOutputFactory() { |
| if (StaxParser.outputFactory == null) { |
| StaxParser.outputFactory = XMLOutputFactory.newInstance(); |
| } |
| return StaxParser.outputFactory; |
| } |
| |
| } |