Switch to Objects.requireNonNull
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / impl / BindingNormalizedNodeCodecRegistry.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies s.r.o. 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.javav2.dom.codec.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.Beta;
13 import com.google.common.cache.CacheBuilder;
14 import com.google.common.cache.CacheLoader;
15 import com.google.common.cache.LoadingCache;
16 import java.io.IOException;
17 import java.util.AbstractMap.SimpleEntry;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import java.util.function.Function;
23 import javax.annotation.Nonnull;
24 import javax.annotation.Nullable;
25 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.BindingTreeCodec;
26 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.factory.BindingNormalizedNodeWriterFactory;
27 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.factory.BindingTreeCodecFactory;
28 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.serializer.BindingNormalizedNodeSerializer;
29 import org.opendaylight.mdsal.binding.javav2.dom.codec.generator.api.TreeNodeSerializerGenerator;
30 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.NotificationCodecContext;
31 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.BindingCodecContext;
32 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.NodeCodecContext;
33 import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.context.base.TreeNodeCodecContext;
34 import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
35 import org.opendaylight.mdsal.binding.javav2.runtime.context.ModuleInfoBackedContext;
36 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
37 import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
38 import org.opendaylight.mdsal.binding.javav2.spec.base.Instantiable;
39 import org.opendaylight.mdsal.binding.javav2.spec.base.Notification;
40 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
41 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
42 import org.opendaylight.mdsal.binding.javav2.spec.runtime.BindingStreamEventWriter;
43 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializer;
44 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerImplementation;
45 import org.opendaylight.mdsal.binding.javav2.spec.runtime.TreeNodeSerializerRegistry;
46 import org.opendaylight.yangtools.concepts.Delegator;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
57 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
58 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
59 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
60 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 /**
65  * Serializing and deserializing Binding and DOM data.
66  */
67 @Beta
68 public class BindingNormalizedNodeCodecRegistry implements TreeNodeSerializerRegistry, BindingTreeCodecFactory,
69         BindingNormalizedNodeWriterFactory, BindingNormalizedNodeSerializer {
70
71     private static final Logger LOG = LoggerFactory.getLogger(BindingNormalizedNodeCodecRegistry.class);
72
73     private final TreeNodeSerializerGenerator generator;
74     private final LoadingCache<Class<? extends TreeNode>, TreeNodeSerializer> serializers;
75     private volatile BindingCodecContext codecContext;
76
77     /**
78      * Prepare generator for generating serializers and create loader for serializers.
79      *
80      * @param generator
81      *            - serializer generator
82      */
83     public BindingNormalizedNodeCodecRegistry(final TreeNodeSerializerGenerator generator) {
84         this.generator = requireNonNull(generator);
85         this.serializers = CacheBuilder.newBuilder().weakKeys().build(new GeneratorLoader());
86     }
87
88     @Override
89     public TreeNodeSerializer getSerializer(final Class<? extends TreeNode> type) {
90         return serializers.getUnchecked(type);
91     }
92
93     /**
94      * Get binding tree codec context.
95      *
96      * @return codec context
97      */
98     public BindingTreeCodec getCodecContext() {
99         return codecContext;
100     }
101
102     /**
103      * Create codec context based on runtime context and notify generator that runtime context has been
104      * updated.
105      *
106      * @param context
107      *            - runtime context
108      */
109     public void onBindingRuntimeContextUpdated(final BindingRuntimeContext context) {
110         codecContext = create(context);
111         generator.onBindingRuntimeContextUpdated(context);
112     }
113
114     @Nullable
115     @Override
116     public YangInstanceIdentifier toYangInstanceIdentifier(@Nonnull final InstanceIdentifier<?> binding) {
117         return codecContext.getInstanceIdentifierCodec().serialize(binding);
118     }
119
120     @Nullable
121     @Override
122     public InstanceIdentifier<?> fromYangInstanceIdentifier(@Nonnull final YangInstanceIdentifier dom) {
123         return codecContext.getInstanceIdentifierCodec().deserialize(dom);
124     }
125
126     @Nullable
127     @Override
128     public <T extends TreeNode> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>
129             toNormalizedNode(final InstanceIdentifier<T> path, final T data) {
130         final NormalizedNodeResult result = new NormalizedNodeResult();
131         // We create DOM stream writer which produces normalized nodes
132         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
133
134         // We create Binding Stream Writer which translates from Binding to Normalized Nodes
135         final Entry<YangInstanceIdentifier, BindingStreamEventWriter> writeCtx =
136                 codecContext.newWriter(path, domWriter);
137
138         // We get serializer which reads binding data and uses Binding To Normalized Node writer to write
139         // result
140         try {
141             getSerializer(path.getTargetType()).serialize(data, writeCtx.getValue());
142         } catch (final IOException e) {
143             LOG.error("Unexpected failure while serializing path {} data {}", path, data, e);
144             throw new IllegalStateException("Failed to create normalized node", e);
145         }
146         return new SimpleEntry<>(writeCtx.getKey(), result.getResult());
147     }
148
149     @Nonnull
150     @SuppressWarnings("rawtypes")
151     @Override
152     public ContainerNode toNormalizedNodeNotification(@Nonnull final Notification data) {
153         final NormalizedNodeResult result = new NormalizedNodeResult();
154         // We create DOM stream writer which produces normalized nodes
155         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
156         @SuppressWarnings("unchecked")
157         final Class<? extends TreeNode> type = (Class) ((Instantiable<?>) data).implementedInterface();
158         @SuppressWarnings("unchecked")
159         final BindingStreamEventWriter writer = newNotificationWriter((Class) type, domWriter);
160         try {
161             getSerializer(type).serialize((TreeNode) data, writer);
162         } catch (final IOException e) {
163             LOG.error("Unexpected failure while serializing data {}", data, e);
164             throw new IllegalStateException("Failed to create normalized node", e);
165         }
166         return (ContainerNode) result.getResult();
167
168     }
169
170     @Nonnull
171     @SuppressWarnings("unchecked")
172     @Override
173     public ContainerNode toNormalizedNodeOperationData(@Nonnull final TreeNode data) {
174         final NormalizedNodeResult result = new NormalizedNodeResult();
175         // We create DOM stream writer which produces normalized nodes
176         final NormalizedNodeStreamWriter domWriter = ImmutableNormalizedNodeStreamWriter.from(result);
177         @SuppressWarnings("rawtypes")
178         final Class<? extends TreeNode> type = data.getClass();
179         final Class<? extends Instantiable<?>> instData = (Class<? extends Instantiable<?>>) data.getClass();
180         final BindingStreamEventWriter writer = newOperationWriter(instData, domWriter);
181         try {
182             getSerializer(type).serialize(data, writer);
183         } catch (final IOException e) {
184             LOG.error("Unexpected failure while serializing data {}", data, e);
185             throw new IllegalStateException("Failed to create normalized node", e);
186         }
187         return (ContainerNode) result.getResult();
188     }
189
190     private static boolean isBindingRepresentable(final NormalizedNode<?, ?> data) {
191         if (data instanceof ChoiceNode) {
192             return false;
193         }
194         if (data instanceof LeafNode<?>) {
195             return false;
196         }
197         if (data instanceof LeafSetNode) {
198             return false;
199         }
200         if (data instanceof LeafSetEntryNode<?>) {
201             return false;
202         }
203         if (data instanceof MapNode) {
204             return false;
205         }
206         if (data instanceof UnkeyedListNode) {
207             return false;
208         }
209
210         return true;
211     }
212
213     @Nullable
214     @Override
215     public Entry<InstanceIdentifier<?>, TreeNode> fromNormalizedNode(@Nonnull final YangInstanceIdentifier path,
216             final NormalizedNode<?, ?> data) {
217         if (!isBindingRepresentable(data)) {
218             return null;
219         }
220
221         final List<TreeArgument<?>> builder = new ArrayList<>();
222         final NodeCodecContext<?> codec = codecContext.getCodecContextNode(path, builder);
223         if (codec == null) {
224             if (data != null) {
225                 LOG.warn("Path {} does not have a binding equivalent, should have been caught earlier ({})", path,
226                         data.getClass());
227             }
228             return null;
229         }
230
231         final TreeNode lazyObj = codec.deserialize(data);
232         final InstanceIdentifier<?> bindingPath = InstanceIdentifier.create(builder);
233         return new SimpleEntry<>(bindingPath, lazyObj);
234     }
235
236     @Nullable
237     @SuppressWarnings({ "rawtypes", "unchecked" })
238     @Override
239     public Notification fromNormalizedNodeNotification(@Nonnull final SchemaPath path,
240             @Nonnull final ContainerNode data) {
241         final NotificationCodecContext<?> codec = codecContext.getNotificationContext(path);
242         return codec.deserialize(data);
243     }
244
245     @Nullable
246     @Override
247     public TreeNode fromNormalizedNodeOperationData(@Nonnull final SchemaPath path, @Nonnull final ContainerNode data) {
248         final OperationInputCodec<?> codec = codecContext.getOperationInputCodec(path);
249         return codec.deserialize(data);
250     }
251
252     @Nonnull
253     @Override
254     public Entry<YangInstanceIdentifier, BindingStreamEventWriter>
255             newWriterAndIdentifier(@Nonnull final InstanceIdentifier<?> path,
256                     @Nonnull final NormalizedNodeStreamWriter domWriter) {
257         return codecContext.newWriter(path, domWriter);
258     }
259
260     @Nonnull
261     @Override
262     public BindingStreamEventWriter newWriter(@Nonnull final InstanceIdentifier<?> path,
263             @Nonnull final NormalizedNodeStreamWriter domWriter) {
264         return codecContext.newWriterWithoutIdentifier(path, domWriter);
265     }
266
267     @Nonnull
268     @Override
269     public BindingStreamEventWriter newNotificationWriter(@Nonnull final Class<? extends Notification<?>> notification,
270             @Nonnull final NormalizedNodeStreamWriter domWriter) {
271         return codecContext.newNotificationWriter(notification, domWriter);
272     }
273
274     @Nonnull
275     @Override
276     public BindingStreamEventWriter newOperationWriter(
277             @Nonnull final Class<? extends Instantiable<?>> operationInputOrOutput,
278             @Nonnull final NormalizedNodeStreamWriter domWriter) {
279         return codecContext.newOperationWriter(operationInputOrOutput, domWriter);
280     }
281
282     /**
283      * Deserialize function based on tree node codec context resolved by binding path.
284      *
285      * @param path
286      *            - binding identifier
287      * @return function deserializer of codec context of binding path
288      */
289     public <T extends TreeNode> Function<Optional<NormalizedNode<?, ?>>, Optional<T>>
290             deserializeFunction(final InstanceIdentifier<T> path) {
291         final TreeNodeCodecContext<?, ?> ctx =
292                 (TreeNodeCodecContext<?, ?>) codecContext.getCodecContextNode(path, null);
293         return new DeserializeFunction<>(ctx);
294     }
295
296     @Override
297     public BindingCodecContext create(final BindingRuntimeContext context) {
298         return new BindingCodecContext(context, this);
299     }
300
301     @Override
302     @SuppressWarnings("checkstyle:illegalCatch")
303     public BindingCodecContext create(final SchemaContext context, final Class<?>... bindingClasses) {
304         final ModuleInfoBackedContext strategy = ModuleInfoBackedContext.create();
305         for (final Class<?> bindingCls : bindingClasses) {
306             try {
307                 strategy.registerModuleInfo(BindingReflections.getModuleInfo(bindingCls));
308             } catch (final Exception e) {
309                 throw new IllegalStateException(
310                         "Could not create BindingRuntimeContext from class " + bindingCls.getName(), e);
311             }
312         }
313         final BindingRuntimeContext runtimeCtx = BindingRuntimeContext.create(strategy, context);
314         return create(runtimeCtx);
315     }
316
317     private static final class DeserializeFunction<T> implements Function<Optional<NormalizedNode<?, ?>>, Optional<T>> {
318         private final TreeNodeCodecContext<?, ?> ctx;
319
320         DeserializeFunction(final TreeNodeCodecContext<?, ?> ctx) {
321             this.ctx = ctx;
322         }
323
324         @Nullable
325         @SuppressWarnings("unchecked")
326         @Override
327         public Optional<T> apply(@Nullable final Optional<NormalizedNode<?, ?>> input) {
328             if (input.isPresent()) {
329                 return Optional.of((T) ctx.deserialize(input.get()));
330             }
331             return Optional.empty();
332         }
333     }
334
335     private final class GeneratorLoader extends CacheLoader<Class<? extends TreeNode>, TreeNodeSerializer> {
336         @Override
337         public TreeNodeSerializer load(final Class<? extends TreeNode> key) throws Exception {
338             final TreeNodeSerializerImplementation prototype = generator.getSerializer(key);
339             return new TreeNodeSerializerProxy(prototype);
340         }
341     }
342
343     private final class TreeNodeSerializerProxy
344             implements TreeNodeSerializer, Delegator<TreeNodeSerializerImplementation> {
345         private final TreeNodeSerializerImplementation delegate;
346
347         TreeNodeSerializerProxy(final TreeNodeSerializerImplementation delegate) {
348             this.delegate = delegate;
349         }
350
351         @Override
352         public TreeNodeSerializerImplementation getDelegate() {
353             return delegate;
354         }
355
356         @Override
357         public void serialize(final TreeNode obj, final BindingStreamEventWriter stream) throws IOException {
358             delegate.serialize(BindingNormalizedNodeCodecRegistry.this, obj, stream);
359         }
360     }
361
362 }