blob: 47987994040530592f4789a93d5b69f8433f70a7 [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.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;
}
}