/**
* 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.aries.blueprint.spring.extender;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.aries.blueprint.NamespaceHandler;
import org.apache.aries.blueprint.ParserContext;
import org.apache.aries.blueprint.mutable.MutableRefMetadata;
import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
import org.apache.aries.blueprint.mutable.MutableServiceMetadata;
import org.apache.aries.blueprint.mutable.MutableValueMetadata;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.blueprint.reflect.Metadata;
import org.osgi.service.blueprint.reflect.NonNullMetadata;
import org.osgi.service.blueprint.reflect.ReferenceMetadata;
import org.osgi.service.blueprint.reflect.ServiceMetadata;
import org.osgi.service.blueprint.reflect.Target;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class SpringOsgiNamespaceHandler implements NamespaceHandler {
public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0";
public static final String SPRING_NAMESPACE = "http://www.springframework.org/schema/beans";
public static final String BEAN_ELEMENT = "bean";
public static final String BEAN_NAME_ELEMENT = "bean-name";
public static final String FILTER_ATTRIBUTE = "filter";
public static final String INTERFACE_ATTRIBUTE = "interface";
public static final String TIMEOUT_ATTRIBUTE = "timeout";
public static final String DEPENDS_ON_ATTRIBUTE = "depends-on";
public static final String CARDINALITY_ATTRIBUTE = "cardinality";
public static final String LISTENER_ELEMENT = "listener";
public static final String REF_ATTRIBUTE = "ref";
public static final String BIND_METHOD_ATTRIBUTE = "bind-method";
public static final String UNBIND_METHOD_ATTRIBUTE = "unbind-method";
public static final String ID_ATTRIBUTE = "id";
public static final String CARDINALITY_0_1 = "0..1";
public static final String VALUE_ATTRIBUTE = "value";
public static final String VALUE_REF_ATTRIBUTE = "value-ref";
public static final String KEY_ATTRIBUTE = "key";
public static final String KEY_REF_ATTRIBUTE = "key-ref";
public static final String ENTRY_ELEMENT = "entry";
public static final String SERVICE_PROPERTIES_ELEMENT = "service-properties";
public static final String REGISTRATION_LISTENER_ELEMENT = "registration-listener";
public static final String INTERFACES_ELEMENT = "interfaces";
public static final String VALUE_ELEMENT = "value";
public static final String AUTO_EXPORT_ATTRIBUTE = "auto-export";
public static final String AUTO_EXPORT_INTERFACES = "interfaces";
public static final String AUTO_EXPORT_CLASS_HIERARCHY = "class-hierarchy";
public static final String AUTO_EXPORT_ALL_CLASSES = "all-classes";
public static final String RANKING_ATTRIBUTE = "ranking";
public static final String REFERENCE_ELEMENT = "reference";
public static final String SERVICE_ELEMENT = "service";
public static final String BUNDLE_ELEMENT = "bundle";
public static final String SET_ELEMENT = "set";
public static final String LIST_ELEMENT = "list";
public static final int DEFAULT_TIMEOUT = 300000;
public static final String REGISTRATION_METHOD_ATTRIBUTE = "registration-method";
public static final String UNREGISTRATION_METHOD_ATTRIBUTE = "unregistration-method";
private int idCounter;
@Override
public URL getSchemaLocation(String namespace) {
if (namespace.startsWith("http://www.springframework.org/schema/osgi/spring-osgi")) {
String sub = namespace.substring("http://www.springframework.org/schema/osgi/".length());
if ("spring-osgi.xsd".equals(sub)) {
sub = "spring-osgi-1.2.xsd";
}
return getClass().getResource(sub);
}
return null;
}
@Override
public Set<Class> getManagedClasses() {
return null;
}
@Override
public Metadata parse(Element element, ParserContext context) {
if (REFERENCE_ELEMENT.equals(element.getLocalName())) {
return parseReference(element, context);
}
else if (SERVICE_ELEMENT.equals(element.getLocalName())) {
return parseService(element, context);
}
else if (BUNDLE_ELEMENT.equals(element.getLocalName())) {
return parseBundle(element, context);
}
else if (SET_ELEMENT.equals(element.getLocalName())) {
return parseSet(element, context);
}
else if (LIST_ELEMENT.equals(element.getLocalName())) {
return parseList(element, context);
}
else {
throw new UnsupportedOperationException();
}
}
private Metadata parseBundle(Element element, ParserContext context) {
throw new UnsupportedOperationException();
}
private Metadata parseList(Element element, ParserContext context) {
// TODO: support list
throw new UnsupportedOperationException();
}
private Metadata parseSet(Element element, ParserContext context) {
// TODO: support set
throw new UnsupportedOperationException();
}
private Metadata parseService(Element element, ParserContext context) {
MutableServiceMetadata metadata = context.createMetadata(MutableServiceMetadata.class);
// Parse attributes
if (element.hasAttribute(ID_ATTRIBUTE)) {
metadata.setId(element.getAttribute(ID_ATTRIBUTE));
} else {
metadata.setId(generateId(context));
}
if (nonEmpty(element.getAttribute(REF_ATTRIBUTE)) != null) {
MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
ref.setComponentId(element.getAttribute(REF_ATTRIBUTE));
metadata.setServiceComponent(ref);
}
metadata.setRanking(nonEmpty(element.getAttribute(RANKING_ATTRIBUTE)) != null
? Integer.parseInt(element.getAttribute(RANKING_ATTRIBUTE))
: 0);
String itf = nonEmpty(element.getAttribute(INTERFACE_ATTRIBUTE));
if (itf != null) {
metadata.addInterface(itf);
}
String[] dependsOn = StringUtils.tokenizeToStringArray(nonEmpty(element.getAttribute(DEPENDS_ON_ATTRIBUTE)), ",; ");
metadata.setDependsOn(dependsOn != null ? Arrays.asList(dependsOn) : null);
String autoExp = nonEmpty(element.getAttribute(AUTO_EXPORT_ATTRIBUTE));
if (AUTO_EXPORT_INTERFACES.equals(autoExp)) {
metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_INTERFACES);
} else if (AUTO_EXPORT_CLASS_HIERARCHY.equals(autoExp)) {
metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY);
} else if (AUTO_EXPORT_ALL_CLASSES.equals(autoExp)) {
metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
} else {
metadata.setAutoExport(ServiceMetadata.AUTO_EXPORT_DISABLED);
}
// TODO: @context-class-loader
// Parse child elements
for (Element child : getChildren(element)) {
if (element.getNamespaceURI().equals(child.getNamespaceURI())) {
if (INTERFACES_ELEMENT.equals(child.getLocalName())) {
List<String> itfs = parseInterfaces(child);
for (String intf : itfs) {
metadata.addInterface(intf);
}
}
else if (REGISTRATION_LISTENER_ELEMENT.equals(child.getLocalName())) {
String regMethod = nonEmpty(child.getAttribute(REGISTRATION_METHOD_ATTRIBUTE));
String unregMethod = nonEmpty(child.getAttribute(UNREGISTRATION_METHOD_ATTRIBUTE));
String refStr = nonEmpty(child.getAttribute(REF_ATTRIBUTE));
Target listenerComponent = null;
if (refStr != null) {
MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
ref.setComponentId(refStr);
listenerComponent = ref;
}
for (Element cchild : getChildren(child)) {
if (listenerComponent != null) {
throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
}
listenerComponent = parseInlinedTarget(context, metadata, cchild);
}
if (listenerComponent == null) {
throw new IllegalArgumentException("Missing @ref attribute or inlined bean definition element");
}
metadata.addRegistrationListener(listenerComponent, regMethod, unregMethod);
}
else if (SERVICE_PROPERTIES_ELEMENT.equals(child.getLocalName())) {
// TODO: @key-type
for (Element e : getChildren(child)) {
if (ENTRY_ELEMENT.equals(e.getLocalName())) {
NonNullMetadata key;
Metadata val;
boolean hasKeyAttribute = e.hasAttribute(KEY_ATTRIBUTE);
boolean hasKeyRefAttribute = e.hasAttribute(KEY_REF_ATTRIBUTE);
if (hasKeyRefAttribute && !hasKeyAttribute) {
MutableRefMetadata r = context.createMetadata(MutableRefMetadata.class);
r.setComponentId(e.getAttribute(KEY_REF_ATTRIBUTE));
key = r;
} else if (hasKeyAttribute && !hasKeyRefAttribute) {
MutableValueMetadata v = context.createMetadata(MutableValueMetadata.class);
v.setStringValue(e.getAttribute(KEY_ATTRIBUTE));
key = v;
} else {
throw new IllegalStateException("Either key or key-ref must be specified");
}
// TODO: support key
boolean hasValAttribute = e.hasAttribute(VALUE_ATTRIBUTE);
boolean hasValRefAttribute = e.hasAttribute(VALUE_REF_ATTRIBUTE);
if (hasValRefAttribute && !hasValAttribute) {
MutableRefMetadata r = context.createMetadata(MutableRefMetadata.class);
r.setComponentId(e.getAttribute(VALUE_REF_ATTRIBUTE));
val = r;
} else if (hasValAttribute && !hasValRefAttribute) {
MutableValueMetadata v = context.createMetadata(MutableValueMetadata.class);
v.setStringValue(e.getAttribute(VALUE_ATTRIBUTE));
val = v;
} else {
throw new IllegalStateException("Either val or val-ref must be specified");
}
// TODO: support children elements ?
metadata.addServiceProperty(key, val);
}
}
}
}
else if (BLUEPRINT_NAMESPACE.equals(child.getNamespaceURI())
&& BEAN_ELEMENT.equals(child.getLocalName())) {
if (metadata.getServiceComponent() != null) {
throw new IllegalArgumentException("Only one of @ref attribute and bean element is allowed");
}
Target bean = context.parseElement(BeanMetadata.class, metadata, child);
metadata.setServiceComponent(bean);
}
else {
if (metadata.getServiceComponent() != null) {
throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
}
NamespaceHandler handler = context.getNamespaceHandler(URI.create(child.getNamespaceURI()));
if (handler == null) {
throw new IllegalStateException("No NamespaceHandler found for " + child.getNamespaceURI());
}
Metadata md = handler.parse(child, context);
if (!(md instanceof Target)) {
throw new IllegalStateException("NamespaceHandler did not return a Target instance but " + md);
}
metadata.setServiceComponent((Target) md);
}
}
return metadata;
}
private Metadata parseReference(Element element, ParserContext context) {
MutableReferenceMetadata metadata = context.createMetadata(MutableReferenceMetadata.class);
// Parse attributes
if (element.hasAttribute(ID_ATTRIBUTE)) {
metadata.setId(element.getAttribute(ID_ATTRIBUTE));
} else {
metadata.setId(generateId(context));
}
metadata.setAvailability(CARDINALITY_0_1.equals(element.getAttribute(CARDINALITY_ATTRIBUTE))
? ReferenceMetadata.AVAILABILITY_OPTIONAL
: ReferenceMetadata.AVAILABILITY_MANDATORY);
metadata.setTimeout(getLong(element.getAttribute(TIMEOUT_ATTRIBUTE), DEFAULT_TIMEOUT));
metadata.setInterface(element.getAttribute(INTERFACE_ATTRIBUTE));
metadata.setFilter(element.getAttribute(FILTER_ATTRIBUTE));
String[] dependsOn = StringUtils.tokenizeToStringArray(element.getAttribute(DEPENDS_ON_ATTRIBUTE), ",; ");
metadata.setDependsOn(dependsOn != null ? Arrays.asList(dependsOn) : null);
metadata.setComponentName(element.getAttribute(BEAN_NAME_ELEMENT));
// TODO: @context-class-loader
// Parse child elements
for (Element child : getChildren(element)) {
if (element.getNamespaceURI().equals(child.getNamespaceURI())) {
if (INTERFACES_ELEMENT.equals(child.getLocalName())) {
List<String> itfs = parseInterfaces(child);
metadata.setExtraInterfaces(itfs);
}
else if (LISTENER_ELEMENT.equals(child.getLocalName())) {
String bindMethod = nonEmpty(child.getAttribute(BIND_METHOD_ATTRIBUTE));
String unbindMethod = nonEmpty(child.getAttribute(UNBIND_METHOD_ATTRIBUTE));
String refStr = nonEmpty(child.getAttribute(REF_ATTRIBUTE));
Target listenerComponent = null;
if (refStr != null) {
MutableRefMetadata ref = context.createMetadata(MutableRefMetadata.class);
ref.setComponentId(refStr);
listenerComponent = ref;
}
for (Element cchild : getChildren(child)) {
if (listenerComponent != null) {
throw new IllegalArgumentException("Only one of @ref attribute or inlined bean definition element is allowed");
}
listenerComponent = parseInlinedTarget(context, metadata, cchild);
}
if (listenerComponent == null) {
throw new IllegalArgumentException("Missing @ref attribute or inlined bean definition element");
}
metadata.addServiceListener(listenerComponent, bindMethod, unbindMethod);
}
}
else {
throw new UnsupportedOperationException("Custom namespaces not supported");
}
}
return metadata;
}
private Target parseInlinedTarget(ParserContext context, ComponentMetadata metadata, Element element) {
Target listenerComponent;
if (BLUEPRINT_NAMESPACE.equals(element.getNamespaceURI())
&& BEAN_ELEMENT.equals(element.getLocalName())) {
listenerComponent = context.parseElement(BeanMetadata.class, metadata, element);
}
else {
NamespaceHandler handler = context.getNamespaceHandler(URI.create(element.getNamespaceURI()));
if (handler == null) {
throw new IllegalStateException("No NamespaceHandler found for " + element.getNamespaceURI());
}
Metadata md = handler.parse(element, context);
if (!(md instanceof Target)) {
throw new IllegalStateException("NamespaceHandler did not return a Target instance but " + md);
}
listenerComponent = (Target) md;
}
return listenerComponent;
}
private List<String> parseInterfaces(Element element) {
List<String> extra = new ArrayList<String>();
for (Element e : getChildren(element)) {
if (VALUE_ELEMENT.equals(e.getLocalName())) {
extra.add(getTextValue(e));
} else {
// The schema support all kind of children for a list type
// The type for the spring property is converted to a Class[] array
// TODO: support other elements ?
throw new UnsupportedOperationException("Unsupported child: " + element.getLocalName());
}
}
return extra;
}
private String nonEmpty(String ref) {
return ref != null && ref.isEmpty() ? null : ref;
}
private String generateId(ParserContext context) {
String id;
do {
id = ".spring-osgi-" + ++idCounter;
} while (context.getComponentDefinitionRegistry().containsComponentDefinition(id));
return id;
}
@Override
public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
return component;
}
private List<Element> getChildren(Element element) {
List<Element> children = new ArrayList<Element>();
for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
if (child instanceof Element) {
children.add((Element) child);
}
}
return children;
}
private long getLong(String str, long def) {
if (str == null || str.isEmpty()) {
return def;
} else {
return Long.parseLong(str);
}
}
public static String getTextValue(Element valueEle) {
Assert.notNull(valueEle, "Element must not be null");
StringBuilder sb = new StringBuilder();
NodeList nl = valueEle.getChildNodes();
for(int i = 0, l = nl.getLength(); i < l; ++i) {
Node item = nl.item(i);
if(item instanceof CharacterData && !(item instanceof Comment) || item instanceof EntityReference) {
sb.append(item.getNodeValue());
}
}
return sb.toString();
}
}