blob: 0cfa78f5189a8ef107d7ab30a018e956beb03443 [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.bundlerepository.impl;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.felix.bundlerepository.Capability;
import org.apache.felix.bundlerepository.Requirement;
import org.apache.felix.bundlerepository.Resource;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Namespace;
import org.osgi.service.repository.ContentNamespace;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
public class SpecXMLPullParser
{
private static final String ATTRIBUTE = "attribute";
private static final String CAPABILITY = "capability";
private static final String DIRECTIVE = "directive";
private static final String INCREMENT = "increment";
private static final String NAME = "name";
private static final String NAMESPACE = "namespace";
private static final String REFERRAL = "referral";
private static final String REPOSITORY = "repository";
private static final String REQUIREMENT = "requirement";
private static final String RESOURCE = "resource";
public static RepositoryImpl parse(XmlPullParser reader, URI baseUri) throws Exception
{
RepositoryImpl repository = new RepositoryImpl();
for (int i = 0, ac = reader.getAttributeCount(); i < ac; i++)
{
String name = reader.getAttributeName(i);
String value = reader.getAttributeValue(i);
if (NAME.equals(name))
repository.setName(value);
else if (INCREMENT.equals(name))
repository.setLastModified(value); // TODO increment is not necessarily a timestamp
}
int event;
while ((event = reader.nextTag()) == XmlPullParser.START_TAG)
{
String element = reader.getName();
if (REFERRAL.equals(element))
{
// TODO
}
else if (RESOURCE.equals(element))
{
Resource resource = parseResource(reader, baseUri);
repository.addResource(resource);
}
else
{
PullParser.ignoreTag(reader);
}
}
PullParser.sanityCheckEndElement(reader, event, REPOSITORY);
return repository;
}
private static Resource parseResource(XmlPullParser reader, URI baseUri) throws Exception
{
ResourceImpl resource = new ResourceImpl();
try
{
int event;
while ((event = reader.nextTag()) == XmlPullParser.START_TAG)
{
String element = reader.getName();
if (CAPABILITY.equals(element))
{
Capability capability = parseCapability(reader, resource, baseUri);
if (capability != null)
resource.addCapability(capability);
}
else if (REQUIREMENT.equals(element))
{
Requirement requirement = parseRequirement(reader);
if (requirement != null) {
resource.addRequire(requirement);
}
}
else
{
PullParser.ignoreTag(reader);
}
}
PullParser.sanityCheckEndElement(reader, event, RESOURCE);
return resource;
}
catch (Exception e)
{
throw new Exception("Error while parsing resource " + resource.getId() + " at line " + reader.getLineNumber() + " and column " + reader.getColumnNumber(), e);
}
}
private static Capability parseCapability(XmlPullParser reader, ResourceImpl resource, URI baseUri) throws Exception
{
String namespace = reader.getAttributeValue(null, NAMESPACE);
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace))
{
parseIdentityNamespace(reader, resource);
return null;
}
if (ContentNamespace.CONTENT_NAMESPACE.equals(namespace))
{
if (resource.getURI() == null)
{
parseContentNamespace(reader, resource, baseUri);
return null;
}
// if the URI is already set, this is a second osgi.content capability.
// The first content capability, which is the main one, is stored in the Resource.
// Subsequent content capabilities are stored are ordinary capabilities.
}
CapabilityImpl capability = new CapabilityImpl();
if (!namespace.equals(NamespaceTranslator.getOSGiNamespace(namespace)))
throw new Exception("Namespace conflict. Namespace not allowed: " + namespace);
capability.setName(NamespaceTranslator.getFelixNamespace(namespace));
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, String> directives = new HashMap<String, String>();
parseAttributesDirectives(reader, attributes, directives, CAPABILITY);
for (Map.Entry<String, Object> entry : attributes.entrySet())
{
if (BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) && BundleNamespace.BUNDLE_NAMESPACE.equals(entry.getKey()))
{
capability.addProperty(new FelixPropertyAdapter(Resource.SYMBOLIC_NAME, entry.getValue()));
continue;
}
if (BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) && BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE.equals(entry.getKey()))
{
capability.addProperty(new FelixPropertyAdapter(Resource.VERSION, entry.getValue()));
continue;
}
capability.addProperty(new FelixPropertyAdapter(NamespaceTranslator.getFelixNamespace(entry.getKey()), entry.getValue()));
}
for (Map.Entry<String, String> entry : directives.entrySet())
{
capability.addDirective(entry.getKey(), entry.getValue());
}
return capability;
}
private static void parseIdentityNamespace(XmlPullParser reader, ResourceImpl resource) throws Exception
{
Map<String, Object> attributes = new HashMap<String, Object>();
parseAttributesDirectives(reader, attributes, new HashMap<String, String>(), CAPABILITY);
// TODO need to cater for the singleton directive...
for (Map.Entry<String, Object> entry : attributes.entrySet())
{
if (IdentityNamespace.IDENTITY_NAMESPACE.equals(entry.getKey()))
resource.put(Resource.SYMBOLIC_NAME, entry.getValue());
else
resource.put(entry.getKey(), entry.getValue());
}
}
private static void parseContentNamespace(XmlPullParser reader, ResourceImpl resource, URI baseUri) throws Exception
{
Map<String, Object> attributes = new HashMap<String, Object>();
parseAttributesDirectives(reader, attributes, new HashMap<String, String>(), CAPABILITY);
for (Map.Entry<String, Object> entry : attributes.entrySet())
{
if (ContentNamespace.CONTENT_NAMESPACE.equals(entry.getKey()))
// TODO we should really check the SHA
continue;
else if (ContentNamespace.CAPABILITY_URL_ATTRIBUTE.equals(entry.getKey())) {
String value = (String) entry.getValue();
URI resourceUri = URI.create(value);
if (!resourceUri.isAbsolute()) {
resourceUri = URI.create(new StringBuilder(baseUri.toString()).append(value).toString());
}
resource.put(Resource.URI, resourceUri);
}
else
resource.put(entry.getKey(), entry.getValue());
}
}
private static void parseAttributesDirectives(XmlPullParser reader, Map<String, Object> attributes, Map<String, String> directives, String parentTag) throws XmlPullParserException, IOException
{
int event;
while ((event = reader.nextTag()) == XmlPullParser.START_TAG)
{
String element = reader.getName();
if (ATTRIBUTE.equals(element))
{
String name = reader.getAttributeValue(null, "name");
String type = reader.getAttributeValue(null, "type");
String value = reader.getAttributeValue(null, "value");
attributes.put(name, getTypedValue(type, value));
PullParser.sanityCheckEndElement(reader, reader.nextTag(), ATTRIBUTE);
}
else if (DIRECTIVE.equals(element))
{
String name = reader.getAttributeValue(null, "name");
String value = reader.getAttributeValue(null, "value");
directives.put(name, value);
PullParser.sanityCheckEndElement(reader, reader.nextTag(), DIRECTIVE);
}
else
{
PullParser.ignoreTag(reader);
}
}
PullParser.sanityCheckEndElement(reader, event, parentTag);
}
private static Object getTypedValue(String type, String value)
{
if (type == null)
return value;
type = type.trim();
if ("Version".equals(type))
return Version.parseVersion(value);
else if ("Long".equals(type))
return Long.parseLong(value);
else if ("Double".equals(type))
return Double.parseDouble(value);
else if ("List<String>".equals(type))
return parseStringList(value);
else if ("List<Version>".equals(type))
return parseVersionList(value);
else if ("List<Long>".equals(type))
return parseLongList(value);
else if ("List<Double>".equals(type))
return parseDoubleList(value);
return value;
}
private static List<String> parseStringList(String value)
{
List<String> l = new ArrayList<String>();
StringBuilder sb = new StringBuilder();
boolean escaped = false;
for (char c : value.toCharArray())
{
if (escaped)
{
sb.append(c);
escaped = false;
}
else
{
switch (c)
{
case '\\':
escaped = true;
break;
case ',':
l.add(sb.toString().trim());
sb.setLength(0);
break;
default:
sb.append(c);
}
}
}
if (sb.length() > 0)
l.add(sb.toString().trim());
return l;
}
private static List<Version> parseVersionList(String value)
{
List<Version> l = new ArrayList<Version>();
// Version strings cannot contain a comma, as it's not an allowed character in it anywhere
for (String v : value.split(","))
{
l.add(Version.parseVersion(v.trim()));
}
return l;
}
private static List<Long> parseLongList(String value)
{
List<Long> l = new ArrayList<Long>();
for (String x : value.split(","))
{
l.add(Long.parseLong(x.trim()));
}
return l;
}
private static List<Double> parseDoubleList(String value)
{
List<Double> l = new ArrayList<Double>();
for (String d : value.split(","))
{
l.add(Double.parseDouble(d.trim()));
}
return l;
}
private static Requirement parseRequirement(XmlPullParser reader) throws Exception
{
RequirementImpl requirement = new RequirementImpl();
String namespace = reader.getAttributeValue(null, NAMESPACE);
if (!namespace.equals(NamespaceTranslator.getOSGiNamespace(namespace)))
throw new Exception("Namespace conflict. Namespace not allowed: " + namespace);
requirement.setName(NamespaceTranslator.getFelixNamespace(namespace));
Map<String, Object> attributes = new HashMap<String, Object>();
Map<String, String> directives = new HashMap<String, String>();
parseAttributesDirectives(reader, attributes, directives, REQUIREMENT);
requirement.setAttributes(attributes);
String effective = directives.get("effective");
if (effective != null && !effective.equals("resolve")) {
return null;
}
String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
for (String ns : NamespaceTranslator.getTranslatedOSGiNamespaces())
{
if (BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) && BundleNamespace.BUNDLE_NAMESPACE.equals(ns))
{
filter = filter.replaceAll("[(][ ]*" + ns + "[ ]*=",
"(" + Resource.SYMBOLIC_NAME + "=");
}
else
filter = filter.replaceAll("[(][ ]*" + ns + "[ ]*=",
"(" + NamespaceTranslator.getFelixNamespace(ns) + "=");
}
requirement.setFilter(filter);
requirement.setMultiple(Namespace.CARDINALITY_MULTIPLE.equals(
directives.remove(Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE)));
requirement.setOptional(Namespace.RESOLUTION_OPTIONAL.equals(
directives.remove(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE)));
requirement.setDirectives(directives);
requirement.setExtend(false);
return requirement;
}
}