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;
/**
* 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) {
@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);
}
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
* {@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;
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.
*
*/
@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);
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())) {
/**
* 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);
}
}
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;
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;
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) {
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);
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);
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());
+ }
}
--- /dev/null
+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;
+ }
+ }
+}