/** * 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.ext; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.aries.blueprint.ExtendedReferenceListMetadata; import org.apache.aries.blueprint.ParserContext; import org.apache.aries.blueprint.metadata.Builder; import org.apache.aries.blueprint.metadata.MutableBeanMetadata; import org.apache.aries.blueprint.metadata.MutableComponentMetadata; import org.apache.aries.blueprint.metadata.MutableReferenceMetadata; import org.apache.aries.blueprint.metadata.MutableServiceReferenceMetadata; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.osgi.service.blueprint.reflect.BeanMetadata; import org.osgi.service.blueprint.reflect.BeanProperty; import org.osgi.service.blueprint.reflect.CollectionMetadata; import org.osgi.service.blueprint.reflect.ComponentMetadata; import org.osgi.service.blueprint.reflect.MapEntry; import org.osgi.service.blueprint.reflect.Metadata; import org.osgi.service.blueprint.reflect.RefMetadata; import org.osgi.service.blueprint.reflect.ReferenceListMetadata; import org.osgi.service.blueprint.reflect.ReferenceMetadata; import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata; import org.osgi.service.blueprint.reflect.ValueMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Attr; 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; /** * A namespace handler for Aries blueprint extensions * * @version $Rev: 1071628 $, $Date: 2011-02-17 14:44:49 +0000 (Thu, 17 Feb 2011) $ */ public class ExtNamespaceHandler implements org.apache.aries.blueprint.NamespaceHandler { public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0"; public static final String BLUEPRINT_EXT_NAMESPACE_V1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"; public static final String BLUEPRINT_EXT_NAMESPACE_V1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"; public static final String PROPERTY_PLACEHOLDER_ELEMENT = "property-placeholder"; public static final String DEFAULT_PROPERTIES_ELEMENT = "default-properties"; public static final String PROPERTY_ELEMENT = "property"; public static final String VALUE_ELEMENT = "value"; public static final String LOCATION_ELEMENT = "location"; public static final String ID_ATTRIBUTE = "id"; public static final String PLACEHOLDER_PREFIX_ATTRIBUTE = "placeholder-prefix"; public static final String PLACEHOLDER_SUFFIX_ATTRIBUTE = "placeholder-suffix"; public static final String DEFAULTS_REF_ATTRIBUTE = "defaults-ref"; public static final String IGNORE_MISSING_LOCATIONS_ATTRIBUTE = "ignore-missing-locations"; public static final String SYSTEM_PROPERTIES_ATTRIBUTE = "system-properties"; public static final String SYSTEM_PROPERTIES_NEVER = "never"; public static final String SYSTEM_PROPERTIES_FALLBACK = "fallback"; public static final String SYSTEM_PROPERTIES_OVERRIDE = "override"; public static final String PROXY_METHOD_ATTRIBUTE = "proxy-method"; public static final String PROXY_METHOD_DEFAULT = "default"; public static final String PROXY_METHOD_CLASSES = "classes"; public static final String PROXY_METHOD_GREEDY = "greedy"; public static final String ROLE_ATTRIBUTE = "role"; public static final String ROLE_PROCESSOR = "processor"; public static final String FIELD_INJECTION_ATTRIBUTE = "field-injection"; public static final String DEFAULT_REFERENCE_BEAN = "default"; public static String FIELD_INJECTION_KEY = "field-injection"; public static String DEFAULT_BEAN_KEY = "default-bean"; public static String PROCESSOR_KEY = "processor"; public static String PROXY_METHOD_KEY = "proxy-method"; public static String RUNTIME_CLASS_KEY = "runtime-class"; private static final Logger LOGGER = LoggerFactory.getLogger(ExtNamespaceHandler.class); private int idCounter; public URL getSchemaLocation(String namespace) { if (BLUEPRINT_EXT_NAMESPACE_V1_0.equals(namespace)) { return getClass().getResource("blueprint-ext.xsd"); } else if (BLUEPRINT_EXT_NAMESPACE_V1_1.equals(namespace)) { return getClass().getResource("blueprint-ext-1.1.xsd"); } else { return null; } } public Set<Class> getManagedClasses() { return new HashSet<Class>(Arrays.asList( PropertyPlaceholder.class )); } public Metadata parse(Element element, ParserContext context) { LOGGER.debug("Parsing element {{}}{}", element.getNamespaceURI(), element.getLocalName()); if (nodeNameEquals(element, PROPERTY_PLACEHOLDER_ELEMENT)) { return parsePropertyPlaceholder(context, element); } else if (nodeNameEquals(element, "identity")) { NodeList list = element.getChildNodes(); for (int i=0; i<list.getLength(); i++) { Node n = list.item(i); if (n instanceof Element) { return context.parseElement(BeanMetadata.class, context.getEnclosingComponent(), (Element) n); } } return null; } else { throw new ComponentDefinitionException("Unsupported element: " + element.getNodeName()); } } public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) { if (node instanceof Attr && nodeNameEquals(node, PROXY_METHOD_ATTRIBUTE)) { return decorateProxyMethod(node, component, context); } else if (node instanceof Attr && nodeNameEquals(node, ROLE_ATTRIBUTE)) { return decorateRole(node, component, context); } else if (node instanceof Attr && nodeNameEquals(node, FIELD_INJECTION_ATTRIBUTE)) { return decorateFieldInjection(node, component, context); } else if (node instanceof Attr && nodeNameEquals(node, DEFAULT_REFERENCE_BEAN)) { return decorateDefaultBean(node, component, context); } else { throw new ComponentDefinitionException("Unsupported node: " + node.getNodeName()); } } private ComponentMetadata decorateDefaultBean(Node node, ComponentMetadata component, ParserContext context) { if (!(component instanceof ReferenceMetadata)) { throw new ComponentDefinitionException("Attribute " + node.getNodeName() + " can only be used on a <reference> element"); } if (!(component instanceof MutableReferenceMetadata)) { throw new ComponentDefinitionException("Expected an instanceof MutableReferenceMetadata"); } String value = ((Attr) node).getValue(); ((MutableReferenceMetadata<?>) component).addCustomData(ExtNamespaceHandler.class, DEFAULT_BEAN_KEY, value); return component; } private ComponentMetadata decorateFieldInjection(Node node, ComponentMetadata component, ParserContext context) { if (!(component instanceof BeanMetadata)) { throw new ComponentDefinitionException("Attribute " + node.getNodeName() + " can only be used on a <bean> element"); } if (!(component instanceof MutableBeanMetadata)) { throw new ComponentDefinitionException("Expected an instanceof MutableBeanMetadata"); } String value = ((Attr) node).getValue(); ((MutableBeanMetadata<?>) component).addCustomData(ExtNamespaceHandler.class, FIELD_INJECTION_KEY, "true".equals(value) || "1".equals(value)); return component; } private ComponentMetadata decorateRole(Node node, ComponentMetadata component, ParserContext context) { if (!(component instanceof BeanMetadata)) { throw new ComponentDefinitionException("Attribute " + node.getNodeName() + " can only be used on a <bean> element"); } if (!(component instanceof MutableBeanMetadata)) { throw new ComponentDefinitionException("Expected an instance of MutableBeanMetadata"); } boolean processor = false; String value = ((Attr) node).getValue(); String[] flags = value.trim().split(" "); for (String flag : flags) { if (ROLE_PROCESSOR.equals(flag)) { processor = true; } else { throw new ComponentDefinitionException("Unknown proxy method: " + flag); } } ((MutableBeanMetadata<?>) component).addCustomData(ExtNamespaceHandler.class, PROCESSOR_KEY, processor); return component; } private ComponentMetadata decorateProxyMethod(Node node, ComponentMetadata component, ParserContext context) { if (!(component instanceof ServiceReferenceMetadata)) { throw new ComponentDefinitionException("Attribute " + node.getNodeName() + " can only be used on a <reference> or <reference-list> element"); } if (!(component instanceof MutableServiceReferenceMetadata)) { throw new ComponentDefinitionException("Expected an instance of MutableServiceReferenceMetadata"); } int method = 0; String value = ((Attr) node).getValue(); String[] flags = value.trim().split(" "); for (String flag : flags) { if (PROXY_METHOD_DEFAULT.equals(flag)) { method += ExtendedReferenceListMetadata.PROXY_METHOD_DEFAULT; } else if (PROXY_METHOD_CLASSES.equals(flag)) { method += ExtendedReferenceListMetadata.PROXY_METHOD_CLASSES; } else if (PROXY_METHOD_GREEDY.equals(flag)) { method += ExtendedReferenceListMetadata.PROXY_METHOD_GREEDY; } else { throw new ComponentDefinitionException("Unknown proxy method: " + flag); } } if ((method & ExtendedReferenceListMetadata.PROXY_METHOD_GREEDY) != 0 && !(component instanceof ReferenceListMetadata)) { throw new ComponentDefinitionException("Greedy proxying is only available for <reference-list> element"); } ((MutableServiceReferenceMetadata<?,?>) component).addCustomData(ExtNamespaceHandler.class, PROXY_METHOD_KEY, method); return component; } private Metadata parsePropertyPlaceholder(ParserContext context, Element element) { Builder builder = context.getMetadataBuilder(); MutableBeanMetadata<?> metadata = builder.newBean(); metadata.addCustomData(ExtNamespaceHandler.class, PROCESSOR_KEY, true); metadata.addCustomData(ExtNamespaceHandler.class, RUNTIME_CLASS_KEY, PropertyPlaceholder.class); metadata.id(getId(context, element)) .scope(BeanMetadata.SCOPE_SINGLETON) .initMethod("init"); String prefix = element.hasAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE) ? element.getAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE) : "${"; metadata.addProperty(builder.newBeanProperty().name("placeholderPrefix").value(createValue(context, prefix))); String suffix = element.hasAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE) ? element.getAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE) : "}"; metadata.addProperty(builder.newBeanProperty().name("placeholderSuffix").value(createValue(context, suffix))); String defaultsRef = element.hasAttribute(DEFAULTS_REF_ATTRIBUTE) ? element.getAttribute(DEFAULTS_REF_ATTRIBUTE) : null; if (defaultsRef != null) { metadata.addProperty(builder.newBeanProperty().name("defaultProperties").value(createRef(context, defaultsRef))); } String ignoreMissingLocations = element.hasAttribute(IGNORE_MISSING_LOCATIONS_ATTRIBUTE) ? element.getAttribute(IGNORE_MISSING_LOCATIONS_ATTRIBUTE) : null; if (ignoreMissingLocations != null) { metadata.addProperty(builder.newBeanProperty().name("ignoreMissingLocations").value(createValue(context, ignoreMissingLocations))); } String systemProperties = element.hasAttribute(SYSTEM_PROPERTIES_ATTRIBUTE) ? element.getAttribute(SYSTEM_PROPERTIES_ATTRIBUTE) : null; if (systemProperties != null) { metadata.addProperty(builder.newBeanProperty().name("systemProperties").value(createValue(context, systemProperties))); } // Parse elements List<String> locations = new ArrayList<String>(); NodeList nl = element.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element e = (Element) node; if (BLUEPRINT_EXT_NAMESPACE_V1_0.equals(e.getNamespaceURI())) { if (nodeNameEquals(e, DEFAULT_PROPERTIES_ELEMENT)) { if (defaultsRef != null) { throw new ComponentDefinitionException("Only one of " + DEFAULTS_REF_ATTRIBUTE + " attribute or " + DEFAULT_PROPERTIES_ELEMENT + " element is allowed"); } Metadata props = parseDefaultProperties(context, metadata, e); metadata.addProperty(builder.newBeanProperty().name("defaultProperties").value(props)); } else if (nodeNameEquals(e, LOCATION_ELEMENT)) { locations.add(getTextValue(e)); } } } } if (!locations.isEmpty()) { metadata.addProperty(builder.newBeanProperty().name("locations").value(createList(context, locations))); } PlaceholdersUtils.validatePlaceholder(metadata, context.getComponentDefinitionRegistry()); return metadata; } private Metadata parseDefaultProperties(ParserContext context, MutableBeanMetadata<?> enclosingComponent, Element element) { NodeList nl = element.getChildNodes(); List<MapEntry> entries = new ArrayList<MapEntry>(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element e = (Element) node; if (BLUEPRINT_EXT_NAMESPACE_V1_0.equals(e.getNamespaceURI())) { if (nodeNameEquals(e, PROPERTY_ELEMENT)) { BeanProperty prop = context.parseElement(BeanProperty.class, enclosingComponent, e); entries.add( context.getMetadataBuilder().newMapEntry() .key(createValue(context, prop.getName(), String.class.getName())) .value(prop.getValue())); } } } } return context.getMetadataBuilder().newMap().entries(entries); } public String getId(ParserContext context, Element element) { if (element.hasAttribute(ID_ATTRIBUTE)) { return element.getAttribute(ID_ATTRIBUTE); } else { return generateId(context); } } public void generateIdIfNeeded(ParserContext context, MutableComponentMetadata<?, ?> metadata) { if (metadata.getId() == null) { metadata.id(generateId(context)); } } private String generateId(ParserContext context) { String id; do { id = ".ext-" + ++idCounter; } while (context.getComponentDefinitionRegistry().containsComponentDefinition(id)); return id; } private static ValueMetadata createValue(ParserContext context, String value) { return createValue(context, value, null); } private static ValueMetadata createValue(ParserContext context, String value, String type) { return context.getMetadataBuilder().newValue().stringValue(value).type(type); } private static RefMetadata createRef(ParserContext context, String value) { return context.getMetadataBuilder().newRef().componentId(value); } private static CollectionMetadata createList(ParserContext context, List<String> list) { List<ValueMetadata> values = new ArrayList<ValueMetadata>(); for (String v : list) { values.add(createValue(context, v, String.class.getName())); } return context.getMetadataBuilder().newCollection() .collectionClass(List.class) .valueType(String.class.getName()) .values(values); } private static String getTextValue(Element element) { StringBuffer value = new StringBuffer(); NodeList nl = element.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node item = nl.item(i); if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) { value.append(item.getNodeValue()); } } return value.toString(); } private static boolean nodeNameEquals(Node node, String name) { return (name.equals(node.getNodeName()) || name.equals(node.getLocalName())); } public static boolean isBlueprintNamespace(String ns) { return BLUEPRINT_NAMESPACE.equals(ns); } }