From 88330d2f3ff048ab4e2e6f348ec3ea56e4c02cd4 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Fri, 20 Jan 2017 22:04:11 +0100 Subject: [PATCH] BUG-7608: Add ActionServiceMetadata and ActionProviderBean This patch add the new concepts of action-provider and action-service. The implementation does nothing, as we are transitioning from a run-time logic being coupled with sal-remoterpc-connector. This allows us to migrate users, while retaining behavior indepent of sal-remoterpc-connector's actions. This will allow us to fix BUG-3128. Once it is fixed, and DOMRpcRouter can express the action-provider advertisement, we are going to actiovate the commented-out code ActionServiceMetadata.acceptableStrategy(). Change-Id: I3f412d092c10b51a198721f288fdefdfc907f0b7 Signed-off-by: Robert Varga --- opendaylight/blueprint/pom.xml | 4 + ...ractDependentComponentFactoryMetadata.java | 30 +-- .../ext/AbstractInvokableServiceMetadata.java | 177 ++++++++++++++++++ .../blueprint/ext/ActionProviderBean.java | 149 +++++++++++++++ .../blueprint/ext/ActionServiceMetadata.java | 42 +++++ .../ext/DataStoreAppConfigMetadata.java | 2 +- .../ext/OpendaylightNamespaceHandler.java | 65 ++++++- .../blueprint/ext/RpcServiceMetadata.java | 159 +--------------- .../controller/blueprint/ext/RpcUtil.java | 54 ++++++ .../ext/SpecificReferenceListMetadata.java | 4 +- .../opendaylight-blueprint-ext-1.0.0.xsd | 56 ++++++ 11 files changed, 567 insertions(+), 175 deletions(-) create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java diff --git a/opendaylight/blueprint/pom.xml b/opendaylight/blueprint/pom.xml index 4ae8c6e0e0..df30471e30 100644 --- a/opendaylight/blueprint/pom.xml +++ b/opendaylight/blueprint/pom.xml @@ -62,6 +62,10 @@ org.opendaylight.controller sal-core-api + + org.opendaylight.controller + sal-core-spi + org.opendaylight.mdsal mdsal-binding-dom-codec 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 index c15a7037e7..9bd51a460a 100644 --- 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 @@ -32,7 +32,7 @@ import org.slf4j.LoggerFactory; * @author Thomas Pantelis */ abstract class AbstractDependentComponentFactoryMetadata implements DependentComponentFactoryMetadata { - private final Logger log = LoggerFactory.getLogger(getClass()); + final Logger log = LoggerFactory.getLogger(getClass()); private final String id; private final AtomicBoolean started = new AtomicBoolean(); private final AtomicBoolean satisfied = new AtomicBoolean(); @@ -43,11 +43,11 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom private volatile SatisfactionCallback satisfactionCallback; private volatile String failureMessage; private volatile Throwable failureCause; - private volatile String dependendencyDesc; + private volatile String dependencyDesc; @GuardedBy("serviceRecipes") private boolean stoppedServiceRecipes; - protected AbstractDependentComponentFactoryMetadata(String id) { + protected AbstractDependentComponentFactoryMetadata(final String id) { this.id = Preconditions.checkNotNull(id); } @@ -68,7 +68,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom @Override public String getDependencyDescriptor() { - return dependendencyDesc; + return dependencyDesc; } @Override @@ -76,17 +76,17 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom return satisfied.get(); } - protected void setFailureMessage(String failureMessage) { + protected void setFailureMessage(final String failureMessage) { setFailure(failureMessage, null); } - protected void setFailure(String failureMessage, Throwable failureCause) { + protected void setFailure(final String failureMessage, final Throwable failureCause) { this.failureMessage = failureMessage; this.failureCause = failureCause; } - protected void setDependendencyDesc(String dependendencyDesc) { - this.dependendencyDesc = dependendencyDesc; + protected void setDependencyDesc(final String dependencyDesc) { + this.dependencyDesc = dependencyDesc; } protected final ExtendedBlueprintContainer container() { @@ -99,11 +99,13 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom } } - protected void retrieveService(String name, Class interfaceClass, Consumer onServiceRetrieved) { + protected void retrieveService(final String name, final Class interfaceClass, + final Consumer onServiceRetrieved) { retrieveService(name, interfaceClass.getName(), onServiceRetrieved); } - protected void retrieveService(String name, String interfaceName, Consumer onServiceRetrieved) { + protected void retrieveService(final String name, final String interfaceName, + final Consumer onServiceRetrieved) { synchronized (serviceRecipes) { if (stoppedServiceRecipes) { return; @@ -111,7 +113,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom StaticServiceReferenceRecipe recipe = new StaticServiceReferenceRecipe(getId() + "-" + name, container, interfaceName); - setDependendencyDesc(recipe.getOsgiFilter()); + setDependencyDesc(recipe.getOsgiFilter()); serviceRecipes.add(recipe); recipe.startTracking(onServiceRetrieved); @@ -123,7 +125,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom } @Override - public void init(ExtendedBlueprintContainer newContainer) { + public void init(final ExtendedBlueprintContainer newContainer) { this.container = newContainer; log.debug("{}: In init", logName()); @@ -192,7 +194,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom } @Override - public void destroy(Object instance) { + public void destroy(final Object instance) { log.debug("{}: In destroy", logName()); stopServiceRecipes(); @@ -221,7 +223,7 @@ abstract class AbstractDependentComponentFactoryMetadata implements DependentCom @SuppressWarnings("unchecked") @Nullable - protected T getOSGiService(Class serviceInterface) { + protected T getOSGiService(final Class serviceInterface) { try { ServiceReference serviceReference = container().getBundleContext().getServiceReference(serviceInterface); diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java new file mode 100644 index 0000000000..1c1f4cb813 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java @@ -0,0 +1,177 @@ +/* + * 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.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import java.util.Collection; +import java.util.Set; +import java.util.function.Predicate; +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.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; +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.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.osgi.service.blueprint.container.ComponentDefinitionException; + +abstract class AbstractInvokableServiceMetadata extends AbstractDependentComponentFactoryMetadata { + private final String interfaceName; + + private ListenerRegistration rpcListenerReg; + private RpcProviderRegistry rpcRegistry; + private Class rpcInterface; + private Set rpcSchemaPaths; + + AbstractInvokableServiceMetadata(final String id, final String interfaceName) { + super(id); + this.interfaceName = Preconditions.checkNotNull(interfaceName); + } + + Class rpcInterface() { + return rpcInterface; + } + + @SuppressWarnings({ "checkstyle:IllegalCatch", "unchecked" }) + @Override + public final void init(final ExtendedBlueprintContainer container) { + super.init(container); + + final Class interfaceClass; + try { + interfaceClass = container().getBundleContext().getBundle().loadClass(interfaceName); + } catch (Exception e) { + throw new ComponentDefinitionException(String.format("%s: Error obtaining interface class %s", + logName(), interfaceName), e); + } + + if (!RpcService.class.isAssignableFrom(interfaceClass)) { + throw new ComponentDefinitionException(String.format( + "%s: The specified interface %s is not an RpcService", logName(), interfaceName)); + } + + rpcInterface = (Class)interfaceClass; + } + + @Override + protected final void startTracking() { + // Request RpcProviderRegistry first ... + retrieveService("RpcProviderRegistry", RpcProviderRegistry.class, this::onRpcRegistry); + } + + private void onRpcRegistry(final Object service) { + log.debug("{}: Retrieved RpcProviderRegistry {}", logName(), service); + rpcRegistry = (RpcProviderRegistry)service; + + // Now acquire SchemaService... + retrieveService("SchemaService", SchemaService.class, this::onSchemaService); + } + + private void onSchemaService(final Object service) { + log.debug("{}: Retrieved SchemaService {}", logName(), service); + + // Now get the SchemaContext and trigger RPC resolution + retrievedSchemaContext(((SchemaService)service).getGlobalContext()); + } + + private void retrievedSchemaContext(final SchemaContext schemaContext) { + log.debug("{}: retrievedSchemaContext", logName()); + + final Collection schemaPaths = RpcUtil.decomposeRpcService(rpcInterface, schemaContext, + rpcFilter()); + if (schemaPaths.isEmpty()) { + log.warn("{}: interface {} has no accptable entries, assuming it is satisfied"); + setSatisfied(); + return; + } + + rpcSchemaPaths = ImmutableSet.copyOf(schemaPaths); + 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, this::retrievedDOMRpcService); + } + + private void retrievedDOMRpcService(final Object service) { + log.debug("{}: retrievedDOMRpcService {}", logName(), service); + final DOMRpcService domRpcService = (DOMRpcService)service; + + setDependencyDesc("Available DOM RPC for binding RPC: " + rpcInterface); + rpcListenerReg = domRpcService.registerRpcListener(new DOMRpcAvailabilityListener() { + @Override + public void onRpcAvailable(final Collection rpcs) { + onRpcsAvailable(rpcs); + } + + @Override + public void onRpcUnavailable(final Collection rpcs) { + } + }); + } + + abstract Predicate rpcFilter(); + + @SuppressWarnings("checkstyle:IllegalCatch") + @Override + public final Object create() throws ComponentDefinitionException { + log.debug("{}: In create: interfaceName: {}", logName(), interfaceName); + + super.onCreate(); + + try { + RpcService rpcService = rpcRegistry.getRpcService(rpcInterface); + + log.debug("{}: create returning service {}", logName(), rpcService); + + return rpcService; + } catch (RuntimeException e) { + throw new ComponentDefinitionException("Error getting RPC service for " + interfaceName, e); + } + } + + protected final void onRpcsAvailable(final Collection rpcs) { + for (DOMRpcIdentifier identifier : rpcs) { + if (rpcSchemaPaths.contains(identifier.getType())) { + log.debug("{}: onRpcsAvailable - found SchemaPath {}", logName(), identifier.getType()); + setSatisfied(); + break; + } + } + } + + @Override + public final void stopTracking() { + super.stopTracking(); + closeRpcListenerReg(); + } + + private void closeRpcListenerReg() { + if (rpcListenerReg != null) { + rpcListenerReg.close(); + rpcListenerReg = null; + } + } + + @Override + public final void destroy(final Object instance) { + super.destroy(instance); + closeRpcListenerReg(); + } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("id", getId()).add("interfaceName", interfaceName).toString(); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java new file mode 100644 index 0000000000..bbd989b633 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2017 Cisco 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.collect.Collections2; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import java.util.Collection; +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; +import org.opendaylight.controller.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; +import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; +import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.osgi.framework.Bundle; +import org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Blueprint bean corresponding to the "action-provider" element that registers the promise to instantiate action + * instances with RpcProviderRegistry. + * + *

