Move AbstractLazyActionContainerNode to SPI
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / BindingNormalizedNodeCodecRegistry.java
1 /*
2  * Copyright (c) 2014 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 static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
14 import java.io.IOException;
15 import java.time.Instant;
16 import java.util.AbstractMap.SimpleEntry;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Map.Entry;
20 import java.util.Optional;
21 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
22 import java.util.function.BiFunction;
23 import java.util.function.Function;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.opendaylight.binding.runtime.api.BindingRuntimeContext;
26 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
27 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
28 import org.opendaylight.mdsal.binding.dom.codec.api.BindingLazyContainerNode;
29 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
30 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeWriterFactory;
31 import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
32 import org.opendaylight.mdsal.binding.dom.codec.spi.LazyActionInputContainerNode;
33 import org.opendaylight.mdsal.binding.dom.codec.spi.LazyActionOutputContainerNode;
34 import org.opendaylight.yangtools.yang.binding.Action;
35 import org.opendaylight.yangtools.yang.binding.DataContainer;
36 import org.opendaylight.yangtools.yang.binding.DataObject;
37 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
39 import org.opendaylight.yangtools.yang.binding.Notification;
40 import org.opendaylight.yangtools.yang.binding.RpcInput;
41 import org.opendaylight.yangtools.yang.binding.RpcOutput;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
52 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
53 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
54 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 public class BindingNormalizedNodeCodecRegistry
59         implements BindingNormalizedNodeWriterFactory, BindingNormalizedNodeSerializer {
60     private static final Logger LOG = LoggerFactory.getLogger(BindingNormalizedNodeCodecRegistry.class);
61
62     private static final AtomicReferenceFieldUpdater<BindingNormalizedNodeCodecRegistry, BindingCodecContext> UPDATER =
63             AtomicReferenceFieldUpdater.newUpdater(BindingNormalizedNodeCodecRegistry.class, BindingCodecContext.class,
64                 "codecContext");
65     private volatile BindingCodecContext codecContext;
66
67     public BindingNormalizedNodeCodecRegistry() {
68
69     }
70
71     public BindingNormalizedNodeCodecRegistry(final BindingRuntimeContext codecContext) {
72         this();
73         onBindingRuntimeContextUpdated(codecContext);
74     }
75
76     public BindingCodecTree getCodecContext() {
77         return codecContext;
78     }
79
80     public void onBindingRuntimeContextUpdated(final BindingRuntimeContext context) {
81         // BindingCodecContext is a costly resource. Let us not ditch it unless we have to
82         final BindingCodecContext current = codecContext;
83         if (current != null && context.equals(current.getRuntimeContext())) {
84             LOG.debug("Skipping update of runtime context {}", context);
85             return;
86         }
87
88         final BindingCodecContext updated = new BindingCodecContext(context);
89         if (!UPDATER.compareAndSet(this, current, updated)) {
90             LOG.warn("Concurrent update of runtime context (expected={} current={}) detected at ", current,
91                 codecContext, new Throwable());
92         }
93     }
94
95     final @NonNull BindingCodecContext codecContext() {
96         final BindingCodecContext local = codecContext;
97         checkState(local != null, "No context available yet");
98         return local;
99     }
100
101     @Override
102     public YangInstanceIdentifier toYangInstanceIdentifier(final InstanceIdentifier<?> binding) {
103         return codecContext.getInstanceIdentifierCodec().fromBinding(binding);
104     }
105
106     @Override
107     public <T extends DataObject> InstanceIdentifier<T> fromYangInstanceIdentifier(final YangInstanceIdentifier dom) {
108         return codecContext.getInstanceIdentifierCodec().toBinding(dom);
109     }
110
111     @Override
112     public <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?,?>> toNormalizedNode(
113             final InstanceIdentifier<T> path, final T data) {
114         final NormalizedNodeResult result = new NormalizedNodeResult();
115         // We create DOM stream writer which produces normalized nodes
116         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
117
118         // We create Binding Stream Writer which translates from Binding to Normalized Nodes
119         final Entry<YangInstanceIdentifier, BindingStreamEventWriter> writeCtx = codecContext.newWriter(path,
120             domWriter);
121
122         // We get serializer which reads binding data and uses Binding To Normalized Node writer to write result
123         try {
124             codecContext.getSerializer(path.getTargetType()).serialize(data, writeCtx.getValue());
125         } catch (final IOException e) {
126             LOG.error("Unexpected failure while serializing path {} data {}", path, data, e);
127             throw new IllegalStateException("Failed to create normalized node", e);
128         }
129         return new SimpleEntry<>(writeCtx.getKey(),result.getResult());
130     }
131
132     @Override
133     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
134     public ContainerNode toNormalizedNodeNotification(final Notification data) {
135         // FIXME: Should the cast to DataObject be necessary?
136         return serializeDataObject((DataObject) data,
137             // javac does not like a methodhandle here
138             (iface, domWriter) -> newNotificationWriter(iface.asSubclass(Notification.class), domWriter));
139     }
140
141     @Override
142     @SuppressFBWarnings("BC_UNCONFIRMED_CAST")
143     public ContainerNode toNormalizedNodeRpcData(final DataContainer data) {
144         // FIXME: Should the cast to DataObject be necessary?
145         return serializeDataObject((DataObject) data, this::newRpcWriter);
146     }
147
148     @Override
149     public ContainerNode toNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
150             final RpcInput input) {
151         return serializeDataObject(input, (iface, domWriter) -> newActionInputWriter(action, domWriter));
152     }
153
154     @Override
155     public ContainerNode toNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
156             final RpcOutput output) {
157         return serializeDataObject(output, (iface, domWriter) -> newActionOutputWriter(action, domWriter));
158     }
159
160     @Override
161     public BindingLazyContainerNode<RpcInput> toLazyNormalizedNodeActionInput(
162             final Class<? extends Action<?, ?, ?>> action, final NodeIdentifier identifier, final RpcInput input) {
163         return new LazyActionInputContainerNode(identifier, input, this, action);
164     }
165
166     @Override
167     public BindingLazyContainerNode<RpcOutput> toLazyNormalizedNodeActionOutput(
168             final Class<? extends Action<?, ?, ?>> action, final NodeIdentifier identifier, final RpcOutput output) {
169         return new LazyActionOutputContainerNode(identifier, output, this, action);
170     }
171
172     private <T extends DataContainer> ContainerNode serializeDataObject(final DataObject data,
173             final BiFunction<Class<? extends T>, NormalizedNodeStreamWriter, BindingStreamEventWriter> newWriter) {
174         final NormalizedNodeResult result = new NormalizedNodeResult();
175         // We create DOM stream writer which produces normalized nodes
176         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
177         final Class<? extends DataObject> type = data.implementedInterface();
178         @SuppressWarnings("unchecked")
179         final BindingStreamEventWriter writer = newWriter.apply((Class<T>)type, domWriter);
180         try {
181             codecContext.getSerializer(type).serialize(data, writer);
182         } catch (final IOException e) {
183             LOG.error("Unexpected failure while serializing data {}", data, e);
184             throw new IllegalStateException("Failed to create normalized node", e);
185         }
186         return (ContainerNode) result.getResult();
187     }
188
189     private static boolean notBindingRepresentable(final NormalizedNode<?, ?> data) {
190         // ValueNode covers LeafNode and LeafSetEntryNode
191         return data instanceof ValueNode
192                 || data instanceof MapNode || data instanceof UnkeyedListNode
193                 || data instanceof ChoiceNode
194                 || data instanceof LeafSetNode;
195     }
196
197     @Override
198     public Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(final YangInstanceIdentifier path,
199             final NormalizedNode<?, ?> data) {
200         if (notBindingRepresentable(data)) {
201             return null;
202         }
203
204         final List<PathArgument> builder = new ArrayList<>();
205         final BindingDataObjectCodecTreeNode<?> codec = codecContext.getCodecContextNode(path, builder);
206         if (codec == null) {
207             if (data != null) {
208                 LOG.warn("Path {} does not have a binding equivalent, should have been caught earlier ({})", path,
209                     data.getClass());
210             }
211             return null;
212         }
213
214         final DataObject lazyObj = codec.deserialize(data);
215         final InstanceIdentifier<?> bindingPath = InstanceIdentifier.create(builder);
216         return new SimpleEntry<>(bindingPath, lazyObj);
217     }
218
219     @Override
220     public Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data) {
221         final NotificationCodecContext<?> codec = codecContext.getNotificationContext(path);
222         return codec.deserialize(data);
223     }
224
225     @Override
226     public Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data,
227             final Instant eventInstant) {
228         if (eventInstant == null) {
229             return fromNormalizedNodeNotification(path, data);
230         }
231
232         final NotificationCodecContext<?> codec = codecContext.getNotificationContext(path);
233         return codec.deserialize(data, eventInstant);
234     }
235
236     @Override
237     public DataObject fromNormalizedNodeRpcData(final SchemaPath path, final ContainerNode data) {
238         final RpcInputCodec<?> codec = codecContext.getRpcInputCodec(path);
239         return codec.deserialize(data);
240     }
241
242     @Override
243     public <T extends RpcInput> T fromNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
244             final ContainerNode input) {
245         return (T) requireNonNull(codecContext.getActionCodec(action).input().deserialize(requireNonNull(input)));
246     }
247
248     @Override
249     public <T extends RpcOutput> T fromNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
250             final ContainerNode output) {
251         return (T) requireNonNull(codecContext.getActionCodec(action).output().deserialize(requireNonNull(output)));
252     }
253
254     @Override
255     public Entry<YangInstanceIdentifier, BindingStreamEventWriter> newWriterAndIdentifier(
256             final InstanceIdentifier<?> path, final NormalizedNodeStreamWriter domWriter) {
257         return codecContext.newWriter(path, domWriter);
258     }
259
260     @Override
261     public BindingStreamEventWriter newWriter(final InstanceIdentifier<?> path,
262             final NormalizedNodeStreamWriter domWriter) {
263         return codecContext.newWriterWithoutIdentifier(path, domWriter);
264     }
265
266     @Override
267     public BindingStreamEventWriter newNotificationWriter(final Class<? extends Notification> notification,
268             final NormalizedNodeStreamWriter streamWriter) {
269         return codecContext.newNotificationWriter(notification, streamWriter);
270     }
271
272     @Override
273     public BindingStreamEventWriter newActionInputWriter(final Class<? extends Action<?, ?, ?>> action,
274             final NormalizedNodeStreamWriter domWriter) {
275         return codecContext.getActionCodec(action).input().createWriter(domWriter);
276     }
277
278     @Override
279     public BindingStreamEventWriter newActionOutputWriter(final Class<? extends Action<?, ?, ?>> action,
280             final NormalizedNodeStreamWriter domWriter) {
281         return codecContext.getActionCodec(action).output().createWriter(domWriter);
282     }
283
284     @Override
285     public BindingStreamEventWriter newRpcWriter(final Class<? extends DataContainer> rpcInputOrOutput,
286             final NormalizedNodeStreamWriter streamWriter) {
287         return codecContext.newRpcWriter(rpcInputOrOutput,streamWriter);
288     }
289
290     public <T extends DataObject> Function<Optional<NormalizedNode<?, ?>>, Optional<T>>  deserializeFunction(
291             final InstanceIdentifier<T> path) {
292         final DataObjectCodecContext<?,?> ctx = (DataObjectCodecContext<?,?>) codecContext.getCodecContextNode(path,
293             null);
294         return new DeserializeFunction<>(ctx);
295     }
296
297     private static final class DeserializeFunction<T> implements Function<Optional<NormalizedNode<?, ?>>, Optional<T>> {
298         private final DataObjectCodecContext<?,?> ctx;
299
300         DeserializeFunction(final DataObjectCodecContext<?,?> ctx) {
301             this.ctx = ctx;
302         }
303
304         @SuppressWarnings("unchecked")
305         @Override
306         public Optional<T> apply(final Optional<NormalizedNode<?, ?>> input) {
307             return input.map(data -> (T) ctx.deserialize(data));
308         }
309     }
310 }