Wait for RPCService registered in RpcServiceMetadata 71/39971/2
authorTom Pantelis <tpanteli@brocade.com>
Tue, 7 Jun 2016 18:04:50 +0000 (14:04 -0400)
committerTom Pantelis <tpanteli@brocade.com>
Fri, 10 Jun 2016 03:07:42 +0000 (03:07 +0000)
When obtaining an RpcService via the RpcProviderRegistry, the returned
instance is actually a proxy and the underlying DOM service instance may
not be registered yet. So if the caller tries to invoke an RPC it will
fail due to no implementation available. This is seen with the
ToasterTest with the switch to blueprint.

To alleviate I modified the RpcServiceMetadata to wait (asycnhronously)
for the underlying DOM RPC service implementation by registering a
DOMRpcAvailabilityListener with the DOMRpcService. In the callback, once
one of the RPCs is seen then it notifies the blueprint container that
its dependencies are satisfied so it can proceed.

Change-Id: I9f35afdd69ad069654a895239654a2ddc1ce1ee0
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java [new file with mode: 0644]
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java
opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java

diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java
new file mode 100644 (file)
index 0000000..bdcc1ee
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.blueprint.ext;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import org.apache.aries.blueprint.di.AbstractRecipe;
+import org.apache.aries.blueprint.di.ExecutionContext;
+import org.apache.aries.blueprint.di.Recipe;
+import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
+import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.osgi.service.blueprint.container.ComponentDefinitionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Thomas Pantelis
+ */
+abstract class AbstractDependentComponentFactoryMetadata implements DependentComponentFactoryMetadata {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final String id;
+    private final AtomicBoolean started = new AtomicBoolean();
+    private final AtomicBoolean satisfied = new AtomicBoolean();
+    private final List<StaticServiceReferenceRecipe> serviceRecipes = new ArrayList<>();
+    private volatile ExtendedBlueprintContainer container;
+    private volatile SatisfactionCallback satisfactionCallback;
+    private volatile String failureMessage;
+    private volatile String dependendencyDesc;
+
+    protected AbstractDependentComponentFactoryMetadata(String id) {
+        this.id = Preconditions.checkNotNull(id);
+    }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public int getActivation() {
+        return ACTIVATION_EAGER;
+    }
+
+    @Override
+    public List<String> getDependsOn() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public String getDependencyDescriptor() {
+        return dependendencyDesc;
+    }
+
+    @Override
+    public boolean isSatisfied() {
+        return satisfied.get();
+    }
+
+    protected abstract void startTracking();
+
+    protected void setFailureMessage(String failureMessage) {
+        this.failureMessage = failureMessage;
+    }
+
+    protected void setDependendencyDesc(String dependendencyDesc) {
+        this.dependendencyDesc = dependendencyDesc;
+    }
+
+    protected final ExtendedBlueprintContainer container() {
+        return container;
+    }
+
+    protected void setSatisfied() {
+        satisfied.set(true);
+        satisfactionCallback.notifyChanged();
+    }
+
+    protected void retrieveService(String name, Class<?> interfaceClass, Consumer<Object> onServiceRetrieved) {
+        StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name,
+                container, interfaceClass.getName());
+        setDependendencyDesc(recipe.getOsgiFilter());
+        serviceRecipes.add(recipe);
+
+        recipe.startTracking(onServiceRetrieved);
+    }
+
+    protected final String logName() {
+        return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") +
+                " (" + id + ")";
+    }
+
+    @Override
+    public void init(ExtendedBlueprintContainer container) {
+        this.container = container;
+
+        log.debug("{}: In init", logName());
+    }
+
+    protected void onCreate() throws ComponentDefinitionException {
+        if(failureMessage != null) {
+            throw new ComponentDefinitionException(failureMessage);
+        }
+
+        // The following code is a bit odd so requires some explanation. A little background... If a bean
+        // is a prototype then the corresponding Recipe create method does not register the bean as created
+        // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
+        // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
+        // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
+        // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
+        // us to indicate the prototype state via our metadata.
+        //
+        // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
+        // the removePartialObject method which removes any partially created instance, which does not apply
+        // in our case, and also has the side effect of registering our bean as created as if it wasn't a
+        // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
+        // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
+        // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
+        // point.
+        //
+        // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
+        // be called multiple times during the container creation process for Satisfiable recipes as bean
+        // processors may modify the metadata which could affect how dependencies are satisfied. An example of
+        // this is with service references where the OSGi filter metadata can be modified by bean processors
+        // after the initial service dependency is satisfied. However we don't have any metadata that could
+        // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
+        // so we only process startTracking once and close the DTCL registration once on container destroy.
+        ExecutionContext executionContext = ExecutionContext.Holder.getContext();
+        executionContext.removePartialObject(id);
+
+        Recipe myRecipe = executionContext.getRecipe(id);
+        if(myRecipe instanceof AbstractRecipe) {
+            log.debug("{}: setPrototype to false", logName());
+            ((AbstractRecipe)myRecipe).setPrototype(false);
+        } else {
+            log.warn("{}: Recipe is null or not an AbstractRecipe", logName());
+        }
+    }
+
+    @Override
+    public final void startTracking(final SatisfactionCallback satisfactionCallback) {
+        if(!started.compareAndSet(false, true)) {
+            return;
+        }
+
+        log.debug("{}: In startTracking", logName());
+
+        this.satisfactionCallback = satisfactionCallback;
+
+        startTracking();
+    }
+
+    @Override
+    public void stopTracking() {
+        log.debug("{}: In stopTracking", logName());
+
+        stopServiceRecipes();
+    }
+
+    @Override
+    public void destroy(Object instance) {
+        log.debug("{}: In destroy", logName());
+
+        stopServiceRecipes();
+    }
+
+    private void stopServiceRecipes() {
+        for(StaticServiceReferenceRecipe recipe: serviceRecipes) {
+            recipe.stop();
+        }
+
+        serviceRecipes.clear();
+    }
+}
index b2ea693..c1f2842 100644 (file)
@@ -20,10 +20,6 @@ import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import org.apache.aries.blueprint.di.AbstractRecipe;
-import org.apache.aries.blueprint.di.ExecutionContext;
-import org.apache.aries.blueprint.di.Recipe;
-import org.apache.aries.blueprint.ext.DependentComponentFactoryMetadata;
 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
 import org.opendaylight.controller.blueprint.BlueprintContainerRestartService;
 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
