Add decorating "type" attr extension for service refs 82/36482/21
authorTom Pantelis <tpanteli@brocade.com>
Sun, 20 Mar 2016 04:06:08 +0000 (00:06 -0400)
committerTom Pantelis <tpanteli@brocade.com>
Thu, 7 Apr 2016 18:39:49 +0000 (18:39 +0000)
Added an attribute extension to the <service> element that adds an
OSGi service property of the form "type=<value>". This allows providers
of a service to advertise the type of the service to distinguish it from
other services provided for the same interface.

Conversely, the extension can also be used in <reference> elements to allow
consumers of the service to specify which service implementation they
want.

As a convention, the "default" type should be used for the default
implementation of the service with other types being specialized
implementations. This allows consumers to obtain whichever
implementation the provider deems as the default without having to
explicitly know it and allows the provider to switch to a new
implementation without requiring all consumers to change their
referenced type.

If a consumer doesn't specify the "type" attribute, it may be ambiguous
as to which service is obtained. Rather than requiring that the "type"
attribute be specfied for every <reference> element, I added an attribute
extension attribute, "use-default-for-reference-types", to the <blueprint>
element that automatically adds a filter to all <reference> elements where
the "type" property is either not set or set to "default" if the "type"
attribute isn't explicitly specified. This ensures the default implementation
is imported if there are other implementations advertised with other types.

Change-Id: Ie61bb45da1c7539732cd31ab0f8130233c9696fc
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ComponentProcessor.java
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java
opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd

index ed87e70..bae06b1 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.blueprint.ext;
 
+import com.google.common.base.Strings;
 import java.util.ArrayList;
 import java.util.Dictionary;
 import java.util.Hashtable;
@@ -15,6 +16,7 @@ import org.apache.aries.blueprint.ComponentDefinitionRegistry;
 import org.apache.aries.blueprint.ComponentDefinitionRegistryProcessor;
 import org.apache.aries.blueprint.ext.AbstractPropertyPlaceholder;
 import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
+import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata;
 import org.apache.aries.util.AriesFrameworkUtil;
 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
 import org.osgi.framework.Bundle;
@@ -41,6 +43,7 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
     private Bundle bundle;
     private BlueprintContainerRestartService blueprintContainerRestartService;
     private boolean restartDependentsOnUpdates;
+    private boolean useDefaultForReferenceTypes;
 
     public void setBundle(Bundle bundle) {
         this.bundle = bundle;
@@ -54,6 +57,10 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
         this.restartDependentsOnUpdates = restartDependentsOnUpdates;
     }
 
