From 16997ff04c96e198f570d69c4bdcef6659f80b92 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 17 Jan 2024 08:59:32 +0100 Subject: [PATCH] Revert "Remove support for actions/rpc/notifications" This reverts commit beaac808943b25f5627c70ddb5029408101204a7. JIRA: CONTROLLER-2090 Change-Id: I3a624a4c5ef8159d2667794f734609f0a2be3e5b Signed-off-by: Robert Varga --- opendaylight/blueprint/pom.xml | 4 + .../ext/AbstractInvokableServiceMetadata.java | 178 ++++++++++++++ .../blueprint/ext/ActionProviderBean.java | 143 ++++++++++++ .../blueprint/ext/ActionServiceMetadata.java | 39 ++++ .../ext/NotificationListenerBean.java | 58 +++++ .../ext/OpendaylightNamespaceHandler.java | 149 +++++++++++- .../blueprint/ext/RpcImplementationBean.java | 111 +++++++++ .../blueprint/ext/RpcServiceMetadata.java | 29 +++ .../controller/blueprint/ext/RpcUtil.java | 45 ++++ .../ext/SpecificReferenceListMetadata.java | 220 ++++++++++++++++++ .../ext/StaticReferenceMetadata.java | 55 +++++ .../opendaylight-blueprint-ext-1.0.0.xsd | 117 ++++++++++ .../opendaylight-blueprint-ext-2.0.0.xsd | 37 --- 13 files changed, 1146 insertions(+), 39 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/NotificationListenerBean.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java create mode 100644 opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java create mode 100644 opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd delete mode 100644 opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-2.0.0.xsd diff --git a/opendaylight/blueprint/pom.xml b/opendaylight/blueprint/pom.xml index 42d660f988..4eec604143 100644 --- a/opendaylight/blueprint/pom.xml +++ b/opendaylight/blueprint/pom.xml @@ -107,6 +107,10 @@ org.opendaylight.mdsal mdsal-dom-api + + org.opendaylight.mdsal + mdsal-dom-spi + org.opendaylight.mdsal mdsal-binding-api 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..1938fc3332 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/AbstractInvokableServiceMetadata.java @@ -0,0 +1,178 @@ +/* + * 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 static java.util.Objects.requireNonNull; + +import com.google.common.base.MoreObjects; +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.mdsal.binding.api.RpcConsumerRegistry; +import org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener; +import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.mdsal.dom.spi.ContentRoutedRpcContext; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.osgi.service.blueprint.container.ComponentDefinitionException; + +abstract class AbstractInvokableServiceMetadata extends AbstractDependentComponentFactoryMetadata { + private final String interfaceName; + + private ListenerRegistration rpcListenerReg; + private RpcConsumerRegistry rpcRegistry; + private Class rpcInterface; + private Set rpcSchemaPaths; + + AbstractInvokableServiceMetadata(final String id, final String interfaceName) { + super(id); + this.interfaceName = requireNonNull(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 (final 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("RpcConsumerRegistry", RpcConsumerRegistry.class, this::onRpcRegistry); + } + + private void onRpcRegistry(final Object service) { + log.debug("{}: Retrieved RpcProviderRegistry {}", logName(), service); + rpcRegistry = (RpcConsumerRegistry)service; + + // Now acquire SchemaService... + retrieveService("SchemaService", DOMSchemaService.class, this::onSchemaService); + } + + private void onSchemaService(final Object service) { + log.debug("{}: Retrieved SchemaService {}", logName(), service); + + // Now get the SchemaContext and trigger RPC resolution + retrievedSchemaContext(((DOMSchemaService)service).getGlobalContext()); + } + + private void retrievedSchemaContext(final EffectiveModelContext schemaContext) { + log.debug("{}: retrievedSchemaContext", logName()); + + final Collection schemaPaths = RpcUtil.decomposeRpcService(rpcInterface, schemaContext, + rpcFilter()); + if (schemaPaths.isEmpty()) { + log.debug("{}: interface {} has no acceptable entries, assuming it is satisfied", logName(), rpcInterface); + 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 (final 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..5139b5fab6 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionProviderBean.java @@ -0,0 +1,143 @@ +/* + * 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 java.util.Objects; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier; +import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.mdsal.dom.api.DOMRpcProviderService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.util.concurrent.FluentFutures; +import org.opendaylight.yangtools.yang.binding.RpcService; +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 RpcProviderService} + * - 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 + */ +public class ActionProviderBean { + static final String ACTION_PROVIDER = "action-provider"; + + private static final Logger LOG = LoggerFactory.getLogger(ActionProviderBean.class); + + private DOMRpcProviderService domRpcProvider = null; + private RpcProviderService bindingRpcProvider = null; + private DOMSchemaService schemaService = null; + private RpcService implementation = null; + private String interfaceName = null; + private Registration reg; + private Bundle bundle = null; + + 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 setDomRpcProvider(final DOMRpcProviderService rpcProviderService) { + domRpcProvider = rpcProviderService; + } + + public void setBindingRpcProvider(final RpcProviderService rpcProvider) { + bindingRpcProvider = rpcProvider; + } + + public void setSchemaService(final DOMSchemaService schemaService) { + this.schemaService = schemaService; + } + + public void init() { + // First resolve the interface class + final Class interfaceClass = getRpcClass(); + + 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 (final Exception e) { + LOG.warn("{}: error while unregistering", ACTION_PROVIDER, e); + } finally { + reg = null; + } + } + } + + @SuppressWarnings("unchecked") + private Class getRpcClass() { + final Class iface; + + try { + iface = bundle.loadClass(interfaceName); + } catch (final 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 var paths = RpcUtil.decomposeRpcService(interfaceClass, schemaService.getGlobalContext(), + Objects::nonNull); + if (paths.isEmpty()) { + LOG.warn("{}: interface {} has no actions defined", ACTION_PROVIDER, interfaceClass); + return; + } + + final var rpcs = ImmutableSet.copyOf(Collections2.transform(paths, DOMRpcIdentifier::create)); + reg = domRpcProvider.registerRpcImplementation( + (rpc, input) -> FluentFutures.immediateFailedFluentFuture(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 = bindingRpcProvider.registerRpcImplementation(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..7498eabe8c --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/ActionServiceMetadata.java @@ -0,0 +1,39 @@ +/* + * 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.Objects; +import java.util.function.Predicate; +import org.opendaylight.mdsal.dom.api.DOMRpcService; +import org.opendaylight.mdsal.dom.spi.ContentRoutedRpcContext; + +/** + * 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. + */ +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() { + return Objects::nonNull; + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java new file mode 100644 index 0000000000..55be59cea3 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/NotificationListenerBean.java @@ -0,0 +1,58 @@ +/* + * 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 org.opendaylight.mdsal.binding.api.NotificationService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.NotificationListener; +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Blueprint bean corresponding to the "notification-listener" element that registers a NotificationListener + * with the NotificationService. + * + * @author Thomas Pantelis + */ +public class NotificationListenerBean { + private static final Logger LOG = LoggerFactory.getLogger(NotificationListenerBean.class); + static final String NOTIFICATION_LISTENER = "notification-listener"; + + private Bundle bundle = null; + private NotificationService notificationService = null; + private NotificationListener notificationListener = null; + private ListenerRegistration registration = null; + + public void setNotificationService(final NotificationService notificationService) { + this.notificationService = notificationService; + } + + public void setNotificationListener(final NotificationListener notificationListener) { + this.notificationListener = notificationListener; + } + + public void setBundle(final Bundle bundle) { + this.bundle = bundle; + } + + public void init() { + LOG.debug("{}: init - registering NotificationListener {}", bundle.getSymbolicName(), notificationListener); + + registration = notificationService.registerNotificationListener(notificationListener); + } + + public void destroy() { + if (registration != null) { + LOG.debug("{}: destroy - closing ListenerRegistration {}", bundle.getSymbolicName(), notificationListener); + registration.close(); + } else { + LOG.debug("{}: destroy - listener was not registered", bundle.getSymbolicName()); + } + } +} 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 b766e59cdc..af26acae0a 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 @@ -16,6 +16,7 @@ import java.util.Set; import org.apache.aries.blueprint.ComponentDefinitionRegistry; import org.apache.aries.blueprint.NamespaceHandler; import org.apache.aries.blueprint.ParserContext; +import org.apache.aries.blueprint.ext.ComponentFactoryMetadata; import org.apache.aries.blueprint.mutable.MutableBeanMetadata; import org.apache.aries.blueprint.mutable.MutableRefMetadata; import org.apache.aries.blueprint.mutable.MutableReferenceMetadata; @@ -23,6 +24,10 @@ import org.apache.aries.blueprint.mutable.MutableServiceMetadata; import org.apache.aries.blueprint.mutable.MutableServiceReferenceMetadata; import org.apache.aries.blueprint.mutable.MutableValueMetadata; import org.opendaylight.controller.blueprint.BlueprintContainerRestartService; +import org.opendaylight.mdsal.binding.api.NotificationService; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.mdsal.dom.api.DOMRpcProviderService; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; import org.opendaylight.yangtools.util.xml.UntrustedXML; import org.osgi.service.blueprint.container.ComponentDefinitionException; import org.osgi.service.blueprint.reflect.BeanMetadata; @@ -64,7 +69,12 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { private static final String USE_DEFAULT_FOR_REFERENCE_TYPES = "use-default-for-reference-types"; private static final String CLUSTERED_APP_CONFIG = "clustered-app-config"; private static final String INTERFACE = "interface"; + 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"; @SuppressWarnings("rawtypes") @Override @@ -87,8 +97,22 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { public Metadata parse(final Element element, final ParserContext context) { LOG.debug("In parse for {}", element); - if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) { + if (nodeNameEquals(element, RpcImplementationBean.RPC_IMPLEMENTATION)) { + return parseRpcImplementation(element, context); + } else if (nodeNameEquals(element, RPC_SERVICE)) { + return parseRpcService(element, context); + } else if (nodeNameEquals(element, NotificationListenerBean.NOTIFICATION_LISTENER)) { + return parseNotificationListener(element, context); + } else if (nodeNameEquals(element, CLUSTERED_APP_CONFIG)) { return parseClusteredAppConfig(element, context); + } else if (nodeNameEquals(element, SPECIFIC_SERVICE_REF_LIST)) { + 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()); @@ -120,10 +144,12 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { private static ComponentMetadata decorateServiceType(final Attr attr, final ComponentMetadata component, final ParserContext context) { - if (!(component instanceof MutableServiceMetadata service)) { + if (!(component instanceof MutableServiceMetadata)) { throw new ComponentDefinitionException("Expected an instanceof MutableServiceMetadata"); } + MutableServiceMetadata service = (MutableServiceMetadata)component; + LOG.debug("decorateServiceType for {} - adding type property {}", service.getId(), attr.getValue()); service.addServiceProperty(createValue(context, TYPE_ATTR), createValue(context, attr.getValue())); @@ -209,6 +235,107 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { return metadata; } + private static Metadata parseActionProvider(final Element element, final ParserContext context) { + registerDomRpcProviderServiceRefBean(context); + registerBindingRpcProviderServiceRefBean(context); + registerSchemaServiceRefBean(context); + + MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), ActionProviderBean.class, + true, true); + addBlueprintBundleRefProperty(context, metadata); + metadata.addProperty("domRpcProvider", createRef(context, DOM_RPC_PROVIDER_SERVICE_NAME)); + metadata.addProperty("bindingRpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_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) { + registerBindingRpcProviderServiceRefBean(context); + + MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), RpcImplementationBean.class, + true, true); + addBlueprintBundleRefProperty(context, metadata); + metadata.addProperty("rpcProvider", createRef(context, BINDING_RPC_PROVIDER_SERVICE_NAME)); + metadata.addProperty("implementation", createRef(context, element.getAttribute(REF_ATTR))); + + if (element.hasAttribute(INTERFACE)) { + metadata.addProperty("interfaceName", createValue(context, element.getAttribute(INTERFACE))); + } + + LOG.debug("parseRpcImplementation returning {}", metadata); + 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)); + + LOG.debug("parseRpcService returning {}", metadata); + + return metadata; + } + + private static void registerDomRpcProviderServiceRefBean(final ParserContext context) { + registerRefBean(context, DOM_RPC_PROVIDER_SERVICE_NAME, DOMRpcProviderService.class); + } + + private static void registerBindingRpcProviderServiceRefBean(final ParserContext context) { + registerRefBean(context, BINDING_RPC_PROVIDER_SERVICE_NAME, RpcProviderService.class); + } + + private static void registerSchemaServiceRefBean(final ParserContext context) { + registerRefBean(context, SCHEMA_SERVICE_NAME, DOMSchemaService.class); + } + + private static void registerRefBean(final ParserContext context, final String name, final Class clazz) { + ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry(); + if (registry.getComponentDefinition(name) == null) { + MutableReferenceMetadata metadata = createServiceRef(context, clazz, null); + metadata.setId(name); + registry.registerComponentDefinition(metadata); + } + } + + private static Metadata parseNotificationListener(final Element element, final ParserContext context) { + registerNotificationServiceRefBean(context); + + MutableBeanMetadata metadata = createBeanMetadata(context, context.generateId(), NotificationListenerBean.class, + true, true); + addBlueprintBundleRefProperty(context, metadata); + metadata.addProperty("notificationService", createRef(context, NOTIFICATION_SERVICE_NAME)); + metadata.addProperty("notificationListener", createRef(context, element.getAttribute(REF_ATTR))); + + LOG.debug("parseNotificationListener returning {}", metadata); + + return metadata; + } + + private static void registerNotificationServiceRefBean(final ParserContext context) { + ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry(); + if (registry.getComponentDefinition(NOTIFICATION_SERVICE_NAME) == null) { + MutableReferenceMetadata metadata = createServiceRef(context, NotificationService.class, null); + metadata.setId(NOTIFICATION_SERVICE_NAME); + registry.registerComponentDefinition(metadata); + } + } + private static Metadata parseClusteredAppConfig(final Element element, final ParserContext context) { LOG.debug("parseClusteredAppConfig"); @@ -256,6 +383,24 @@ public final class OpendaylightNamespaceHandler implements NamespaceHandler { } } + private static Metadata parseSpecificReferenceList(final Element element, final ParserContext context) { + ComponentFactoryMetadata metadata = new SpecificReferenceListMetadata(getId(context, element), + element.getAttribute(INTERFACE)); + + LOG.debug("parseSpecificReferenceList returning {}", metadata); + + return metadata; + } + + private static Metadata parseStaticReference(final Element element, final ParserContext context) { + ComponentFactoryMetadata metadata = new StaticReferenceMetadata(getId(context, element), + element.getAttribute(INTERFACE)); + + LOG.debug("parseStaticReference returning {}", metadata); + + return metadata; + } + private static Element parseXML(final String name, final String xml) { try { return UntrustedXML.newDocumentBuilder().parse(new InputSource(new StringReader(xml))).getDocumentElement(); diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java new file mode 100644 index 0000000000..a8fc47ab45 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcImplementationBean.java @@ -0,0 +1,111 @@ +/* + * 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.Strings; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.opendaylight.mdsal.binding.api.RpcProviderService; +import org.opendaylight.yangtools.concepts.ObjectRegistration; +import org.opendaylight.yangtools.yang.binding.RpcService; +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 "rpc-implementation" element that registers an RPC implementation with + * the RpcProviderRegistry. + * + * @author Thomas Pantelis + */ +public class RpcImplementationBean { + private static final Logger LOG = LoggerFactory.getLogger(RpcImplementationBean.class); + static final String RPC_IMPLEMENTATION = "rpc-implementation"; + + private final List> rpcRegistrations = new ArrayList<>(); + private RpcProviderService rpcProvider = null; + private Bundle bundle = null; + private String interfaceName = null; + private RpcService implementation = null; + + public void setRpcProvider(final RpcProviderService rpcProvider) { + this.rpcProvider = rpcProvider; + } + + 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; + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public void init() { + try { + final var rpcInterfaces = getImplementedRpcServiceInterfaces(interfaceName, + implementation.getClass(), bundle, RPC_IMPLEMENTATION); + + LOG.debug("{}: init - adding implementation {} for RpcService interface(s) {}", bundle.getSymbolicName(), + implementation, rpcInterfaces); + + for (var rpcInterface : rpcInterfaces) { + rpcRegistrations.add(rpcProvider.registerRpcImplementation(rpcInterface, implementation)); + } + } catch (final ComponentDefinitionException e) { + throw e; + } catch (final Exception e) { + throw new ComponentDefinitionException(String.format( + "Error processing \"%s\" for %s", RPC_IMPLEMENTATION, implementation.getClass()), e); + } + } + + public void destroy() { + for (ObjectRegistration reg: rpcRegistrations) { + reg.close(); + } + } + + @SuppressWarnings("unchecked") + static List> getImplementedRpcServiceInterfaces(final String interfaceName, + final Class implementationClass, final Bundle bundle, final String logName) + throws ClassNotFoundException { + if (!Strings.isNullOrEmpty(interfaceName)) { + Class rpcInterface = bundle.loadClass(interfaceName); + + if (!rpcInterface.isAssignableFrom(implementationClass)) { + throw new ComponentDefinitionException(String.format( + "The specified \"interface\" %s for \"%s\" is not implemented by RpcService \"ref\" %s", + interfaceName, logName, implementationClass)); + } + + return Collections.singletonList((Class)rpcInterface); + } + + final var rpcInterfaces = new ArrayList>(); + for (var intface : implementationClass.getInterfaces()) { + if (RpcService.class.isAssignableFrom(intface)) { + rpcInterfaces.add((Class) intface); + } + } + + if (rpcInterfaces.isEmpty()) { + throw new ComponentDefinitionException(String.format( + "The \"ref\" instance %s for \"%s\" does not implemented any RpcService interfaces", + implementationClass, logName)); + } + + return rpcInterfaces; + } +} 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 new file mode 100644 index 0000000000..28590c141b --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcServiceMetadata.java @@ -0,0 +1,29 @@ +/* + * 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 java.util.Objects; +import java.util.function.Predicate; +import org.opendaylight.mdsal.dom.spi.ContentRoutedRpcContext; + +/** + * Factory metadata corresponding to the "rpc-service" element that gets an RPC service implementation from + * the RpcProviderRegistry and provides it to the Blueprint container. + * + * @author Thomas Pantelis + */ +final class RpcServiceMetadata extends AbstractInvokableServiceMetadata { + RpcServiceMetadata(final String id, final String interfaceName) { + super(id, interfaceName); + } + + @Override + Predicate rpcFilter() { + return Objects::isNull; + } +} 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..0d17e38b1a --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/RpcUtil.java @@ -0,0 +1,45 @@ +/* + * 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.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; +import org.opendaylight.mdsal.dom.spi.ContentRoutedRpcContext; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods for dealing with various aspects of RPCs and actions. + */ +final class RpcUtil { + private static final Logger LOG = LoggerFactory.getLogger(RpcUtil.class); + + private RpcUtil() { + // Hidden on purpose + } + + static List decomposeRpcService(final Class service, + final EffectiveModelContext schemaContext, final Predicate filter) { + final var moduleName = BindingReflections.getQNameModule(service); + final var module = schemaContext.findModuleStatement(moduleName) + .orElseThrow(() -> new IllegalArgumentException("Module not found in SchemaContext: " + moduleName + + "; service: " + service)); + LOG.debug("Resolved service {} to module {}", service, module); + + return module.streamEffectiveSubstatements(RpcEffectiveStatement.class) + .filter(rpc -> filter.test(ContentRoutedRpcContext.forRpc(rpc))) + .map(RpcEffectiveStatement::argument) + .collect(Collectors.toList()); + } +} 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 new file mode 100644 index 0000000000..a98a062993 --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/SpecificReferenceListMetadata.java @@ -0,0 +1,220 @@ +/* + * 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.io.Resources; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.ServiceReference; +import org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.BundleTrackerCustomizer; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory metadata corresponding to the "specific-reference-list" element that obtains a specific list + * of service instances from the OSGi registry for a given interface. The specific list is learned by first + * extracting the list of expected service types by inspecting RESOLVED bundles for a resource file under + * META-INF/services with the same name as the given interface. The type(s) listed in the resource file + * must match the "type" property of the advertised service(s). In this manner, an app bundle announces the + * service type(s) that it will advertise so that this class knows which services to expect up front. Once + * all the expected services are obtained, the container is notified that all dependencies of this component + * factory are satisfied. + * + * @author Thomas Pantelis + */ +class SpecificReferenceListMetadata extends AbstractDependentComponentFactoryMetadata { + private static final Logger LOG = LoggerFactory.getLogger(SpecificReferenceListMetadata.class); + + private final String interfaceName; + private final String serviceResourcePath; + private final Collection expectedServiceTypes = new ConcurrentSkipListSet<>(); + private final Collection retrievedServiceTypes = new ConcurrentSkipListSet<>(); + private final Collection retrievedServices = Collections.synchronizedList(new ArrayList<>()); + private volatile BundleTracker bundleTracker; + private volatile ServiceTracker serviceTracker; + + SpecificReferenceListMetadata(final String id, final String interfaceName) { + super(id); + this.interfaceName = interfaceName; + serviceResourcePath = "META-INF/services/" + interfaceName; + } + + @Override + protected void startTracking() { + BundleTrackerCustomizer bundleListener = new BundleTrackerCustomizer<>() { + @Override + public Bundle addingBundle(final Bundle bundle, final BundleEvent event) { + bundleAdded(bundle); + return bundle; + } + + @Override + public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) { + } + + @Override + public void removedBundle(final Bundle bundle, final BundleEvent event, final Bundle object) { + } + }; + + bundleTracker = new BundleTracker<>(container().getBundleContext(), Bundle.RESOLVED | Bundle.STARTING + | Bundle.STOPPING | Bundle.ACTIVE, bundleListener); + + // This will get the list of all current RESOLVED+ bundles. + bundleTracker.open(); + + if (expectedServiceTypes.isEmpty()) { + setSatisfied(); + return; + } + + ServiceTrackerCustomizer serviceListener = new ServiceTrackerCustomizer<>() { + @Override + public Object addingService(final ServiceReference reference) { + return serviceAdded(reference); + } + + @Override + public void modifiedService(final ServiceReference reference, final Object service) { + } + + @Override + public void removedService(final ServiceReference reference, final Object service) { + container().getBundleContext().ungetService(reference); + } + }; + + setDependencyDesc(interfaceName + " services with types " + expectedServiceTypes); + + serviceTracker = new ServiceTracker<>(container().getBundleContext(), interfaceName, serviceListener); + serviceTracker.open(); + } + + private void bundleAdded(final Bundle bundle) { + URL resource = bundle.getEntry(serviceResourcePath); + if (resource == null) { + return; + } + + LOG.debug("{}: Found {} resource in bundle {}", logName(), resource, bundle.getSymbolicName()); + + try { + for (String line : Resources.readLines(resource, StandardCharsets.UTF_8)) { + int ci = line.indexOf('#'); + if (ci >= 0) { + line = line.substring(0, ci); + } + + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + String serviceType = line; + LOG.debug("{}: Retrieved service type {}", logName(), serviceType); + expectedServiceTypes.add(serviceType); + } + } catch (final IOException e) { + setFailure(String.format("%s: Error reading resource %s from bundle %s", logName(), resource, + bundle.getSymbolicName()), e); + } + } + + private Object serviceAdded(final ServiceReference reference) { + Object service = container().getBundleContext().getService(reference); + String serviceType = (String) reference.getProperty(OpendaylightNamespaceHandler.TYPE_ATTR); + + LOG.debug("{}: Service type {} added from bundle {}", logName(), serviceType, + reference.getBundle().getSymbolicName()); + + if (serviceType == null) { + LOG.error("{}: Missing OSGi service property '{}' for service interface {} in bundle {}", logName(), + OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName, reference.getBundle().getSymbolicName()); + return service; + } + + if (!expectedServiceTypes.contains(serviceType)) { + LOG.error("{}: OSGi service property '{}' for service interface {} in bundle {} was not found in the " + + "expected service types {} obtained via {} bundle resources. Is the bundle resource missing or " + + "the service type misspelled?", logName(), OpendaylightNamespaceHandler.TYPE_ATTR, interfaceName, + reference.getBundle().getSymbolicName(), expectedServiceTypes, serviceResourcePath); + return service; + } + + // If already satisfied, meaning we got all initial services, then a new bundle must've been + // dynamically installed or a prior service's blueprint container was restarted, in which case we + // restart our container. + if (isSatisfied()) { + restartContainer(); + } else { + retrievedServiceTypes.add(serviceType); + retrievedServices.add(service); + + if (retrievedServiceTypes.equals(expectedServiceTypes)) { + LOG.debug("{}: Got all expected service types", logName()); + setSatisfied(); + } else { + Set remaining = new HashSet<>(expectedServiceTypes); + remaining.removeAll(retrievedServiceTypes); + setDependencyDesc(interfaceName + " services with types " + remaining); + } + } + + return service; + } + + @Override + public Object create() throws ComponentDefinitionException { + LOG.debug("{}: In create: interfaceName: {}", logName(), interfaceName); + + super.onCreate(); + + LOG.debug("{}: create returning service list {}", logName(), retrievedServices); + + synchronized (retrievedServices) { + return List.copyOf(retrievedServices); + } + } + + @Override + public void destroy(final Object instance) { + super.destroy(instance); + + if (bundleTracker != null) { + bundleTracker.close(); + bundleTracker = null; + } + + if (serviceTracker != null) { + serviceTracker.close(); + serviceTracker = null; + } + } + + @Override + public String toString() { + return new StringBuilder() + .append("SpecificReferenceListMetadata [interfaceName=").append(interfaceName) + .append(", serviceResourcePath=").append(serviceResourcePath).append("]") + .toString(); + } +} diff --git a/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java new file mode 100644 index 0000000000..97c04af56f --- /dev/null +++ b/opendaylight/blueprint/src/main/java/org/opendaylight/controller/blueprint/ext/StaticReferenceMetadata.java @@ -0,0 +1,55 @@ +/* + * 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 org.osgi.service.blueprint.container.ComponentDefinitionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory metadata corresponding to the "static-reference" element that obtains an OSGi service and + * returns the actual instance. This differs from the standard "reference" element that returns a dynamic + * proxy whose underlying service instance can come and go. + * + * @author Thomas Pantelis + */ +class StaticReferenceMetadata extends AbstractDependentComponentFactoryMetadata { + private static final Logger LOG = LoggerFactory.getLogger(StaticReferenceMetadata.class); + + private final String interfaceName; + private volatile Object retrievedService; + + StaticReferenceMetadata(final String id, final String interfaceName) { + super(id); + this.interfaceName = interfaceName; + } + + @Override + protected void startTracking() { + retrieveService(interfaceName, interfaceName, service -> { + retrievedService = service; + setSatisfied(); + }); + } + + @Override + public Object create() throws ComponentDefinitionException { + super.onCreate(); + + LOG.debug("{}: create returning service {}", logName(), retrievedService); + + return retrievedService; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("StaticReferenceMetadata [interfaceName=").append(interfaceName).append("]"); + return builder.toString(); + } +} 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 new file mode 100644 index 0000000000..297bbb4394 --- /dev/null +++ b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-1.0.0.xsd @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-2.0.0.xsd b/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-2.0.0.xsd deleted file mode 100644 index 1a1683e8f0..0000000000 --- a/opendaylight/blueprint/src/main/resources/opendaylight-blueprint-ext-2.0.0.xsd +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- 2.36.6