@@ -68,29 +64,21 @@ import org.w3c.dom.Element;
  *
  * @author Thomas Pantelis
  */
-public class DataStoreAppConfigMetadata implements DependentComponentFactoryMetadata {
+public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactoryMetadata {
     private static final Logger LOG = LoggerFactory.getLogger(DataStoreAppConfigMetadata.class);
 
     static final String BINDING_CLASS = "binding-class";
     static final String DEFAULT_CONFIG = "default-config";
     static final String LIST_KEY_VALUE = "list-key-value";
 
-    private final String id;
     private final Element defaultAppConfigElement;
     private final String appConfigBindingClassName;
     private final String appConfigListKeyValue;
     private final AtomicBoolean readingInitialAppConfig = new AtomicBoolean(true);
-    private final AtomicBoolean started = new AtomicBoolean();
 
     private volatile BindingContext bindingContext;
-    private volatile ExtendedBlueprintContainer container;
-    private volatile StaticServiceReferenceRecipe dataBrokerServiceRecipe;
-    private volatile StaticServiceReferenceRecipe bindingCodecServiceRecipe;
     private volatile ListenerRegistration<?> appConfigChangeListenerReg;
     private volatile DataObject currentAppConfig;
-    private volatile SatisfactionCallback satisfactionCallback;
-    private volatile String failureMessage;
-    private volatile String dependendencyDesc;
 
     // Note: the BindingNormalizedNodeSerializer interface is annotated as deprecated because there's an
     // equivalent interface in the mdsal project but the corresponding binding classes in the controller
@@ -99,38 +87,16 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
 
     public DataStoreAppConfigMetadata(@Nonnull String id, @Nonnull String appConfigBindingClassName,
             @Nullable String appConfigListKeyValue, @Nullable Element defaultAppConfigElement) {
-        this.id = Preconditions.checkNotNull(id);
+        super(id);
         this.defaultAppConfigElement = defaultAppConfigElement;
         this.appConfigBindingClassName = appConfigBindingClassName;
         this.appConfigListKeyValue = appConfigListKeyValue;
     }
 
-    @Override
-    public String getId() {
-        return id;
-    }
-
-    @Override
-    public int getActivation() {
-        return ACTIVATION_EAGER;
-    }
-
-    @Override
-    public List<String> getDependsOn() {
-        return Collections.emptyList();
-    }
-
-    @Override
-    public boolean isSatisfied() {
-        return currentAppConfig != null;
-    }
-
     @Override
     @SuppressWarnings("unchecked")
     public void init(ExtendedBlueprintContainer container) {
-        LOG.debug("{}: In init", id);
-
-        this.container = container;
+        super.init(container);
 
         Class<DataObject> appConfigBindingClass;
         try {
@@ -138,13 +104,13 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
             if(!DataObject.class.isAssignableFrom(bindingClass)) {
                 throw new ComponentDefinitionException(String.format(
                         "%s: Specified app config binding class %s does not extend %s",
-                        id, appConfigBindingClassName, DataObject.class.getName()));
+                        logName(), appConfigBindingClassName, DataObject.class.getName()));
             }
 
             appConfigBindingClass = (Class<DataObject>) bindingClass;
         } catch(ClassNotFoundException e) {
             throw new ComponentDefinitionException(String.format("%s: Error loading app config binding class %s",
-                    id, appConfigBindingClassName), e);
+                    logName(), appConfigBindingClassName), e);
         }
 
         if(Identifiable.class.isAssignableFrom(appConfigBindingClass)) {
@@ -152,7 +118,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
             if(Strings.isNullOrEmpty(appConfigListKeyValue)) {
                 throw new ComponentDefinitionException(String.format(
                         "%s: App config binding class %s represents a yang list therefore \"%s\" must be specified",
-                        id, appConfigBindingClassName, LIST_KEY_VALUE));
+                        logName(), appConfigBindingClassName, LIST_KEY_VALUE));
             }
 
             try {
@@ -160,7 +126,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
             } catch(Exception e) {
                 throw new ComponentDefinitionException(String.format(
                         "%s: Error initializing for app config list binding class %s",
-                        id, appConfigBindingClassName), e);
+                        logName(), appConfigBindingClassName), e);
             }
 
         } else {
@@ -170,89 +136,36 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
 
     @Override
     public Object create() throws ComponentDefinitionException {
-        LOG.debug("{}: In create - currentAppConfig: {}", id, currentAppConfig);
+        LOG.debug("{}: In create - currentAppConfig: {}", logName(), currentAppConfig);
 
-        if(failureMessage != null) {
-            throw new ComponentDefinitionException(failureMessage);
-        }
-
-        // The following code is a bit odd so requires some explanation. A little background... If a bean
-        // is a prototype then the corresponding Recipe create method does not register the bean as created
-        // with the BlueprintRepository and thus the destroy method isn't called on container destroy. We
-        // rely on destroy being called to close our DTCL registration. Unfortunately the default setting
-        // for the prototype flag in AbstractRecipe is true and the DependentComponentFactoryRecipe, which
-        // is created for DependentComponentFactoryMetadata types of which we are one, doesn't have a way for
-        // us to indicate the prototype state via our metadata.
-        //
-        // The ExecutionContext is actually backed by the BlueprintRepository so we access it here to call
-        // the removePartialObject method which removes any partially created instance, which does not apply
-        // in our case, and also has the side effect of registering our bean as created as if it wasn't a
-        // prototype. We also obtain our corresponding Recipe instance and clear the prototype flag. This
-        // doesn't look to be necessary but is done so for completeness. Better late than never. Note we have
-        // to do this here rather than in startTracking b/c the ExecutionContext is not available yet at that
-        // point.
-        //
-        // Now the stopTracking method is called on container destroy but startTracking/stopTracking can also
-        // be called multiple times during the container creation process for Satisfiable recipes as bean
-        // processors may modify the metadata which could affect how dependencies are satisfied. An example of
-        // this is with service references where the OSGi filter metadata can be modified by bean processors
-        // after the initial service dependency is satisfied. However we don't have any metadata that could
-        // be modified by a bean processor and we don't want to register/unregister our DTCL multiple times
-        // so we only process startTracking once and close the DTCL registration once on container destroy.
-        ExecutionContext executionContext = ExecutionContext.Holder.getContext();
-        executionContext.removePartialObject(id);
-
-        Recipe myRecipe = executionContext.getRecipe(id);
-        if(myRecipe instanceof AbstractRecipe) {
-            LOG.debug("{}: setPrototype to false", id);
-            ((AbstractRecipe)myRecipe).setPrototype(false);
-        } else {
-            LOG.warn("{}: Recipe is null or not an AbstractRecipe", id);
-        }
+        super.onCreate();
 
         return currentAppConfig;
     }
 
     @Override
-    public void startTracking(final SatisfactionCallback satisfactionCallback) {
-        if(!started.compareAndSet(false, true)) {
-            return;
-        }
-
-        LOG.debug("{}: In startTracking", id);
-
-        this.satisfactionCallback = satisfactionCallback;
-
+    protected void startTracking() {
         // First get the BindingNormalizedNodeSerializer OSGi service. This will be used to create a default
         // instance of the app config binding class, if necessary.
 
-        bindingCodecServiceRecipe = new StaticServiceReferenceRecipe(id + "-binding-codec", container,
-                BindingNormalizedNodeSerializer.class.getName());
-        dependendencyDesc = bindingCodecServiceRecipe.getOsgiFilter();
-
-        bindingCodecServiceRecipe.startTracking(service -> {
+        retrieveService("binding-codec", BindingNormalizedNodeSerializer.class, service -> {
             bindingSerializer = (BindingNormalizedNodeSerializer)service;
             retrieveDataBrokerService();
         });
     }
 
     private void retrieveDataBrokerService() {
-        LOG.debug("{}: In retrieveDataBrokerService", id);
+        LOG.debug("{}: In retrieveDataBrokerService", logName());
 
         // Get the binding DataBroker OSGi service.
 
-        dataBrokerServiceRecipe = new StaticServiceReferenceRecipe(id + "-data-broker", container,
-                DataBroker.class.getName());
-        dependendencyDesc = dataBrokerServiceRecipe.getOsgiFilter();
-
-        dataBrokerServiceRecipe.startTracking(service -> retrieveInitialAppConfig((DataBroker)service));
-
+        retrieveService("data-broker", DataBroker.class, service -> retrieveInitialAppConfig((DataBroker)service));
     }
 
     private void retrieveInitialAppConfig(DataBroker dataBroker) {
-        LOG.debug("{}: Got DataBroker instance - reading app config {}", id, bindingContext.appConfigPath);
+        LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath);
 
-        dependendencyDesc = "Initial app config " + bindingContext.appConfigBindingClass.getSimpleName();
+        setDependendencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName());
 
         // We register a DTCL to get updates and also read the app config data from the data store. If
         // the app config data is present then both the read and initial DTCN update will return it. If the
@@ -280,7 +193,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
         Futures.addCallback(future, new FutureCallback<Optional<DataObject>>() {
             @Override
             public void onSuccess(Optional<DataObject> possibleAppConfig) {
-                LOG.debug("{}: Read of app config {} succeeded: {}", id, bindingContext.appConfigBindingClass.getName(),
+                LOG.debug("{}: Read of app config {} succeeded: {}", logName(), bindingContext.appConfigBindingClass.getName(),
                         possibleAppConfig);
 
                 readOnlyTx.close();
@@ -293,7 +206,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
 
                 // We may have gotten the app config via the data tree change listener so only retry if not.
                 if(readingInitialAppConfig.get()) {
-                    LOG.warn("{}: Read of app config {} failed - retrying", id,
+                    LOG.warn("{}: Read of app config {} failed - retrying", logName(),
                             bindingContext.appConfigBindingClass.getName(), t);
 
                     readInitialAppConfig(dataBroker);
@@ -307,7 +220,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
             DataObjectModification<DataObject> changeRoot = change.getRootNode();
             ModificationType type = changeRoot.getModificationType();
 
-            LOG.debug("{}: onAppConfigChanged: {}, {}", id, type, change.getRootPath());
+            LOG.debug("{}: onAppConfigChanged: {}, {}", logName(), type, change.getRootPath());
 
             if(type == ModificationType.SUBTREE_MODIFIED || type == ModificationType.WRITE) {
                 DataObject newAppConfig = changeRoot.getDataAfter();
@@ -340,12 +253,12 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
                 localAppConfig = createDefaultInstance();
             }
 
-            LOG.debug("{}: Setting currentAppConfig instance: {}", id, localAppConfig);
+            LOG.debug("{}: Setting currentAppConfig instance: {}", logName(), localAppConfig);
 
             // Now publish the app config instance to the volatile field and notify the callback to let the
             // container know our dependency is now satisfied.
             currentAppConfig = localAppConfig;
-            satisfactionCallback.notifyChanged();
+            setSatisfied();
         }
 
         return result;
@@ -354,11 +267,11 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
     private DataObject createDefaultInstance() {
         YangInstanceIdentifier yangPath = bindingSerializer.toYangInstanceIdentifier(bindingContext.appConfigPath);
 
-        LOG.debug("{}: Creating app config instance from path {}, Qname: {}", id, yangPath, bindingContext.bindingQName);
+        LOG.debug("{}: Creating app config instance from path {}, Qname: {}", logName(), yangPath, bindingContext.bindingQName);
 
         SchemaService schemaService = getOSGiService(SchemaService.class);
         if(schemaService == null) {
-            failureMessage = String.format("%s: Could not obtain the SchemaService OSGi service", id);
+            setFailureMessage(String.format("%s: Could not obtain the SchemaService OSGi service", logName()));
             return null;
         }
 
@@ -367,20 +280,20 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
         Module module = schemaContext.findModuleByNamespaceAndRevision(bindingContext.bindingQName.getNamespace(),
                 bindingContext.bindingQName.getRevision());
         if(module == null) {
-            failureMessage = String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
-                    id, bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision());
+            setFailureMessage(String.format("%s: Could not obtain the module schema for namespace %s, revision %s",
+                    logName(), bindingContext.bindingQName.getNamespace(), bindingContext.bindingQName.getRevision()));
             return null;
         }
 
         DataSchemaNode dataSchema = module.getDataChildByName(bindingContext.bindingQName);
         if(dataSchema == null) {
-            failureMessage = String.format("%s: Could not obtain the schema for %s", id, bindingContext.bindingQName);
+            setFailureMessage(String.format("%s: Could not obtain the schema for %s", logName(), bindingContext.bindingQName));
             return null;
         }
 
         if(!bindingContext.schemaType.isAssignableFrom(dataSchema.getClass())) {
-            failureMessage = String.format("%s: Expected schema type %s for %s but actual type is %s", id,
-                    bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass());
+            setFailureMessage(String.format("%s: Expected schema type %s for %s but actual type is %s", logName(),
+                    bindingContext.schemaType, bindingContext.bindingQName, dataSchema.getClass()));
             return null;
         }
 
@@ -393,8 +306,8 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
 
         if(appConfig == null) {
             // This shouldn't happen but need to handle it in case...
-            failureMessage = String.format("%s: Could not create instance for app config binding %s",
-                    id, bindingContext.appConfigBindingClass);
+            setFailureMessage(String.format("%s: Could not create instance for app config binding %s",
+                    logName(), bindingContext.appConfigBindingClass));
         }
 
         return appConfig;
@@ -407,18 +320,18 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
             return null;
         }
 
-        LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", id, bindingContext.bindingQName);
+        LOG.debug("{}: parsePossibleDefaultAppConfigElement for {}", logName(), bindingContext.bindingQName);
 
         DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(
                 XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext);
 
 
-        LOG.debug("{}: Got app config schema: {}", id, dataSchema);
+        LOG.debug("{}: Got app config schema: {}", logName(), dataSchema);
 
         NormalizedNode<?, ?> dataNode = bindingContext.parseDataElement(defaultAppConfigElement, dataSchema,
                 parserFactory);
 
-        LOG.debug("{}: Parsed data node: {}", id, dataNode);
+        LOG.debug("{}: Parsed data node: {}", logName(), dataNode);
 
         return dataNode;
     }
@@ -426,7 +339,7 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
     private void restartContainer() {
         BlueprintContainerRestartService restartService = getOSGiService(BlueprintContainerRestartService.class);
         if(restartService != null) {
-            restartService.restartContainerAndDependents(container.getBundleContext().getBundle());
+            restartService.restartContainerAndDependents(container().getBundleContext().getBundle());
         }
     }
 
@@ -435,63 +348,36 @@ public class DataStoreAppConfigMetadata implements DependentComponentFactoryMeta
     private <T> T getOSGiService(Class<T> serviceInterface) {
         try {
             ServiceReference<T> serviceReference =
-                    container.getBundleContext().getServiceReference(serviceInterface);
+                    container().getBundleContext().getServiceReference(serviceInterface);
             if(serviceReference == null) {
-                LOG.warn("{}: {} reference not found", id, serviceInterface.getSimpleName());
+                LOG.warn("{}: {} reference not found", logName(), serviceInterface.getSimpleName());
                 return null;
             }
 
-            T service = (T)container.getService(serviceReference);
+            T service = (T)container().getService(serviceReference);
             if(service == null) {
                 // This could happen on shutdown if the service was already unregistered so we log as debug.
-                LOG.debug("{}: {} was not found", id, serviceInterface.getSimpleName());
+                LOG.debug("{}: {} was not found", logName(), serviceInterface.getSimpleName());
             }
 
             return service;
         } catch(IllegalStateException e) {
             // This is thrown if the BundleContext is no longer valid which is possible on shutdown so we
             // log as debug.
-            LOG.debug("{}: Error obtaining {}", id, serviceInterface.getSimpleName(), e);
+            LOG.debug("{}: Error obtaining {}", logName(), serviceInterface.getSimpleName(), e);
         }
 
         return null;
     }
 
-    @Override
-    public void stopTracking() {
-        LOG.debug("{}: In stopTracking", id);
-
-        stopServiceRecipes();
-    }
-
     @Override
     public void destroy(Object instance) {
-        LOG.debug("{}: In destroy", id);
+        super.destroy(instance);
 
         if(appConfigChangeListenerReg != null) {
             appConfigChangeListenerReg.close();
             appConfigChangeListenerReg = null;
         }
-
-        stopServiceRecipes();
-    }
-
-    private void stopServiceRecipes() {
-        stopServiceRecipe(dataBrokerServiceRecipe);
-        stopServiceRecipe(bindingCodecServiceRecipe);
-        dataBrokerServiceRecipe = null;
-        bindingCodecServiceRecipe = null;
-    }
-
-    private void stopServiceRecipe(StaticServiceReferenceRecipe recipe) {
-        if(recipe != null) {
-            recipe.stop();
-        }
-    }
-
-    @Override
-    public String getDependencyDescriptor() {
-        return dependendencyDesc;
     }
 
     /**
index 7d1bbf9..7a1c86b 100644 (file)
@@ -257,8 +257,6 @@ public class OpendaylightNamespaceHandler implements NamespaceHandler {
     }
 
     private Metadata parseRpcService(Element element, ParserContext context) {
-        registerRpcRegistryServiceRefBean(context);
-
         ComponentFactoryMetadata metadata = new RpcServiceMetadata(getId(context, element),
                 element.getAttribute(INTERFACE));
 
index 7fc077d..e2260a6 100644 (file)
@@ -7,13 +7,23 @@
  */
 package org.opendaylight.controller.blueprint.ext;
 
-import com.google.common.base.Preconditions;
-import java.util.Collections;
-import java.util.List;
-import org.apache.aries.blueprint.ext.ComponentFactoryMetadata;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
 import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.osgi.service.blueprint.container.ComponentDefinitionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -24,54 +34,104 @@ import org.slf4j.LoggerFactory;
  *
  * @author Thomas Pantelis
  */
-class RpcServiceMetadata implements ComponentFactoryMetadata {
+class RpcServiceMetadata extends AbstractDependentComponentFactoryMetadata {
     private static final Logger LOG = LoggerFactory.getLogger(RpcServiceMetadata.class);
 
-    private final String id;
     private final String interfaceName;
-    private ExtendedBlueprintContainer container;
+    private volatile Set<SchemaPath> rpcSchemaPaths;
+    private volatile RpcProviderRegistry rpcRegistry;
+    private volatile ListenerRegistration<DOMRpcAvailabilityListener> rpcListenerReg;
+    private volatile Class<RpcService> rpcInterface;
 
     RpcServiceMetadata(String id, String interfaceName) {
-        this.id = id;
+        super(id);
         this.interfaceName = interfaceName;
     }
 
     @Override
-    public String getId() {
-        return id;
+    @SuppressWarnings("unchecked")
+    public void init(ExtendedBlueprintContainer container) {
+        super.init(container);
+
+        try {
+            Class<?> interfaceClass = container().getBundleContext().getBundle().loadClass(interfaceName);
+            if(!RpcService.class.isAssignableFrom(interfaceClass)) {
+                throw new ComponentDefinitionException(String.format(
+                        "%s: The specified interface %s is not an RpcService", logName(), interfaceName));
+            }
+
+            rpcInterface = (Class<RpcService>)interfaceClass;
+        } catch(Exception e) {
+            throw new ComponentDefinitionException(String.format("%s: Error obtaining interface class %s",
+                    logName(), interfaceName), e);
+        }
     }
 
     @Override
-    public int getActivation() {
-        return ACTIVATION_LAZY;
+    protected void startTracking() {
+        // First get the SchemaContext. This will be used to get the RPC SchemaPaths.
+
+        retrieveService("SchemaService", SchemaService.class,
+                service -> retrievedSchemaContext(((SchemaService)service).getGlobalContext()));
     }
 
-    @Override
-    public List<String> getDependsOn() {
-        return Collections.singletonList(OpendaylightNamespaceHandler.RPC_REGISTRY_NAME);
+    private void retrievedSchemaContext(SchemaContext schemaContext) {
+        LOG.debug("{}: retrievedSchemaContext", logName());
+
+        QNameModule moduleName = BindingReflections.getQNameModule(rpcInterface);
+        Module module = schemaContext.findModuleByNamespaceAndRevision(moduleName.getNamespace(), moduleName.getRevision());
+
+        LOG.debug("{}: Got Module: {}", logName(), module);
+
+        rpcSchemaPaths = new HashSet<>();
+        for(RpcDefinition rpcDef : module.getRpcs()) {
+            rpcSchemaPaths.add(rpcDef.getPath());
+        }
+
+        LOG.debug("{}: Got SchemaPaths: {}", logName(), rpcSchemaPaths);
+
+        // First get the DOMRpcService OSGi service. This will be used to register a listener to be notified
+        // when the underlying DOM RPC service is available.
+
+        retrieveService("DOMRpcService", DOMRpcService.class, service -> retrievedDOMRpcService((DOMRpcService)service));
     }
 
-    @Override
-    public void init(ExtendedBlueprintContainer container) {
-        this.container = container;
+    private void retrievedDOMRpcService(DOMRpcService domRpcService) {
+        LOG.debug("{}: retrievedDOMRpcService", logName());
+
+        rpcListenerReg = domRpcService.registerRpcListener(new DOMRpcAvailabilityListener() {
+            @Override
+            public void onRpcAvailable(Collection<DOMRpcIdentifier> rpcs) {
+                onRpcsAvailable(rpcs);
+            }
 
-        LOG.debug("{}: In init", logName());
+            @Override
+            public void onRpcUnavailable(Collection<DOMRpcIdentifier> rpcs) {
+            }
+        });
+    }
+
+    protected void onRpcsAvailable(Collection<DOMRpcIdentifier> rpcs) {
+        for(DOMRpcIdentifier identifier: rpcs) {
+            if(rpcSchemaPaths.contains(identifier.getType())) {
+                LOG.debug("{}: onRpcsAvailable - found SchemaPath {}", logName(), identifier.getType());
+
+                retrieveService("RpcProviderRegistry", RpcProviderRegistry.class, service -> {
+                    rpcRegistry = (RpcProviderRegistry)service;
+                    setSatisfied();
+                });
+
+                break;
+            }
+        }
     }
 
-    @SuppressWarnings("unchecked")
     @Override
     public Object create() throws ComponentDefinitionException {
         LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName);
 
-        RpcProviderRegistry rpcRegistry = (RpcProviderRegistry) container.getComponentInstance(
-                OpendaylightNamespaceHandler.RPC_REGISTRY_NAME);
-
         try {
-            Class<?> rpcInterface = container.getBundleContext().getBundle().loadClass(interfaceName);
-            Preconditions.checkArgument(RpcService.class.isAssignableFrom(rpcInterface),
-                    "Specified interface %s is not an RpcService", interfaceName);
-
-            RpcService rpcService = rpcRegistry.getRpcService((Class<RpcService>)rpcInterface);
+            RpcService rpcService = rpcRegistry.getRpcService(rpcInterface);
 
             LOG.debug("{}: create returning service {}", logName(), rpcService);
 
@@ -82,16 +142,26 @@ class RpcServiceMetadata implements ComponentFactoryMetadata {
     }
 
     @Override
-    public void destroy(Object instance) {
+    public void stopTracking() {
+        super.stopTracking();
+        closeRpcListenerReg();
+    }
+
+    private void closeRpcListenerReg() {
+        if(rpcListenerReg != null) {
+            rpcListenerReg.close();
+            rpcListenerReg = null;
+        }
     }
 
-    private String logName() {
-        return (container != null ? container.getBundleContext().getBundle().getSymbolicName() : "") +
-                " (" + id + ")";
+    @Override
+    public void destroy(Object instance) {
+        super.destroy(instance);
+        closeRpcListenerReg();
     }
 
     @Override
     public String toString() {
-        return "RpcServiceMetadata [id=" + id + ", interfaceName=" + interfaceName + ", container=" + container + "]";
+        return "RpcServiceMetadata [id=" + getId() + ", interfaceName=" + interfaceName + "]";
     }
 }