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