Teach BindingNormalizedNodeCache to cache leaf type objects
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CachingNormalizedNodeSerializer.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import java.io.IOException;
11 import org.opendaylight.mdsal.binding.dom.codec.impl.LeafNodeCodecContext.OfTypeObject;
12 import org.opendaylight.yangtools.yang.binding.BindingSerializer;
13 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
14 import org.opendaylight.yangtools.yang.binding.DataObject;
15 import org.opendaylight.yangtools.yang.binding.TypeObject;
16 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
17 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 /**
22  * Serializer of Binding objects to Normalized Node which uses {@link DataObjectNormalizedNodeCache} to
23  * cache already serialized values.
24  *
25  * <p>
26  * This serializer implements {@link BindingStreamEventWriter} along with {@link BindingSerializer}.
27  *
28  * <p>
29  * {@link BindingSerializer} interface is used by generated implementations of
30  * {@link org.opendaylight.yangtools.yang.binding.DataObjectSerializer} to provide Binding object
31  * for inspection and to prevent streaming of already serialized object.
32  */
33 final class CachingNormalizedNodeSerializer extends ForwardingBindingStreamEventWriter
34         implements BindingSerializer<Object, DataObject> {
35     private static final Logger LOG = LoggerFactory.getLogger(CachingNormalizedNodeSerializer.class);
36
37     private final NormalizedNodeResult domResult;
38     private final NormalizedNodeWriterWithAddChild domWriter;
39     private final BindingToNormalizedStreamWriter delegate;
40     private final AbstractBindingNormalizedNodeCacheHolder cacheHolder;
41
42     CachingNormalizedNodeSerializer(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
43             final DataContainerCodecContext<?, ?> subtreeRoot) {
44         this.cacheHolder = cacheHolder;
45         this.domResult = new NormalizedNodeResult();
46         this.domWriter = new NormalizedNodeWriterWithAddChild(domResult);
47         this.delegate = BindingToNormalizedStreamWriter.create(subtreeRoot, domWriter);
48     }
49
50     @Override
51     protected BindingStreamEventWriter delegate() {
52         return delegate;
53     }
54
55     NormalizedNode<?, ?> build() {
56         return domResult.getResult();
57     }
58
59     @Override
60     public void leafNode(final String localName, final Object value) throws IOException {
61         if (value instanceof TypeObject) {
62             // TypeObject is a tagging interface used for generated classes which wrap derived and restricted types.
63             // They are immutable and hence we can safely wrap them in LeafNodes and reuse them, if directed to do so.
64             final TypeObject typed = (TypeObject) value;
65             final Class<? extends TypeObject> type = typed.getClass();
66             if (cacheHolder.isCached(type)) {
67                 final ValueNodeCodecContext context = ((DataObjectCodecContext<?, ?>) delegate.current())
68                         .getLeafChild(localName);
69                 if (context instanceof OfTypeObject) {
70                     final AbstractBindingNormalizedNodeCache<TypeObject, ?> cache = cacheHolder.getCachingSerializer(
71                         (OfTypeObject<?>)context);
72                     if (cache != null) {
73                         // We have a cache hit and are thus done
74                         domWriter.addChild(cache.get(typed));
75                         return;
76                     }
77
78                     LOG.debug("Unexpected failure to acquire cache for context {}, skipping caching", context);
79                 } else {
80                     LOG.debug("Context {} does not match expected TypeObject {}, skipping caching", context, typed);
81                 }
82             }
83         }
84         super.leafNode(localName, value);
85     }
86
87     /**
88      * Serializes input if it is cached, returns null otherwise.
89      *
90      * <p>
91      * If input is cached it uses {@link NormalizedNodeWriterWithAddChild#addChild(NormalizedNode)}
92      * to provide already serialized value to underlying NormalizedNodeWriter in order to reuse
93      * value instead of creating new one using Normalized Node stream APIs.
94      *
95      * <p>
96      * Note that this optional is serialization of child node invoked from
97      * {@link org.opendaylight.yangtools.yang.binding.DataObjectSerializer}, which may opt-out from
98      * streaming of data when non-null result is returned.
99      */
100     @Override
101     public NormalizedNode<?, ?> serialize(final DataObject input) {
102         final AbstractBindingNormalizedNodeCache<DataObject, ?> cachingSerializer = getCacheSerializer(
103             input.implementedInterface());
104         if (cachingSerializer != null) {
105             final NormalizedNode<?, ?> domData = cachingSerializer.get(input);
106             domWriter.addChild(domData);
107             return domData;
108         }
109         return null;
110     }
111
112     private AbstractBindingNormalizedNodeCache<DataObject, ?> getCacheSerializer(
113             final Class<? extends DataObject> type) {
114         if (cacheHolder.isCached(type)) {
115             final DataContainerCodecContext<?, ?> currentCtx = (DataContainerCodecContext<?, ?>) delegate.current();
116             if (type.equals(currentCtx.getBindingClass())) {
117                 return cacheHolder.getCachingSerializer(currentCtx);
118             }
119             return cacheHolder.getCachingSerializer(currentCtx.streamChild(type));
120         }
121         return null;
122     }
123
124     /**
125      * Serializes supplied data using stream writer with child cache enabled.
126      *
127      * @param cacheHolder Binding to Normalized Node Cache holder
128      * @param subtreeRoot Codec Node for provided data object
129      * @param data Data to be serialized
130      * @return Normalized Node representation of data.
131      */
132     static NormalizedNode<?, ?> serializeUsingStreamWriter(final AbstractBindingNormalizedNodeCacheHolder cacheHolder,
133             final DataContainerCodecContext<?, ?> subtreeRoot, final DataObject data) {
134         final CachingNormalizedNodeSerializer writer = new CachingNormalizedNodeSerializer(cacheHolder, subtreeRoot);
135         try {
136             subtreeRoot.eventStreamSerializer().serialize(data, writer);
137             return writer.build();
138         } catch (final IOException e) {
139             throw new IllegalStateException(e);
140         }
141     }
142 }