Bug 2351 Speed-up Binding-to-Binding routed RPC Invocation. 12/19312/6
authorTony Tkacik <ttkacik@cisco.com>
Mon, 27 Apr 2015 12:16:06 +0000 (14:16 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 13 May 2015 03:37:50 +0000 (03:37 +0000)
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 <ttkacik@cisco.com>
(cherry picked from commit 313b9c3cdff6c7fbf94583db4b8933aa394bccc0)

opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcProviderServiceAdapter.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingDOMRpcServiceAdapter.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/ContextReferenceExtractor.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/DirectGetterRouteContextExtractor.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/GetValueRouteContextExtractor.java [new file with mode: 0644]
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LazySerializedContainerNode.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/RpcServiceAdapter.java
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/ContextExtractorTest.java [new file with mode: 0644]

index ba822989d81d8faa947db6885b9c002d46b3f054..05f11943cdb59391817eb1479823dfd01cb02984 100644 (file)
@@ -63,7 +63,7 @@ public class BindingDOMRpcProviderServiceAdapter {
     private Set<YangInstanceIdentifier> toYangInstanceIdentifiers(final Set<InstanceIdentifier<?>> identifiers) {
         final Set<YangInstanceIdentifier> ret = new HashSet<>();
         for(final InstanceIdentifier<?> binding: identifiers) {
-            ret.add(codec.toNormalized(binding));
+            ret.add(codec.toYangInstanceIdentifierCached(binding));
         }
         return ret;
     }
index 6b64b7ea55473a98027b75a7a96cf9571acfa00e..90d91458e1dcdf869dddd5f2c59d78e67bba71dc 100644 (file)
@@ -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<RpcConsumerRegistry> BUILDER_FACTORY = new Factory<RpcConsumerRegistry>() {
 
@@ -78,45 +61,10 @@ public class BindingDOMRpcServiceAdapter implements RpcConsumerRegistry, Invocat
         return proxy;
     }
 
-    @Override
-    public ListenableFuture<RpcResult<?>> invoke(final SchemaPath rpc, final DataObject input) {
-        final CheckedFuture<DOMRpcResult, DOMRpcException> domFuture = domService.invokeRpc(rpc, serialize(rpc,input));
-        return transformFuture(rpc,domFuture,codec.getCodecRegistry());
-    }
-
     private RpcServiceAdapter createProxy(final Class<? extends RpcService> key) {
         Preconditions.checkArgument(BindingReflections.isBindingClass(key));
         Preconditions.checkArgument(key.isInterface(), "Supplied RPC service type must be interface.");
-        final ImmutableMap<Method, SchemaPath> 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<RpcResult<?>> transformFuture(final SchemaPath rpc,final ListenableFuture<DOMRpcResult> domFuture, final BindingNormalizedNodeCodecRegistry codec) {
-        return Futures.transform(domFuture, new Function<DOMRpcResult, RpcResult<?>>() {
-            @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<RpcConsumerRegistry> {
index ab07f50941a25711c231073e902c5d3a09782921..3795ad3bac8c4787bda7ff27cd22668b398eeb33 100644 (file)
@@ -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<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
+            .softValues().build(new CacheLoader<InstanceIdentifier<?>, 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<? extends DataObject> 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 <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
             final InstanceIdentifier<T> 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<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding) {
@@ -222,6 +258,21 @@ public class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, Bi
         return ret.build();
     }
 
+    protected ImmutableBiMap<Method, RpcDefinition> getRpcMethodToSchema(final Class<? extends RpcService> key) {
+        final QNameModule moduleName = BindingReflections.getQNameModule(key);
+        final Module module = runtimeContext.getSchemaContext().findModuleByNamespaceAndRevision(moduleName.getNamespace(), moduleName.getRevision());
+        final ImmutableBiMap.Builder<Method, RpcDefinition> 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<? extends RpcService> 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<InstanceIdentifier<?>, BindingCodecTreeNode<?>> getSubtreeCodec(
+    @Nonnull
+    protected Map.Entry<InstanceIdentifier<?>, 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 (file)
index 0000000..4653ca3
--- /dev/null
@@ -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<Class<?>, ContextReferenceExtractor> EXTRACTORS = CacheBuilder.newBuilder()
+            .weakKeys().build(new CacheLoader<Class<?>, 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 (file)
index 0000000..33e0582
--- /dev/null
@@ -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 (file)
index 0000000..d5d5a72
--- /dev/null
@@ -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);
+        }
+    }
+
+
+}
index 9eb4ed76f6d15a44b3584e96c7043bf1ca2dce6b..332df7531d82e4a592dcf63ef1b1e5070c2547c9 100644 (file)
@@ -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<QName, String> 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<DataContainerChild<? extends PathArgument, ?>> getValue() {
+    public final Collection<DataContainerChild<? extends PathArgument, ?>> getValue() {
         return delegate().getValue();
     }
 
     @Override
-    public NodeIdentifier getIdentifier() {
+    public final NodeIdentifier getIdentifier() {
         return identifier;
     }
 
     @Override
-    public Optional<DataContainerChild<? extends PathArgument, ?>> getChild(PathArgument child) {
+    public Optional<DataContainerChild<? extends PathArgument, ?>> 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<DataContainerChild<? extends PathArgument, ?>> 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.<DataContainerChild<? extends PathArgument, ?>>of(contextRef);
+            }
+            return super.getChild(child);
+        }
+    }
+
 }
index 61b32324a9d6b1c5db39e562c6a98f24e5e0e493..7f2a074af23db07a928a773a6f8e7ec1af2bb8fb 100644 (file)
@@ -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<RpcResult<?>> invoke(SchemaPath rpc, DataObject dataObject);
+    private final ImmutableMap<Method, RpcInvocationStrategy> rpcNames;
+    private final Class<? extends RpcService> type;
+    private final BindingToNormalizedNodeCodec codec;
+    private final DOMRpcService delegate;
+    private final RpcService proxy;
 
+    RpcServiceAdapter(final Class<? extends RpcService> 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<Method, RpcInvocationStrategy> rpcBuilder = ImmutableMap.builder();
+        for (final Entry<Method, RpcDefinition> 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<Method,SchemaPath> rpcNames;
-    private final Class<? extends RpcService> type;
-    private final InvocationDelegate delegate;
+    private final ListenableFuture<RpcResult<?>> invoke0(final SchemaPath schemaPath, final NormalizedNode<?, ?> input) {
+        final CheckedFuture<DOMRpcResult, DOMRpcException> result = delegate.invokeRpc(schemaPath, input);
+        return transformFuture(schemaPath, result, codec.getCodecFactory());
+    }
 
-    RpcServiceAdapter(Class<? extends RpcService> type, ImmutableMap<Method, SchemaPath> 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<RpcResult<?>> transformFuture(final SchemaPath rpc,
+            final ListenableFuture<DOMRpcResult> domFuture, final BindingNormalizedNodeCodecRegistry codec) {
+        return Futures.transform(domFuture, new Function<DOMRpcResult, RpcResult<?>>() {
+            @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<RpcResult<?>> invoke(final DataObject input) {
+            return invoke0(rpcName, serialize(input));
+        }
+
+        abstract NormalizedNode<?, ?> serialize(DataObject input);
+
+        final ListenableFuture<RpcResult<?>> 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<? extends DataContainer> 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 (file)
index 0000000..a38f3f1
--- /dev/null
@@ -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<? extends DataContainer> 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);
+    }
+ }