From c2fbe8c5fa6d00473aa49b50b557ead738dc6a44 Mon Sep 17 00:00:00 2001 From: Tony Tkacik Date: Mon, 27 Apr 2015 14:16:06 +0200 Subject: [PATCH] Bug 2351 Speed-up Binding-to-Binding routed RPC Invocation. Current RPC Broker uses LazySerializedContainerNodes when going thru DOM RPC broker, so in case RPC is returned it can by-pass deserialization and use Binding DTO directly. In case of routed RPCs full serialization to normalized node was triggered when DOM RPC broker tried to read RPC route. This patchset introduces support for LazySerializedContainerNode to precompute this value and do not use full serialization when DOM Rpc Broker reads only routing context. Change-Id: I6d949b5257d40a96ae9edce3bf15c4c3ff932c27 Signed-off-by: Tony Tkacik (cherry picked from commit 313b9c3cdff6c7fbf94583db4b8933aa394bccc0) --- .../BindingDOMRpcProviderServiceAdapter.java | 2 +- .../impl/BindingDOMRpcServiceAdapter.java | 56 +---- .../impl/BindingToNormalizedNodeCodec.java | 60 +++++- .../impl/ContextReferenceExtractor.java | 108 ++++++++++ .../DirectGetterRouteContextExtractor.java | 42 ++++ .../impl/GetValueRouteContextExtractor.java | 51 +++++ .../impl/LazySerializedContainerNode.java | 68 ++++-- .../sal/binding/impl/RpcServiceAdapter.java | 197 ++++++++++++++---- .../binding/impl/ContextExtractorTest.java | 74 +++++++ 9 files changed, 547 insertions(+), 111 deletions(-) create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/ContextReferenceExtractor.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/DirectGetterRouteContextExtractor.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/GetValueRouteContextExtractor.java create mode 100644 opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/ContextExtractorTest.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcProviderServiceAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcProviderServiceAdapter.java index ba822989d8..05f11943cd 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcProviderServiceAdapter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcProviderServiceAdapter.java @@ -63,7 +63,7 @@ public class BindingDOMRpcProviderServiceAdapter { private Set toYangInstanceIdentifiers(final Set> identifiers) { final Set ret = new HashSet<>(); for(final InstanceIdentifier binding: identifiers) { - ret.add(codec.toNormalized(binding)); + ret.add(codec.toYangInstanceIdentifierCached(binding)); } return ret; } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcServiceAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcServiceAdapter.java index 6b64b7ea55..90d91458e1 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcServiceAdapter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcServiceAdapter.java @@ -7,38 +7,21 @@ */ package org.opendaylight.controller.md.sal.binding.impl; -import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ClassToInstanceMap; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import java.lang.reflect.Method; import java.util.Set; import org.opendaylight.controller.md.sal.binding.impl.BindingDOMAdapterBuilder.Factory; -import org.opendaylight.controller.md.sal.binding.impl.RpcServiceAdapter.InvocationDelegate; -import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; -import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; import org.opendaylight.controller.md.sal.dom.api.DOMService; import org.opendaylight.controller.sal.binding.api.RpcConsumerRegistry; -import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; -import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.RpcService; import org.opendaylight.yangtools.yang.binding.util.BindingReflections; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.common.RpcResultBuilder; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.model.api.SchemaPath; -public class BindingDOMRpcServiceAdapter implements RpcConsumerRegistry, InvocationDelegate { +public class BindingDOMRpcServiceAdapter implements RpcConsumerRegistry { protected static final Factory BUILDER_FACTORY = new Factory() { @@ -78,45 +61,10 @@ public class BindingDOMRpcServiceAdapter implements RpcConsumerRegistry, Invocat return proxy; } - @Override - public ListenableFuture> invoke(final SchemaPath rpc, final DataObject input) { - final CheckedFuture domFuture = domService.invokeRpc(rpc, serialize(rpc,input)); - return transformFuture(rpc,domFuture,codec.getCodecRegistry()); - } - private RpcServiceAdapter createProxy(final Class key) { Preconditions.checkArgument(BindingReflections.isBindingClass(key)); Preconditions.checkArgument(key.isInterface(), "Supplied RPC service type must be interface."); - final ImmutableMap rpcNames = codec.getRpcMethodToSchemaPath(key); - return new RpcServiceAdapter(key, rpcNames, this); - } - - private NormalizedNode serialize(final SchemaPath rpc,final DataObject input) { - if(input == null) { - return null; - } - final QName rpcInputIdentifier = QName.create(rpc.getLastComponent(),"input"); - return new LazySerializedContainerNode(rpcInputIdentifier, input, codec.getCodecRegistry()); - } - - private static ListenableFuture> transformFuture(final SchemaPath rpc,final ListenableFuture domFuture, final BindingNormalizedNodeCodecRegistry codec) { - return Futures.transform(domFuture, new Function>() { - @Override - public RpcResult apply(final DOMRpcResult input) { - if(input instanceof LazySerializedDOMRpcResult) { - return ((LazySerializedDOMRpcResult) input).bidningRpcResult(); - } - final NormalizedNode domData = input.getResult(); - final DataObject bindingResult; - if(domData != null) { - final SchemaPath rpcOutput = rpc.createChild(QName.create(rpc.getLastComponent(),"output")); - bindingResult = codec.fromNormalizedNodeRpcData(rpcOutput, (ContainerNode) domData); - } else { - bindingResult = null; - } - return RpcResult.class.cast(RpcResultBuilder.success(bindingResult).build()); - } - }); + return new RpcServiceAdapter(key, codec, domService); } private static final class Builder extends BindingDOMAdapterBuilder { diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java index ab07f50941..3795ad3bac 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java @@ -10,6 +10,9 @@ package org.opendaylight.controller.md.sal.binding.impl; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableBiMap; import java.lang.reflect.Method; import java.util.AbstractMap.SimpleEntry; @@ -49,20 +52,39 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.opendaylight.yangtools.yang.model.api.SchemaPath; -public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable { +public final class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable { private final BindingNormalizedNodeCodecRegistry codecRegistry; private DataNormalizer legacyToNormalized; private final GeneratedClassLoadingStrategy classLoadingStrategy; private BindingRuntimeContext runtimeContext; + private final LoadingCache, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder() + .softValues().build(new CacheLoader, YangInstanceIdentifier>() { + + @Override + public YangInstanceIdentifier load(final InstanceIdentifier key) throws Exception { + return toYangInstanceIdentifier(key); + } + + }); public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy, final BindingNormalizedNodeCodecRegistry codecRegistry) { - this.classLoadingStrategy = classLoadingStrategy; - this.codecRegistry = codecRegistry; + this.classLoadingStrategy = Preconditions.checkNotNull(classLoadingStrategy,"classLoadingStrategy"); + this.codecRegistry = Preconditions.checkNotNull(codecRegistry,"codecRegistry"); } + /** + * Translates supplied Binding Instance Identifier into NormalizedNode + * instance identifier. + * + * @param binding + * Binding Instance Identifier + * @return DOM Instance Identifier + * @throws IllegalArgumentException + * If supplied Instance Identifier is not valid. + */ public YangInstanceIdentifier toNormalized(final InstanceIdentifier binding) { return codecRegistry.toYangInstanceIdentifier(binding); } @@ -72,12 +94,26 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi return codecRegistry.toYangInstanceIdentifier(binding); } + + YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier binding) { + return iiCache .getUnchecked(binding); + } + @Override public Entry> toNormalizedNode( final InstanceIdentifier path, final T data) { return codecRegistry.toNormalizedNode(path, data); } + /** + * Converts Binding Map.Entry to DOM Map.Entry + * + * Same as {@link #toNormalizedNode(InstanceIdentifier, DataObject)}. + * + * @param binding Map Entry with InstanceIdentifier as key and DataObject as value. + * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode} + * as value. + */ @SuppressWarnings({"unchecked", "rawtypes"}) public Entry> toNormalizedNode( final Entry, DataObject> binding) { @@ -222,6 +258,21 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi return ret.build(); } + protected ImmutableBiMap getRpcMethodToSchema(final Class key) { + final QNameModule moduleName = BindingReflections.getQNameModule(key); + final Module module = runtimeContext.getSchemaContext().findModuleByNamespaceAndRevision(moduleName.getNamespace(), moduleName.getRevision()); + final ImmutableBiMap.Builder ret = ImmutableBiMap.builder(); + try { + for (final RpcDefinition rpcDef : module.getRpcs()) { + final Method method = findRpcMethod(key, rpcDef); + ret.put(method, rpcDef); + } + } catch (final NoSuchMethodException e) { + throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e); + } + return ret.build(); + } + private Method findRpcMethod(final Class key, final RpcDefinition rpcDef) throws NoSuchMethodException { final String methodName = BindingMapping.getMethodName(rpcDef.getQName()); if(rpcDef.getInput() != null) { @@ -241,7 +292,8 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi return codecRegistry.create(context, bindingClasses); } - @Nonnull protected Map.Entry, BindingCodecTreeNode> getSubtreeCodec( + @Nonnull + protected Map.Entry, BindingCodecTreeNode> getSubtreeCodec( final YangInstanceIdentifier domIdentifier) { final BindingCodecTree currentCodecTree = codecRegistry.getCodecContext(); diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/ContextReferenceExtractor.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/ContextReferenceExtractor.java new file mode 100644 index 0000000000..4653ca399e --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/ContextReferenceExtractor.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.lang.reflect.Method; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.annotations.RoutingContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +abstract class ContextReferenceExtractor { + + private static final Logger LOG = LoggerFactory.getLogger(ContextReferenceExtractor.class); + private static final ContextReferenceExtractor NULL_EXTRACTOR = new ContextReferenceExtractor() { + + @Override + InstanceIdentifier extract(final DataObject obj) { + return null; + } + }; + + + private static final LoadingCache, ContextReferenceExtractor> EXTRACTORS = CacheBuilder.newBuilder() + .weakKeys().build(new CacheLoader, ContextReferenceExtractor>() { + + @Override + public ContextReferenceExtractor load(final Class key) throws Exception { + return create(key); + } + }); + + + private static final String GET_VALUE_NAME = "getValue"; + + static ContextReferenceExtractor from(final Class obj) { + return EXTRACTORS.getUnchecked(obj); + } + + /** + * Extract context-reference (Instance Identifier) from + * Binding DataObject. + * + * @param obj DataObject from which context reference + * should be extracted. + * + * @return Instance Identifier representing context reference + * or null, if data object does not contain context reference. + */ + abstract @Nullable InstanceIdentifier extract(DataObject obj); + + private static @Nonnull ContextReferenceExtractor create(final Class key) { + final Method contextGetter = getContextGetter(key); + if (contextGetter == null) { + return NULL_EXTRACTOR; + } + final Class returnType = contextGetter.getReturnType(); + try { + if (InstanceIdentifier.class.isAssignableFrom(returnType)) { + return DirectGetterRouteContextExtractor.create(contextGetter); + } + final Method getValueMethod = findGetValueMethod(returnType,InstanceIdentifier.class); + if (getValueMethod != null) { + return GetValueRouteContextExtractor.create(contextGetter, getValueMethod); + } else { + LOG.warn("Class {} can not be used to determine context, falling back to NULL_EXTRACTOR.",returnType); + } + } catch (final IllegalAccessException e) { + LOG.warn("Class {} does not conform to Binding Specification v1. Falling back to NULL_EXTRACTOR", e); + } + return NULL_EXTRACTOR; + } + + private static @Nullable Method findGetValueMethod(final Class type, final Class returnType) { + try { + final Method method = type.getMethod(GET_VALUE_NAME); + if(returnType.equals(method.getReturnType())) { + return method; + } + } catch (final NoSuchMethodException e) { + LOG.warn("Value class {} does not comform to Binding Specification v1.", type, e); + } + return null; + } + + private static Method getContextGetter(final Class key) { + for (final Method method : key.getMethods()) { + if (method.getAnnotation(RoutingContext.class) != null) { + return method; + } + } + return null; + } + + + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/DirectGetterRouteContextExtractor.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/DirectGetterRouteContextExtractor.java new file mode 100644 index 0000000000..33e0582f2a --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/DirectGetterRouteContextExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Throwables; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class DirectGetterRouteContextExtractor extends ContextReferenceExtractor { + + private final static Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup(); + private final MethodHandle handle; + + private DirectGetterRouteContextExtractor(final MethodHandle rawHandle) { + handle = rawHandle.asType(MethodType.methodType(InstanceIdentifier.class, DataObject.class)); + } + + static final ContextReferenceExtractor create(final Method getterMethod) throws IllegalAccessException { + final MethodHandle getterHandle = PUBLIC_LOOKUP.unreflect(getterMethod); + return new DirectGetterRouteContextExtractor(getterHandle); + } + + @Override + InstanceIdentifier extract(final DataObject obj) { + try { + return (InstanceIdentifier) handle.invokeExact(obj); + } catch (final Throwable e) { + throw Throwables.propagate(e); + } + } + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/GetValueRouteContextExtractor.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/GetValueRouteContextExtractor.java new file mode 100644 index 0000000000..d5d5a72d4d --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/GetValueRouteContextExtractor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import com.google.common.base.Throwables; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +final class GetValueRouteContextExtractor extends ContextReferenceExtractor { + + private final static Lookup PUBLIC_LOOKUP = MethodHandles.publicLookup(); + private final MethodHandle contextHandle; + private final MethodHandle valueHandle; + + private GetValueRouteContextExtractor(final MethodHandle rawContextHandle, final MethodHandle rawValueHandle) { + contextHandle = rawContextHandle.asType(MethodType.methodType(Object.class, DataObject.class)); + valueHandle = rawValueHandle.asType(MethodType.methodType(InstanceIdentifier.class, Object.class)); + } + + public static ContextReferenceExtractor create(final Method contextGetter, final Method getValueMethod) + throws IllegalAccessException { + final MethodHandle rawContextHandle = PUBLIC_LOOKUP.unreflect(contextGetter); + final MethodHandle rawValueHandle = PUBLIC_LOOKUP.unreflect(getValueMethod); + return new GetValueRouteContextExtractor(rawContextHandle, rawValueHandle); + } + + @Override + InstanceIdentifier extract(final DataObject obj) { + try { + final Object ctx = contextHandle.invokeExact(obj); + if (ctx != null) { + return (InstanceIdentifier) valueHandle.invokeExact(ctx); + } + return null; + } catch (final Throwable e) { + throw Throwables.propagate(e); + } + } + + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazySerializedContainerNode.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazySerializedContainerNode.java index 9eb4ed76f6..332df7531d 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazySerializedContainerNode.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazySerializedContainerNode.java @@ -1,9 +1,9 @@ /* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 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 + * 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.md.sal.binding.impl; @@ -17,6 +17,9 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** * @@ -31,21 +34,31 @@ class LazySerializedContainerNode implements ContainerNode { private BindingNormalizedNodeCodecRegistry registry; private ContainerNode domData; - LazySerializedContainerNode(QName identifier, DataObject binding, - BindingNormalizedNodeCodecRegistry registry) { + private LazySerializedContainerNode(final QName identifier, final DataObject binding, + final BindingNormalizedNodeCodecRegistry registry) { this.identifier = new NodeIdentifier(identifier); this.bindingData = binding; this.registry = registry; this.domData = null; } + static NormalizedNode create(final SchemaPath rpcName, final DataObject data, + final BindingNormalizedNodeCodecRegistry codec) { + return new LazySerializedContainerNode(rpcName.getLastComponent(), data, codec); + } + + static NormalizedNode withContextRef(final SchemaPath rpcName, final DataObject data, + final LeafNode contextRef, final BindingNormalizedNodeCodecRegistry codec) { + return new WithContextRef(rpcName.getLastComponent(), data, contextRef, codec); + } + @Override public Map getAttributes() { return delegate().getAttributes(); } private ContainerNode delegate() { - if(domData == null) { + if (domData == null) { domData = registry.toNormalizedNodeRpcData(bindingData); registry = null; } @@ -53,32 +66,59 @@ class LazySerializedContainerNode implements ContainerNode { } @Override - public QName getNodeType() { - return delegate().getNodeType(); + public final QName getNodeType() { + return identifier.getNodeType(); } @Override - public Collection> getValue() { + public final Collection> getValue() { return delegate().getValue(); } @Override - public NodeIdentifier getIdentifier() { + public final NodeIdentifier getIdentifier() { return identifier; } @Override - public Optional> getChild(PathArgument child) { + public Optional> getChild(final PathArgument child) { return delegate().getChild(child); } @Override - public Object getAttributeValue(QName name) { + public final Object getAttributeValue(final QName name) { return delegate().getAttributeValue(name); } - public DataObject bindingData() { + final DataObject bindingData() { return bindingData; } + /** + * Lazy Serialized Node with pre-cached serialized leaf holding routing information. + * + */ + private static final class WithContextRef extends LazySerializedContainerNode { + + private final LeafNode contextRef; + + protected WithContextRef(final QName identifier, final DataObject binding, final LeafNode contextRef, + final BindingNormalizedNodeCodecRegistry registry) { + super(identifier, binding, registry); + this.contextRef = contextRef; + } + + @Override + public Optional> getChild(final PathArgument child) { + /* + * Use precached value of routing field and do not run full serialization if we are + * accessing it. + */ + if (contextRef.getIdentifier().equals(child)) { + return Optional.>of(contextRef); + } + return super.getChild(child); + } + } + } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/RpcServiceAdapter.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/RpcServiceAdapter.java index 61b32324a9..7f2a074af2 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/RpcServiceAdapter.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/RpcServiceAdapter.java @@ -1,40 +1,76 @@ /* - * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 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 + * 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.md.sal.binding.impl; +import com.google.common.base.Function; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.util.Map.Entry; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; +import org.opendaylight.controller.md.sal.dom.broker.spi.rpc.RpcRoutingStrategy; +import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; +import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.binding.util.BindingReflections; +import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaPath; class RpcServiceAdapter implements InvocationHandler { - interface InvocationDelegate { - - ListenableFuture> invoke(SchemaPath rpc, DataObject dataObject); + private final ImmutableMap rpcNames; + private final Class type; + private final BindingToNormalizedNodeCodec codec; + private final DOMRpcService delegate; + private final RpcService proxy; + RpcServiceAdapter(final Class type, final BindingToNormalizedNodeCodec codec, + final DOMRpcService domService) { + this.type = Preconditions.checkNotNull(type); + this.codec = Preconditions.checkNotNull(codec); + this.delegate = Preconditions.checkNotNull(domService); + final ImmutableMap.Builder rpcBuilder = ImmutableMap.builder(); + for (final Entry rpc : codec.getRpcMethodToSchema(type).entrySet()) { + rpcBuilder.put(rpc.getKey(), createStrategy(rpc.getKey(), rpc.getValue())); + } + rpcNames = rpcBuilder.build(); + proxy = (RpcService) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, this); } - private final RpcService proxy; - private final ImmutableMap rpcNames; - private final Class type; - private final InvocationDelegate delegate; + private final ListenableFuture> invoke0(final SchemaPath schemaPath, final NormalizedNode input) { + final CheckedFuture result = delegate.invokeRpc(schemaPath, input); + return transformFuture(schemaPath, result, codec.getCodecFactory()); + } - RpcServiceAdapter(Class type, ImmutableMap rpcNames, InvocationDelegate delegate) { - this.rpcNames = rpcNames; - this.type = type; - this.delegate = delegate; - this.proxy = (RpcService) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, this); + private RpcInvocationStrategy createStrategy(final Method method, final RpcDefinition schema) { + final RpcRoutingStrategy strategy = RpcRoutingStrategy.from(schema); + if (strategy.isContextBasedRouted()) { + return new RoutedStrategy(schema.getPath(), method, strategy.getLeaf()); + } + return new NonRoutedStrategy(schema.getPath()); } RpcService getProxy() { @@ -42,46 +78,131 @@ class RpcServiceAdapter implements InvocationHandler { } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - SchemaPath rpc = rpcNames.get(method); - if(rpc != null) { - if(method.getParameterTypes().length == 0) { - return delegate.invoke(rpc, null); + final RpcInvocationStrategy rpc = rpcNames.get(method); + if (rpc != null) { + if (method.getParameterTypes().length == 0) { + return rpc.invokeEmpty(); } - if(args.length != 1) { + if (args.length != 1) { throw new IllegalArgumentException("Input must be provided."); } - return delegate.invoke(rpc,(DataObject) args[0]); + return rpc.invoke((DataObject) args[0]); } - if(isObjectMethod(method)) { + if (isObjectMethod(method)) { return callObjectMethod(proxy, method, args); } throw new UnsupportedOperationException("Method " + method.toString() + "is unsupported."); } - private static boolean isObjectMethod(Method m) { + private static boolean isObjectMethod(final Method m) { switch (m.getName()) { - case "toString": - return (m.getReturnType() == String.class && m.getParameterTypes().length == 0); - case "hashCode": - return (m.getReturnType() == int.class && m.getParameterTypes().length == 0); - case "equals": - return (m.getReturnType() == boolean.class && m.getParameterTypes().length == 1 && m.getParameterTypes()[0] == Object.class); + case "toString": + return (m.getReturnType() == String.class && m.getParameterTypes().length == 0); + case "hashCode": + return (m.getReturnType() == int.class && m.getParameterTypes().length == 0); + case "equals": + return (m.getReturnType() == boolean.class && m.getParameterTypes().length == 1 && m + .getParameterTypes()[0] == Object.class); } return false; } - private Object callObjectMethod(Object self, Method m, Object[] args) { + private Object callObjectMethod(final Object self, final Method m, final Object[] args) { switch (m.getName()) { - case "toString": - return type.getName() + "$Adapter{delegate=" + delegate.toString()+"}"; - case "hashCode": - return System.identityHashCode(self); - case "equals": - return (self == args[0]); + case "toString": + return type.getName() + "$Adapter{delegate=" + delegate.toString() + "}"; + case "hashCode": + return System.identityHashCode(self); + case "equals": + return (self == args[0]); } return null; } + + private static ListenableFuture> transformFuture(final SchemaPath rpc, + final ListenableFuture domFuture, final BindingNormalizedNodeCodecRegistry codec) { + return Futures.transform(domFuture, new Function>() { + @Override + public RpcResult apply(final DOMRpcResult input) { + if (input instanceof LazySerializedDOMRpcResult) { + return ((LazySerializedDOMRpcResult) input).bidningRpcResult(); + } + final NormalizedNode domData = input.getResult(); + final DataObject bindingResult; + if (domData != null) { + final SchemaPath rpcOutput = rpc.createChild(QName.create(rpc.getLastComponent(), "output")); + bindingResult = codec.fromNormalizedNodeRpcData(rpcOutput, (ContainerNode) domData); + } else { + bindingResult = null; + } + return RpcResult.class.cast(RpcResultBuilder.success(bindingResult).build()); + } + }); + } + + private abstract class RpcInvocationStrategy { + + private final SchemaPath rpcName; + + protected RpcInvocationStrategy(final SchemaPath path) { + rpcName = path; + } + + final ListenableFuture> invoke(final DataObject input) { + return invoke0(rpcName, serialize(input)); + } + + abstract NormalizedNode serialize(DataObject input); + + final ListenableFuture> invokeEmpty() { + return invoke0(rpcName, null); + } + + final SchemaPath getRpcName() { + return rpcName; + } + + } + + private final class NonRoutedStrategy extends RpcInvocationStrategy { + + protected NonRoutedStrategy(final SchemaPath path) { + super(path); + } + + @Override + NormalizedNode serialize(final DataObject input) { + return LazySerializedContainerNode.create(getRpcName(), input, codec.getCodecRegistry()); + } + + } + + private final class RoutedStrategy extends RpcInvocationStrategy { + + private final ContextReferenceExtractor refExtractor; + private final NodeIdentifier contextName; + + protected RoutedStrategy(final SchemaPath path, final Method rpcMethod, final QName leafName) { + super(path); + final Class inputType = BindingReflections.resolveRpcInputClass(rpcMethod).get(); + refExtractor = ContextReferenceExtractor.from(inputType); + this.contextName = new NodeIdentifier(leafName); + } + + @Override + NormalizedNode serialize(final DataObject input) { + final InstanceIdentifier bindingII = refExtractor.extract(input); + if (bindingII != null) { + final YangInstanceIdentifier yangII = codec.toYangInstanceIdentifierCached(bindingII); + final LeafNode contextRef = ImmutableNodes.leafNode(contextName, yangII); + return LazySerializedContainerNode.withContextRef(getRpcName(), input, contextRef, + codec.getCodecRegistry()); + } + return LazySerializedContainerNode.create(getRpcName(), input, codec.getCodecRegistry()); + } + + } } diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/ContextExtractorTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/ContextExtractorTest.java new file mode 100644 index 0000000000..a38f3f1539 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/ContextExtractorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015 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.md.sal.binding.impl; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.rpcservice.rev140701.RockTheHouseInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.rpcservice.rev140701.RockTheHouseInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.EncapsulatedRoute; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.EncapsulatedRouteInGrouping; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.RoutedSimpleRouteInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.RoutedSimpleRouteInputBuilder; +import org.opendaylight.yangtools.yang.binding.DataContainer; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public final class ContextExtractorTest { + + public interface Transitive extends EncapsulatedRouteInGrouping { + + } + + private static final InstanceIdentifier TEST_ROUTE = InstanceIdentifier.create(Top.class); + private static final Transitive TEST_GROUPING = new Transitive() { + + @Override + public Class getImplementedInterface() { + return Transitive.class; + } + + @Override + public EncapsulatedRoute getRoute() { + return new EncapsulatedRoute(TEST_ROUTE); + } + }; + + @Test + public void testNonRoutedExtraction() { + final ContextReferenceExtractor extractor = ContextReferenceExtractor.from(RockTheHouseInput.class); + final RockTheHouseInput input = new RockTheHouseInputBuilder().build(); + final InstanceIdentifier extractedValue = extractor.extract(input); + assertNull(extractedValue); + } + + @Test + public void testRoutedSimpleExtraction() { + final ContextReferenceExtractor extractor = ContextReferenceExtractor.from(RoutedSimpleRouteInput.class); + final RoutedSimpleRouteInput input = new RoutedSimpleRouteInputBuilder().setRoute(TEST_ROUTE).build(); + final InstanceIdentifier extractedValue = extractor.extract(input); + assertSame(TEST_ROUTE,extractedValue); + } + + @Test + public void testRoutedEncapsulatedExtraction() { + final ContextReferenceExtractor extractor = ContextReferenceExtractor.from(EncapsulatedRouteInGrouping.class); + final InstanceIdentifier extractedValue = extractor.extract(TEST_GROUPING); + assertSame(TEST_ROUTE,extractedValue); + + } + + @Test + public void testRoutedEncapsulatedTransitiveExtraction() { + final ContextReferenceExtractor extractor = ContextReferenceExtractor.from(Transitive.class); + final InstanceIdentifier extractedValue = extractor.extract(TEST_GROUPING); + assertSame(TEST_ROUTE,extractedValue); + } + } -- 2.36.6