+    public void setUseDefaultForReferenceTypes(boolean useDefaultForReferenceTypes) {
+        this.useDefaultForReferenceTypes = useDefaultForReferenceTypes;
+    }
+
     public void destroy() {
         for(ServiceRegistration<?> reg: managedServiceRegs) {
             AriesFrameworkUtil.safeUnregisterService(reg);
@@ -62,20 +69,42 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
 
     @Override
     public void process(ComponentDefinitionRegistry registry) {
-        LOG.debug("{}: In process", bundle.getSymbolicName());
+        LOG.debug("{}: In process",  logName());
 
         for(String name : registry.getComponentDefinitionNames()) {
             ComponentMetadata component = registry.getComponentDefinition(name);
             if(component instanceof MutableBeanMetadata) {
                 processMutableBeanMetadata((MutableBeanMetadata)component);
+            } else if(component instanceof MutableServiceReferenceMetadata) {
+                processServiceReferenceMetadata((MutableServiceReferenceMetadata)component);
             }
         }
     }
 
+    private void processServiceReferenceMetadata(MutableServiceReferenceMetadata serviceRef) {
+        if(!useDefaultForReferenceTypes) {
+            return;
+        }
+
+        String filter = serviceRef.getFilter();
+        String extFilter = serviceRef.getExtendedFilter() == null ? null :
+            serviceRef.getExtendedFilter().getStringValue();
+
+        LOG.debug("{}: processServiceReferenceMetadata for {}, filter: {}, ext filter: {}", logName(),
+                serviceRef.getId(), filter, extFilter);
+
+        if(Strings.isNullOrEmpty(filter) && Strings.isNullOrEmpty(extFilter)) {
+            serviceRef.setFilter("(|(type=default)(!(type=*)))");
+
+            LOG.debug("{}: processServiceReferenceMetadata for {} set filter to {}", logName(),
+                    serviceRef.getId(), serviceRef.getFilter());
+        }
+    }
+
     private void processMutableBeanMetadata(MutableBeanMetadata bean) {
         if(restartDependentsOnUpdates && bean.getRuntimeClass() != null &&
                 AbstractPropertyPlaceholder.class.isAssignableFrom(bean.getRuntimeClass())) {
-            LOG.debug("{}: Found PropertyPlaceholder bean: {}, runtime {}", bundle.getSymbolicName(), bean.getId(),
+            LOG.debug("{}: Found PropertyPlaceholder bean: {}, runtime {}", logName(), bean.getId(),
                     bean.getRuntimeClass());
 
             for(BeanProperty prop: bean.getProperties()) {
@@ -83,13 +112,13 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
                     if(prop.getValue() instanceof ValueMetadata) {
                         ValueMetadata persistentId = (ValueMetadata)prop.getValue();
 
-                        LOG.debug("{}: Found {} property, value : {}", bundle.getSymbolicName(),
+                        LOG.debug("{}: Found {} property, value : {}", logName(),
                                 CM_PERSISTENT_ID_PROPERTY, persistentId.getStringValue());
 
                         registerManagedService(persistentId.getStringValue());
                     } else {
                         LOG.debug("{}: {} property metadata {} is not instanceof ValueMetadata",
-                                bundle.getSymbolicName(), CM_PERSISTENT_ID_PROPERTY, prop.getValue());
+                                logName(), CM_PERSISTENT_ID_PROPERTY, prop.getValue());
                     }
 
                     break;
@@ -107,7 +136,7 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
             @Override
             public void updated(Dictionary<String, ?> properties) {
                 LOG.debug("{}: ManagedService updated for persistentId {}, properties: {}, initialUpdate: {}",
-                        bundle.getSymbolicName(), persistentId, properties, initialUpdate);
+                        logName(), persistentId, properties, initialUpdate);
 
                 // The first update occurs when the service is registered so ignore it as we want subsequent
                 // updates when it changes. The ConfigAdmin will send an update even if the cfg file doesn't
@@ -127,4 +156,8 @@ public class ComponentProcessor implements ComponentDefinitionRegistryProcessor
         managedServiceRegs.add(bundle.getBundleContext().registerService(ManagedService.class.getName(),
                 managedService, props));
     }
+
+    private String logName() {
+        return bundle.getSymbolicName();
+    }
 }
index 89bea93..0b36126 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.blueprint.ext;
 
+import com.google.common.base.Strings;
 import java.net.URL;
 import java.util.Collections;
 import java.util.Set;
@@ -16,6 +17,8 @@ import org.apache.aries.blueprint.ParserContext;
 import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
 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.MutableServiceReferenceMetadata;
 import org.apache.aries.blueprint.mutable.MutableValueMetadata;
 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
 import org.osgi.service.blueprint.container.ComponentDefinitionException;
@@ -24,6 +27,8 @@ import org.osgi.service.blueprint.reflect.ComponentMetadata;
 import org.osgi.service.blueprint.reflect.Metadata;
 import org.osgi.service.blueprint.reflect.RefMetadata;
 import org.osgi.service.blueprint.reflect.ReferenceMetadata;
+import org.osgi.service.blueprint.reflect.ServiceMetadata;
+import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
 import org.osgi.service.blueprint.reflect.ValueMetadata;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,6 +47,8 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler {
     private static final Logger LOG = LoggerFactory.getLogger(OpendaylightNamespaceHandler.class);
     private static final String COMPONENT_PROCESSOR_NAME = ComponentProcessor.class.getName();
     private static final String RESTART_DEPENDENTS_ON_UPDATES = "restart-dependents-on-updates";
+    private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types";
+    private static final String TYPE_ATTR = "type";
 
     @SuppressWarnings("rawtypes")
     @Override
@@ -71,6 +78,17 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler {
         if(node instanceof Attr) {
             if (nodeNameEquals(node, RESTART_DEPENDENTS_ON_UPDATES)) {
                 return decorateRestartDependentsOnUpdates((Attr)node, component, context);
+            } else if (nodeNameEquals(node, USE_DEFAULT_FOR_REFERENCE_TYPES)) {
+                return decorateUseDefaultForReferenceTypes((Attr)node, component, context);
+            } else if (nodeNameEquals(node, TYPE_ATTR)) {
+                if(component instanceof ServiceReferenceMetadata) {
+                    return decorateServiceReferenceType((Attr)node, component, context);
+                } else if(component instanceof ServiceMetadata) {
+                    return decorateServiceType((Attr)node, component, context);
+                }
+
+                throw new ComponentDefinitionException("Attribute " + node.getNodeName() +
+                        " can only be used on a <reference>, <reference-list> or <service> element");
             }
 
             throw new ComponentDefinitionException("Unsupported attribute: " + node.getNodeName());
@@ -79,21 +97,75 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler {
         }
     }
 
+    private ComponentMetadata decorateServiceType(Attr attr, ComponentMetadata component, ParserContext context) {
+        if (!(component instanceof MutableServiceMetadata)) {
+            throw new ComponentDefinitionException("Expected an instanceof MutableServiceMetadata");
+        }
+
+        MutableServiceMetadata service = (MutableServiceMetadata)component;
+
+        LOG.debug("decorateServiceType for {} - adding type property {}", service.getId(), attr.getValue());
+
+        service.addServiceProperty(createValue(context, TYPE_ATTR), createValue(context, attr.getValue()));
+        return component;
+    }
+
+    private ComponentMetadata decorateServiceReferenceType(Attr attr, ComponentMetadata component, ParserContext context) {
+        if (!(component instanceof MutableServiceReferenceMetadata)) {
+            throw new ComponentDefinitionException("Expected an instanceof MutableServiceReferenceMetadata");
+        }
+
+        // We don't actually need the ComponentProcessor for augmenting the OSGi filter here but we create it
+        // to workaround an issue in Aries where it doesn't use the extended filter unless there's a
+        // Processor or ComponentDefinitionRegistryProcessor registered. This may actually be working as
+        // designed in Aries b/c the extended filter was really added to allow the OSGi filter to be
+        // substituted by a variable via the "cm:property-placeholder" processor. If so, it's a bit funky
+        // but as long as there's at least one processor registered, it correctly uses the extended filter.
+        registerComponentProcessor(context);
+
+        MutableServiceReferenceMetadata serviceRef = (MutableServiceReferenceMetadata)component;
+        String oldFilter = serviceRef.getExtendedFilter() == null ? null :
+            serviceRef.getExtendedFilter().getStringValue();
+
+        String filter;
+        if(Strings.isNullOrEmpty(oldFilter)) {
+            filter = String.format("(type=%s)", attr.getValue());
+        } else {
+            filter = String.format("(&(%s)(type=%s))", oldFilter, attr.getValue());
+        }
+
+        LOG.debug("decorateServiceReferenceType for {} with type {}, old filter: {}, new filter: {}",
+                serviceRef.getId(), attr.getValue(), oldFilter, filter);
+
+        serviceRef.setExtendedFilter(createValue(context, filter));
+        return component;
+    }
+
     private static ComponentMetadata decorateRestartDependentsOnUpdates(Attr attr, ComponentMetadata component,
             ParserContext context) {
+        return enableComponentProcessorProperty(attr, component, context, "restartDependentsOnUpdates");
+    }
+
+    private static ComponentMetadata decorateUseDefaultForReferenceTypes(Attr attr, ComponentMetadata component,
+            ParserContext context) {
+        return enableComponentProcessorProperty(attr, component, context, "useDefaultForReferenceTypes");
+    }
+
+    private static ComponentMetadata enableComponentProcessorProperty(Attr attr, ComponentMetadata component,
+            ParserContext context, String propertyName) {
         if(component != null) {
             throw new ComponentDefinitionException("Attribute " + attr.getNodeName() +
                     " can only be used on the root <blueprint> element");
         }
 
-        LOG.debug("decorateRestartDependentsOnUpdates: {}", attr.getValue());
+        LOG.debug("{}: {}", propertyName, attr.getValue());
 
         if(!Boolean.TRUE.equals(Boolean.valueOf(attr.getValue()))) {
             return component;
         }
 
         MutableBeanMetadata metadata = registerComponentProcessor(context);
-        metadata.addProperty("restartDependentsOnUpdates", createValue(context, "true"));
+        metadata.addProperty(propertyName, createValue(context, "true"));
 
         return component;
     }
index f83434f..088e2fc 100644 (file)
@@ -6,5 +6,7 @@
 <!-- <xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0"/> -->
 
   <xsd:attribute name="restart-dependents-on-updates" type="xsd:boolean"/>
+  <xsd:attribute name="use-default-for-reference-types" type="xsd:boolean"/>
+  <xsd:attribute name="type" type="xsd:string"/>
 
 </xsd:schema>
\ No newline at end of file

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.