Teach BindingNormalizedNodeCache to cache leaf type objects 90/79990/32
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 11 Apr 2019 15:40:28 +0000 (17:40 +0200)
committerRobert Varga <nite@hq.sk>
Mon, 29 Apr 2019 14:52:52 +0000 (14:52 +0000)
This adds the final bits to enable CachingNormalizedNodeSerializer
to actually cache leaf objects.

JIRA: MDSAL-407
Change-Id: I39bac36395f4e6809342d48a375d441b49cbc3b8
Signed-off-by: Jie Han <han.jie@zte.com.cn>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/AbstractBindingNormalizedNodeCacheHolder.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CachingNormalizedNodeCodec.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/CachingNormalizedNodeSerializer.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/TypeObjectNormalizedNodeCache.java
binding/mdsal-binding-dom-codec/src/test/java/org/opendaylight/mdsal/binding/dom/codec/test/CachingCodecTest.java
binding/mdsal-binding-test-model/src/main/yang/opendaylight-test-leafcachingcodec.yang [new file with mode: 0644]

index d578a559d469bc038813be1b3ff9e580d8f57e67..d900d4e8c816085899631f4567fd296f0e3f8680 100644 (file)
@@ -13,6 +13,8 @@ import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingObjectCodecTreeNode;
+import org.opendaylight.mdsal.binding.dom.codec.impl.LeafNodeCodecContext.OfTypeObject;
 import org.opendaylight.yangtools.yang.binding.BindingObject;
 
 /**
@@ -20,24 +22,32 @@ import org.opendaylight.yangtools.yang.binding.BindingObject;
  * associated.
  */
 abstract class AbstractBindingNormalizedNodeCacheHolder {
-    private final LoadingCache<DataContainerCodecContext<?, ?>, DataObjectNormalizedNodeCache> caches = CacheBuilder
-            .newBuilder().build(new CacheLoader<DataContainerCodecContext<?, ?>, DataObjectNormalizedNodeCache>() {
+    @SuppressWarnings("rawtypes")
+    private final LoadingCache<NodeCodecContext, AbstractBindingNormalizedNodeCache> caches = CacheBuilder
+            .newBuilder().build(new CacheLoader<NodeCodecContext, AbstractBindingNormalizedNodeCache>() {
                 @Override
-                public DataObjectNormalizedNodeCache load(final DataContainerCodecContext<?, ?> key) {
-                    return new DataObjectNormalizedNodeCache(AbstractBindingNormalizedNodeCacheHolder.this, key);
+                public AbstractBindingNormalizedNodeCache load(final NodeCodecContext key) {
+                    if (key instanceof DataContainerCodecContext) {
+                        return new DataObjectNormalizedNodeCache(AbstractBindingNormalizedNodeCacheHolder.this,
+                            (DataContainerCodecContext<?, ?>) key);
+                    } else if (key instanceof OfTypeObject) {
+                        return new TypeObjectNormalizedNodeCache<>((OfTypeObject)key);
+                    } else {
+                        throw new IllegalStateException("Unhandled context " + key);
+                    }
                 }
             });
+
     private final ImmutableSet<Class<? extends BindingObject>> cacheSpec;
 
     AbstractBindingNormalizedNodeCacheHolder(final ImmutableSet<Class<? extends BindingObject>> cacheSpec) {
         this.cacheSpec = requireNonNull(cacheSpec);
     }
 
-    DataObjectNormalizedNodeCache getCachingSerializer(final DataContainerCodecContext<?, ?> childCtx) {
-        if (isCached(childCtx.getBindingClass())) {
-            return caches.getUnchecked(childCtx);
-        }
-        return null;
+    @SuppressWarnings("unchecked")
+    <T extends BindingObject, C extends NodeCodecContext & BindingObjectCodecTreeNode<?>>
+            AbstractBindingNormalizedNodeCache<T, C> getCachingSerializer(final C childCtx) {
+        return isCached(childCtx.getBindingClass()) ? caches.getUnchecked(childCtx) : null;
     }
 
     final boolean isCached(final Class<? extends BindingObject> type) {
index 5c89dae130344a1e9033d947f5dd16868083b8ae..c215141f9b7040074e2002ba2fae8cb56dc86cf2 100644 (file)
@@ -33,7 +33,7 @@ class CachingNormalizedNodeCodec<D extends DataObject> extends AbstractBindingNo
     @Override
     public NormalizedNode<?, ?> serialize(final D data) {
         // Serialize data using stream writer with child cache enable or using the cache if it is available
-        final DataObjectNormalizedNodeCache cache = getCachingSerializer(context);
+        final AbstractBindingNormalizedNodeCache<D, ?> cache = getCachingSerializer(context);
         return cache == null ? CachingNormalizedNodeSerializer.serializeUsingStreamWriter(this, context, data)
                 : cache.get(data);
     }
index 658bc8939c7fab37aa9e64c382d67afe463e0174..4012de25382f54f7a14d364c2fab513f1f9cacad 100644 (file)
@@ -8,11 +8,15 @@
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import java.io.IOException;
+import org.opendaylight.mdsal.binding.dom.codec.impl.LeafNodeCodecContext.OfTypeObject;
 import org.opendaylight.yangtools.yang.binding.BindingSerializer;
 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
 import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.TypeObject;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Serializer of Binding objects to Normalized Node which uses {@link DataObjectNormalizedNodeCache} to
@@ -26,8 +30,9 @@ import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
  * {@link org.opendaylight.yangtools.yang.binding.DataObjectSerializer} to provide Binding object
  * for inspection and to prevent streaming of already serialized object.
  */
-final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEventWriter implements
-        BindingSerializer<Object, DataObject> {
+final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEventWriter
+        implements BindingSerializer<Object, DataObject> {
+    private static final Logger LOG = LoggerFactory.getLogger(CachingNormalizedNodeSerializer.class);
 
     private final NormalizedNodeResult domResult;
     private final NormalizedNodeWriterWithAddChild domWriter;
@@ -51,6 +56,34 @@ final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEvent
         return domResult.getResult();
     }
 
+    @Override
+    public void leafNode(final String localName, final Object value) throws IOException {
+        if (value instanceof TypeObject) {
+            // TypeObject is a tagging interface used for generated classes which wrap derived and restricted types.
+            // They are immutable and hence we can safely wrap them in LeafNodes and reuse them, if directed to do so.
+            final TypeObject typed = (TypeObject) value;
+            final Class<? extends TypeObject> type = typed.getClass();
+            if (cacheHolder.isCached(type)) {
+                final ValueNodeCodecContext context = ((DataObjectCodecContext<?, ?>) delegate.current())
+                        .getLeafChild(localName);
+                if (context instanceof OfTypeObject) {
+                    final AbstractBindingNormalizedNodeCache<TypeObject, ?> cache = cacheHolder.getCachingSerializer(
+                        (OfTypeObject<?>)context);
+                    if (cache != null) {
+                        // We have a cache hit and are thus done
+                        domWriter.addChild(cache.get(typed));
+                        return;
+                    }
+
+                    LOG.debug("Unexpected failure to acquire cache for context {}, skipping caching", context);
+                } else {
+                    LOG.debug("Context {} does not match expected TypeObject {}, skipping caching", context, typed);
+                }
+            }
+        }
+        super.leafNode(localName, value);
+    }
+
     /**
      * Serializes input if it is cached, returns null otherwise.
      *
@@ -66,7 +99,8 @@ final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEvent
      */
     @Override
     public NormalizedNode<?, ?> serialize(final DataObject input) {
-        final DataObjectNormalizedNodeCache cachingSerializer = getCacheSerializer(input.implementedInterface());
+        final AbstractBindingNormalizedNodeCache<DataObject, ?> cachingSerializer = getCacheSerializer(
+            input.implementedInterface());
         if (cachingSerializer != null) {
             final NormalizedNode<?, ?> domData = cachingSerializer.get(input);
             domWriter.addChild(domData);
@@ -75,7 +109,8 @@ final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEvent
         return null;
     }
 
-    private DataObjectNormalizedNodeCache getCacheSerializer(final Class<? extends DataObject> type) {
+    private AbstractBindingNormalizedNodeCache<DataObject, ?> getCacheSerializer(
+            final Class<? extends DataObject> type) {
         if (cacheHolder.isCached(type)) {
             final DataContainerCodecContext<?, ?> currentCtx = (DataContainerCodecContext<?, ?>) delegate.current();
             if (type.equals(currentCtx.getBindingClass())) {
index a9152a4351eaac7535ba653066a51c5bf75f8277..7046cbf3cb021e41e1bc768318673c0ab345d040 100644 (file)
@@ -14,15 +14,14 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 /**
  * A cache of NormalizedNodes corresponding to a particular TypeObject instantiation.
  */
-final class TypeObjectNormalizedNodeCache<T extends TypeObject,
-        C extends NodeCodecContext & BindingTypeObjectCodecTreeNode<T>>
-        extends AbstractBindingNormalizedNodeCache<T, C> {
+final class TypeObjectNormalizedNodeCache<C extends NodeCodecContext & BindingTypeObjectCodecTreeNode<TypeObject>>
+        extends AbstractBindingNormalizedNodeCache<TypeObject, C> {
     TypeObjectNormalizedNodeCache(final C rootContext) {
         super(rootContext);
     }
 
     @Override
-    public NormalizedNode<?, ?> load(final T key) {
+    public NormalizedNode<?, ?> load(final TypeObject key) {
         return rootContext().serialize(key);
     }
 }
index a5280ccde5ee00c260e34dc8702af33e2aeb2e65..a70e46733d8f46c36d26082d795d70cd4fdc1487 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.mdsal.binding.dom.codec.test;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -26,8 +27,13 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.te
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelList;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelListBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.two.level.list.TopLevelListKey;
+import org.opendaylight.yang.gen.v1.urn.test.leaf.caching.codec.rev190201.Cont;
+import org.opendaylight.yang.gen.v1.urn.test.leaf.caching.codec.rev190201.ContBuilder;
+import org.opendaylight.yang.gen.v1.urn.test.leaf.caching.codec.rev190201.MyType;
+import org.opendaylight.yangtools.yang.binding.BindingObject;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
@@ -44,13 +50,20 @@ public class CachingCodecTest extends AbstractBindingCodecTest {
     private static final Top TOP_TWO_LIST_DATA = new TopBuilder().setTopLevelList(TWO_LIST).build();
     private static final Top TOP_THREE_LIST_DATA = new TopBuilder().setTopLevelList(THREE_LIST).build();
 
+    private static final NodeIdentifier LEAF_ARG = new NodeIdentifier(QName.create(Cont.QNAME, "caching"));
+    private static final InstanceIdentifier<Cont> CONT_PATH = InstanceIdentifier.create(Cont.class);
+    private static final Cont CONT_DATA = new ContBuilder().setCaching(new MyType(1)).setNonCaching("test").build();
+    private static final Cont CONT2_DATA = new ContBuilder().setCaching(new MyType(1)).setNonCaching("test2").build();
+
     private BindingDataObjectCodecTreeNode<Top> topNode;
+    private BindingDataObjectCodecTreeNode<Cont> contNode;
 
     @Override
     @Before
     public void before() {
         super.before();
         topNode = registry.getCodecContext().getSubtreeCodec(TOP_PATH);
+        contNode = registry.getCodecContext().getSubtreeCodec(CONT_PATH);
     }
 
     private static List<TopLevelList> createList(final int num) {
@@ -91,6 +104,16 @@ public class CachingCodecTest extends AbstractBindingCodecTest {
         verifyListItemSame(first, third);
     }
 
+    @Test
+    public void testLeafCache() {
+        final BindingNormalizedNodeCachingCodec<Cont> cachingCodec = createContCachingCodec(Cont.class, MyType.class);
+        final NormalizedNode<?, ?> first = cachingCodec.serialize(CONT_DATA);
+        final NormalizedNode<?, ?> second = cachingCodec.serialize(CONT2_DATA);
+
+        assertNotEquals(first, second);
+        verifyLeafItemSame(first, second);
+    }
+
     @Test
     public void testDefaultInvocation() {
         final BindingNormalizedNodeCachingCodec<Top> cachingCodec = createCachingCodec(Top.class, TopLevelList.class);
@@ -114,6 +137,12 @@ public class CachingCodecTest extends AbstractBindingCodecTest {
         return topNode.createCachingCodec(ImmutableSet.copyOf(classes));
     }
 
+    @SafeVarargs
+    private final BindingNormalizedNodeCachingCodec<Cont> createContCachingCodec(
+            final Class<? extends BindingObject>... classes) {
+        return contNode.createCachingCodec(ImmutableSet.copyOf(classes));
+    }
+
     private static void verifyListItemSame(final NormalizedNode<?, ?> firstTop, final NormalizedNode<?, ?> secondTop) {
         final Collection<MapEntryNode> initialNodes = getListItems(firstTop).getValue();
         final MapNode secondMap = getListItems(secondTop);
@@ -128,4 +157,10 @@ public class CachingCodecTest extends AbstractBindingCodecTest {
     private static MapNode getListItems(final NormalizedNode<?, ?> top) {
         return (MapNode) ((DataContainerNode<?>) top).getChild(TOP_LEVEL_LIST_ARG).get();
     }
+
+    private static void verifyLeafItemSame(final NormalizedNode<?, ?> firstCont,
+            final NormalizedNode<?, ?> secondCont) {
+        assertSame(((DataContainerNode<?>) firstCont).getChild(LEAF_ARG).get(),
+                ((DataContainerNode<?>) secondCont).getChild(LEAF_ARG).get());
+    }
 }
diff --git a/binding/mdsal-binding-test-model/src/main/yang/opendaylight-test-leafcachingcodec.yang b/binding/mdsal-binding-test-model/src/main/yang/opendaylight-test-leafcachingcodec.yang
new file mode 100644 (file)
index 0000000..748f1eb
--- /dev/null
@@ -0,0 +1,22 @@
+module test-leaf-caching-codec {
+    namespace "urn:test:leaf:caching:codec";
+    prefix test;
+
+    revision 2019-02-01;
+
+    typedef my-type {
+        type uint16 {
+          range "1..4094";
+        }
+    }
+
+    container cont{
+       leaf caching {
+         type my-type;
+       }
+
+       leaf non-caching {
+         type string;
+       }
+    }
+}