From ef7700c56ee56a5c79444c95199f5b016de22de9 Mon Sep 17 00:00:00 2001 From: Tom Pantelis Date: Tue, 7 Jun 2016 14:04:50 -0400 Subject: [PATCH] Wait for RPCService registered in RpcServiceMetadata 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 --- ...ractDependentComponentFactoryMetadata.java | 182 +++++++++++++++++ .../ext/DataStoreAppConfigMetadata.java | 192 ++++-------------- .../ext/OpendaylightNamespaceHandler.java | 2 - .../blueprint/ext/RpcServiceMetadata.java | 136 ++++++++++--- 4 files changed, 324 insertions(+), 188 deletions(-) create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.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 index 0000000000..bdcc1ee86d --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractDependentComponentFactoryMetadata.java @@ -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 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 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 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(); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java index b2ea693ab9..c1f28428c9 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/DataStoreAppConfigMetadata.java @@ -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 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 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) 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>() { @Override public void onSuccess(Optional 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 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 getOSGiService(Class serviceInterface) { try { ServiceReference 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; } /** diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java index 7d1bbf9509..7a1c86ba19 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/OpendaylightNamespaceHandler.java @@ -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)); diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java index 7fc077d287..e2260a6d74 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java @@ -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 rpcSchemaPaths; + private volatile RpcProviderRegistry rpcRegistry; + private volatile ListenerRegistration rpcListenerReg; + private volatile Class 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)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 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 rpcs) { + onRpcsAvailable(rpcs); + } - LOG.debug("{}: In init", logName()); + @Override + public void onRpcUnavailable(Collection rpcs) { + } + }); + } + + protected void onRpcsAvailable(Collection 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)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 + "]"; } } -- 2.36.6