+ * This bean has two distinct facets: + * - if a reference bean is provided, it registers it with {@link RpcProviderRegistry} + * - if a reference bean is not provided, it registers the corresponding no-op implementation with + * {@link DOMRpcProviderService} for all action (Routed RPC) elements in the provided interface + * + * @author Robert Varga + */ +public class ActionProviderBean { + static final String ACTION_PROVIDER = "action-provider"; + + private static final Logger LOG = LoggerFactory.getLogger(ActionProviderBean.class); + + private DOMRpcProviderService rpcProviderService; + private RpcProviderRegistry rpcRegistry; + private SchemaService schemaService; + private RpcService implementation; + private String interfaceName; + private Registration reg; + private Bundle bundle; + + public void setBundle(final Bundle bundle) { + this.bundle = bundle; + } + + public void setInterfaceName(final String interfaceName) { + this.interfaceName = interfaceName; + } + + public void setImplementation(final RpcService implementation) { + this.implementation = implementation; + } + + public void setRpcProviderService(final DOMRpcProviderService rpcProviderService) { + this.rpcProviderService = rpcProviderService; + } + + public void setRpcRegistry(final RpcProviderRegistry rpcRegistry) { + this.rpcRegistry = rpcRegistry; + } + + public void setSchemaService(final SchemaService schemaService) { + this.schemaService = schemaService; + } + + public void init() { + // First resolve the interface class + final Class interfaceClass = getRpcClass(interfaceName); + + LOG.debug("{}: resolved interface {} to {}", ACTION_PROVIDER, interfaceName, interfaceClass); + + if (implementation != null) { + registerImplementation(interfaceClass); + } else { + registerFallback(interfaceClass); + } + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public void destroy() { + if (reg != null) { + try { + reg.close(); + } catch (Exception e) { + LOG.warn("{}: error while unregistering", ACTION_PROVIDER, e); + } finally { + reg = null; + } + } + } + + @SuppressWarnings("unchecked") + private Class getRpcClass(final String interfaceName) { + final Class iface; + + try { + iface = bundle.loadClass(interfaceName); + } catch (ClassNotFoundException e) { + throw new ComponentDefinitionException(String.format( + "The specified \"interface\" for %s \"%s\" does not refer to an available class", interfaceName, + ACTION_PROVIDER), e); + } + if (!RpcService.class.isAssignableFrom(iface)) { + throw new ComponentDefinitionException(String.format( + "The specified \"interface\" %s for \"%s\" is not an RpcService", interfaceName, ACTION_PROVIDER)); + } + + return (Class) iface; + } + + private void registerFallback(final Class interfaceClass) { + final Collection paths = RpcUtil.decomposeRpcService(interfaceClass, + schemaService.getGlobalContext(), RpcRoutingStrategy::isContextBasedRouted); + if (paths.isEmpty()) { + LOG.warn("{}: interface {} has no actions defined", ACTION_PROVIDER, interfaceClass); + return; + } + + final Set rpcs = ImmutableSet.copyOf(Collections2.transform(paths, DOMRpcIdentifier::create)); + reg = rpcProviderService.registerRpcImplementation((rpc, input) -> { + return Futures.immediateFailedCheckedFuture(new DOMRpcImplementationNotAvailableException( + "Action %s has no instance matching %s", rpc, input)); + }, rpcs); + LOG.debug("Registered provider for {}", interfaceName); + } + + private void registerImplementation(final Class interfaceClass) { + if (!interfaceClass.isInstance(implementation)) { + throw new ComponentDefinitionException(String.format( + "The specified \"interface\" %s for \"%s\" is not implemented by RpcService \"ref\" %s", + interfaceName, ACTION_PROVIDER, implementation.getClass())); + } + + reg = rpcRegistry.addRpcImplementation(interfaceClass, implementation); + LOG.debug("Registered implementation {} for {}", implementation, interfaceName); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java new file mode 100644 index 0000000000..768d4c0074 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2017 Cisco 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 java.util.function.Predicate; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; +import org.opendaylight.controller.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; + +/** + * Factory metadata corresponding to the "action-service" element. It waits for a DOM promise of registration + * to appear in the {@link DOMRpcService} and then acquires a dynamic proxy via RpcProviderRegistry. + * + * @author Robert Varga + */ +final class ActionServiceMetadata extends AbstractInvokableServiceMetadata { + /* + * Implementation note: + * + * This implementation assumes Binding V1 semantics for actions, which means actions are packaged along with RPCs + * into a single interface. This has interesting implications on working with RpcServiceMetadata, which only + * handles the RPC side of the contract. + * + * Further interesting interactions stem from the fact that in DOM world each action is a separate entity, so the + * interface contract can let some actions to be invoked, while failing for others. This is a shortcoming of the + * Binding Specification and will be addressed in Binding V2 -- where each action is its own interface. + */ + ActionServiceMetadata(final String id, final String interfaceName) { + super(id, interfaceName); + } + + @Override + Predicate rpcFilter() { + // FIXME: BUG-7608: action-service is a no-op for now + // return RpcRoutingStrategy::isContextBasedRouted; + return (strategy) -> false; + } +} 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 fcbe1dea67..e1b4c2964d 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 @@ -180,7 +180,7 @@ public class DataStoreAppConfigMetadata extends AbstractDependentComponentFactor private void retrieveInitialAppConfig(final DataBroker dataBroker) { LOG.debug("{}: Got DataBroker instance - reading app config {}", logName(), bindingContext.appConfigPath); - setDependendencyDesc("Initial app config " + bindingContext.appConfigBindingClass.getSimpleName()); + setDependencyDesc("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 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 45f1e50017..20f29ca7d0 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 @@ -25,7 +25,9 @@ import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata; import org.apache.aries.blueprint.mutable.MutableValueMetadata; import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; import org.opendaylight.controller.md.sal.binding.api.NotificationService; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; +import org.opendaylight.controller.sal.core.api.model.SchemaService; import org.opendaylight.yangtools.util.xml.UntrustedXML; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.osgi.service.blueprint.reflect.BeanMetadata; @@ -53,7 +55,9 @@ import org.xml.sax.SAXException; public final class OpendaylightNamespaceHandler implements NamespaceHandler { public static final String NAMESPACE_1_0_0 = "http://opendaylight.org/xmlns/blueprint/v1.0.0"; static final String ROUTED_RPC_REG_CONVERTER_NAME = "org.opendaylight.blueprint.RoutedRpcRegConverter"; + static final String RPC_PROVIDER_SERVICE_NAME = "org.opendaylight.blueprint.DOMRpcProviderService"; static final String RPC_REGISTRY_NAME = "org.opendaylight.blueprint.RpcRegistry"; + static final String SCHEMA_SERVICE_NAME = "org.opendaylight.blueprint.SchemaService"; static final String NOTIFICATION_SERVICE_NAME = "org.opendaylight.blueprint.NotificationService"; static final String TYPE_ATTR = "type"; static final String UPDATE_STRATEGY_ATTR = "update-strategy"; @@ -67,6 +71,7 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { private static final String REF_ATTR = "ref"; private static final String ID_ATTR = "id"; private static final String RPC_SERVICE = "rpc-service"; + private static final String ACTION_SERVICE = "action-service"; private static final String SPECIFIC_SERVICE_REF_LIST = "specific-reference-list"; private static final String STATIC_REFERENCE = "static-reference"; @@ -105,6 +110,10 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { return parseSpecificReferenceList(element, context); } else if (nodeNameEquals(element, STATIC_REFERENCE)) { return parseStaticReference(element, context); + } else if (nodeNameEquals(element, ACTION_SERVICE)) { + return parseActionService(element, context); + } else if (nodeNameEquals(element, ActionProviderBean.ACTION_PROVIDER)) { + return parseActionProvider(element, context); } throw new ComponentDefinitionException("Unsupported standalone element: " + element.getNodeName()); @@ -232,6 +241,32 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { return metadata; } + private static Metadata parseActionProvider(final Element element, final ParserContext context) { + registerRpcProviderServiceRefBean(context); + registerRpcRegistryServiceRefBean(context); + registerSchemaServiceRefBean(context); + + MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class); + metadata.setId(context.generateId()); + metadata.setScope(BeanMetadata.SCOPE_SINGLETON); + metadata.setActivation(ReferenceMetadata.ACTIVATION_EAGER); + metadata.setRuntimeClass(ActionProviderBean.class); + metadata.setInitMethod("init"); + metadata.setDestroyMethod("destroy"); + metadata.addProperty("bundle", createRef(context, "blueprintBundle")); + metadata.addProperty("rpcProviderService", createRef(context, RPC_PROVIDER_SERVICE_NAME)); + metadata.addProperty("rpcRegistry", createRef(context, RPC_REGISTRY_NAME)); + metadata.addProperty("schemaService", createRef(context, SCHEMA_SERVICE_NAME)); + metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE))); + + if (element.hasAttribute(REF_ATTR)) { + metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR))); + } + + LOG.debug("parseActionProvider returning {}", metadata); + return metadata; + } + private static Metadata parseRpcImplementation(final Element element, final ParserContext context) { registerRpcRegistryServiceRefBean(context); @@ -250,8 +285,7 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE))); } - LOG.debug("parseAddRpcImplementation returning {}", metadata); - + LOG.debug("parseRpcImplementation returning {}", metadata); return metadata; } @@ -267,6 +301,15 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { return metadata; } + private static Metadata parseActionService(final Element element, final ParserContext context) { + ComponentFactoryMetadata metadata = new ActionServiceMetadata(getId(context, element), + element.getAttribute(INTERFACE)); + + LOG.debug("parseActionService returning {}", metadata); + + return metadata; + } + private static Metadata parseRpcService(final Element element, final ParserContext context) { ComponentFactoryMetadata metadata = new RpcServiceMetadata(getId(context, element), element.getAttribute(INTERFACE)); @@ -288,11 +331,23 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { } } + private static void registerRpcProviderServiceRefBean(final ParserContext context) { + registerRefBean(context, RPC_PROVIDER_SERVICE_NAME, DOMRpcProviderService.class); + } + private static void registerRpcRegistryServiceRefBean(final ParserContext context) { + registerRefBean(context, RPC_REGISTRY_NAME, RpcProviderRegistry.class); + } + + private static void registerSchemaServiceRefBean(final ParserContext context) { + registerRefBean(context, SCHEMA_SERVICE_NAME, SchemaService.class); + } + + private static void registerRefBean(final ParserContext context, final String name, final Class clazz) { ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry(); - if (registry.getComponentDefinition(RPC_REGISTRY_NAME) == null) { - MutableReferenceMetadata metadata = createServiceRef(context, RpcProviderRegistry.class, null); - metadata.setId(RPC_REGISTRY_NAME); + if (registry.getComponentDefinition(name) == null) { + MutableReferenceMetadata metadata = createServiceRef(context, clazz, null); + metadata.setId(name); registry.registerComponentDefinition(metadata); } } 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 90ebeb253c..ffc7940771 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,26 +7,8 @@ */ package org.opendaylight.controller.blueprint.ext; -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; +import java.util.function.Predicate; +import org.opendaylight.controller.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; /** * Factory metadata corresponding to the "rpc-service" element that gets an RPC service implementation from @@ -34,142 +16,13 @@ import org.slf4j.LoggerFactory; * * @author Thomas Pantelis */ -class RpcServiceMetadata extends AbstractDependentComponentFactoryMetadata { - private static final Logger LOG = LoggerFactory.getLogger(RpcServiceMetadata.class); - - private final String interfaceName; - private volatile Set rpcSchemaPaths; - private volatile RpcProviderRegistry rpcRegistry; - private volatile ListenerRegistration rpcListenerReg; - private volatile Class rpcInterface; - +final class RpcServiceMetadata extends AbstractInvokableServiceMetadata { RpcServiceMetadata(final String id, final String interfaceName) { - super(id); - this.interfaceName = interfaceName; - } - - @SuppressWarnings({ "checkstyle:IllegalCatch", "unchecked" }) - @Override - public void init(final ExtendedBlueprintContainer container) { - super.init(container); - - final Class interfaceClass; - try { - interfaceClass = container().getBundleContext().getBundle().loadClass(interfaceName); - } catch (Exception e) { - throw new ComponentDefinitionException(String.format("%s: Error obtaining interface class %s", - logName(), interfaceName), e); - } - - if (!RpcService.class.isAssignableFrom(interfaceClass)) { - throw new ComponentDefinitionException(String.format( - "%s: The specified interface %s is not an RpcService", logName(), interfaceName)); - } - - rpcInterface = (Class)interfaceClass; - } - - @Override - 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())); - } - - private void retrievedSchemaContext(final 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)); - } - - private void retrievedDOMRpcService(final DOMRpcService domRpcService) { - LOG.debug("{}: retrievedDOMRpcService", logName()); - - setDependendencyDesc("Available DOM RPC for binding RPC: " + rpcInterface); - rpcListenerReg = domRpcService.registerRpcListener(new DOMRpcAvailabilityListener() { - @Override - public void onRpcAvailable(final Collection rpcs) { - onRpcsAvailable(rpcs); - } - - @Override - public void onRpcUnavailable(final Collection rpcs) { - } - }); - } - - protected void onRpcsAvailable(final 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("checkstyle:IllegalCatch") - @Override - public Object create() throws ComponentDefinitionException { - LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName); - - super.onCreate(); - - try { - RpcService rpcService = rpcRegistry.getRpcService(rpcInterface); - - LOG.debug("{}: create returning service {}", logName(), rpcService); - - return rpcService; - } catch (RuntimeException e) { - throw new ComponentDefinitionException("Error getting RPC service for " + interfaceName, e); - } - } - - @Override - public void stopTracking() { - super.stopTracking(); - closeRpcListenerReg(); - } - - private void closeRpcListenerReg() { - if (rpcListenerReg != null) { - rpcListenerReg.close(); - rpcListenerReg = null; - } - } - - @Override - public void destroy(final Object instance) { - super.destroy(instance); - closeRpcListenerReg(); + super(id, interfaceName); } @Override - public String toString() { - return "RpcServiceMetadata [id=" + getId() + ", interfaceName=" + interfaceName + "]"; + Predicate rpcFilter() { + return s -> !s.isContextBasedRouted(); } } diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java new file mode 100644 index 0000000000..ee9e36e9d2 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 Cisco 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 java.util.ArrayList; +import java.util.Collection; +import java.util.function.Predicate; +import org.opendaylight.controller.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods for dealing with various aspects of RPCs and actions. + * + * @author Robert Varga + */ +final class RpcUtil { + private static final Logger LOG = LoggerFactory.getLogger(RpcUtil.class); + + private RpcUtil() { + throw new UnsupportedOperationException(); + } + + static Collection decomposeRpcService(final Class service, + final SchemaContext schemaContext, final Predicate filter) { + final QNameModule moduleName = BindingReflections.getQNameModule(service); + final Module module = schemaContext.findModuleByNamespaceAndRevision(moduleName.getNamespace(), + moduleName.getRevision()); + LOG.debug("Resolved service {} to module {}", service, module); + + final Collection rpcs = module.getRpcs(); + final Collection ret = new ArrayList<>(rpcs.size()); + for (RpcDefinition rpc : rpcs) { + final RpcRoutingStrategy strategy = RpcRoutingStrategy.from(rpc); + if (filter.test(strategy)) { + ret.add(rpc.getPath()); + } + } + + return ret; + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java index 02086410f6..c9aab3ba98 100644 --- a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java @@ -103,7 +103,7 @@ class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMet } }; - setDependendencyDesc(interfaceName + " services with types " + expectedServiceTypes); + setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes); serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener); serviceTracker.open(); @@ -175,7 +175,7 @@ class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMet } else { Set remaining = new HashSet<>(expectedServiceTypes); remaining.removeAll(retrievedServiceTypes); - setDependendencyDesc(interfaceName + " services with types " + remaining); + setDependencyDesc(interfaceName + " services with types " + remaining); } } diff --git a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd index a95ba620ea..5bd8ed042c 100644 --- a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd +++ b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd @@ -9,12 +9,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + -- 2.36.6