Switch to Objects.requireNonNull
[mdsal.git] / binding2 / mdsal-binding2-dom-codec / src / main / java / org / opendaylight / mdsal / binding / javav2 / dom / codec / impl / BindingToNormalizedNodeCodec.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 com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.Preconditions;
15 import com.google.common.cache.CacheBuilder;
16 import com.google.common.cache.CacheLoader;
17 import com.google.common.cache.LoadingCache;
18 import com.google.common.collect.ImmutableBiMap;
19 import java.lang.reflect.Method;
20 import java.util.AbstractMap.SimpleEntry;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Optional;
26 import java.util.Set;
27 import java.util.concurrent.TimeUnit;
28 import java.util.function.Function;
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import org.opendaylight.mdsal.binding.javav2.api.DataTreeIdentifier;
32 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.BindingTreeCodec;
33 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.BindingTreeNodeCodec;
34 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.factory.BindingTreeCodecFactory;
35 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.serializer.BindingNormalizedNodeSerializer;
36 import org.opendaylight.mdsal.binding.javav2.generator.impl.GeneratedClassLoadingStrategy;
37 import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
38 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
39 import org.opendaylight.mdsal.binding.javav2.spec.base.Action;
40 import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
41 import org.opendaylight.mdsal.binding.javav2.spec.base.Notification;
42 import org.opendaylight.mdsal.binding.javav2.spec.base.Rpc;
43 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeArgument;
44 import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
45 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.common.QNameModule;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
51 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
52 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
53 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
54 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
55 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.Module;
57 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
58 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
59 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
60 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
61 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
62 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 /**
67  * Codec for serialize/deserialize Binding and DOM data.
68  */
69 @Beta
70 public final class BindingToNormalizedNodeCodec
71         implements BindingTreeCodecFactory, BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable {
72
73     private static final long WAIT_DURATION_SEC = 5;
74     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
75
76     private final BindingNormalizedNodeCodecRegistry codecRegistry;
77     private final GeneratedClassLoadingStrategy classLoadingStrategy;
78     private final FutureSchema futureSchema;
79     private final LoadingCache<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
80             .softValues().build(new CacheLoader<InstanceIdentifier<?>, YangInstanceIdentifier>() {
81
82                 @Override
83                 public YangInstanceIdentifier load(@Nonnull final InstanceIdentifier<?> key) throws Exception {
84                     return toYangInstanceIdentifierBlocking(key);
85                 }
86
87             });
88
89     private volatile BindingRuntimeContext runtimeContext;
90
91     /**
92      * Init class without waiting for schema.
93      *
94      * @param classLoadingStrategy
95      *            - class loader
96      * @param codecRegistry
97      *            - codec registry
98      */
99     public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy,
100             final BindingNormalizedNodeCodecRegistry codecRegistry) {
101         this(classLoadingStrategy, codecRegistry, false);
102     }
103
104     /**
105      * Init class with waiting for schema.
106      *
107      * @param classLoadingStrategy
108      *            - class loader
109      * @param codecRegistry
110      *            - codec registry
111      * @param waitForSchema
112      *            - boolean of waiting for schema
113      */
114     public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy,
115             final BindingNormalizedNodeCodecRegistry codecRegistry, final boolean waitForSchema) {
116         this.classLoadingStrategy = requireNonNull(classLoadingStrategy, "classLoadingStrategy");
117         this.codecRegistry = requireNonNull(codecRegistry, "codecRegistry");
118         this.futureSchema = waitForSchema ? new FutureSchema(WAIT_DURATION_SEC, TimeUnit.SECONDS) : null;
119     }
120
121     /**
122      * Translates supplied Binding Instance Identifier into NormalizedNode instance identifier with waiting
123      * for schema.
124      *
125      * @param binding
126      *            - Binding Instance Identifier
127      * @return DOM Instance Identifier
128      */
129     public YangInstanceIdentifier toYangInstanceIdentifierBlocking(
130             final InstanceIdentifier<? extends TreeNode> binding) {
131         try {
132             return codecRegistry.toYangInstanceIdentifier(binding);
133         } catch (final MissingSchemaException e) {
134             waitForSchema(decompose(binding), e);
135             return codecRegistry.toYangInstanceIdentifier(binding);
136         }
137     }
138
139     /**
140      * Translates supplied Binding Instance Identifier into NormalizedNode instance identifier.
141      *
142      * @param binding
143      *            - Binding Instance Identifier
144      * @return DOM Instance Identifier
145      * @throws IllegalArgumentException
146      *             If supplied Instance Identifier is not valid.
147      */
148     public YangInstanceIdentifier toNormalized(final InstanceIdentifier<? extends TreeNode> binding) {
149         return codecRegistry.toYangInstanceIdentifier(binding);
150     }
151
152     @Nullable
153     @Override
154     public YangInstanceIdentifier toYangInstanceIdentifier(@Nonnull final InstanceIdentifier<?> binding) {
155         return codecRegistry.toYangInstanceIdentifier(binding);
156     }
157
158     /**
159      * Get cached DOM identifier of Binding identifier.
160      *
161      * @param binding
162      *            - binding identifier
163      * @return DOM identifier
164      */
165     public YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier<?> binding) {
166         return iiCache.getUnchecked(binding);
167     }
168
169     @Nullable
170     @Override
171     public <T extends TreeNode> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>
172             toNormalizedNode(final InstanceIdentifier<T> path, final T data) {
173         try {
174             return codecRegistry.toNormalizedNode(path, data);
175         } catch (final MissingSchemaException e) {
176             waitForSchema(decompose(path), e);
177             return codecRegistry.toNormalizedNode(path, data);
178         }
179     }
180
181     /**
182      * Converts Binding Map.Entry to DOM Map.Entry.
183      *
184      * <p>
185      * Same as {@link #toNormalizedNode(InstanceIdentifier, TreeNode)}.
186      *
187      * @param binding
188      *            Map Entry with InstanceIdentifier as key and DataObject as value.
189      * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode} as value.
190      */
191     @SuppressWarnings({ "unchecked", "rawtypes" })
192     @Nullable
193     public Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>
194             toNormalizedNode(final Entry<InstanceIdentifier<? extends TreeNode>, TreeNode> binding) {
195         return toNormalizedNode((InstanceIdentifier) binding.getKey(), binding.getValue());
196     }
197
198     @Nullable
199     @Override
200     public Entry<InstanceIdentifier<?>, TreeNode> fromNormalizedNode(@Nonnull final YangInstanceIdentifier path,
201             final NormalizedNode<?, ?> data) {
202         return codecRegistry.fromNormalizedNode(path, data);
203     }
204
205     @SuppressWarnings({ "rawtypes", "unchecked" })
206     @Nullable
207     @Override
208     public Notification fromNormalizedNodeNotification(@Nonnull final SchemaPath path,
209             @Nonnull final ContainerNode data) {
210         return codecRegistry.fromNormalizedNodeNotification(path, data);
211     }
212
213     @Nullable
214     @Override
215     public TreeNode fromNormalizedNodeOperationData(@Nonnull final SchemaPath path, @Nonnull final ContainerNode data) {
216         return codecRegistry.fromNormalizedNodeOperationData(path, data);
217     }
218
219     @Nullable
220     @Override
221     public InstanceIdentifier<?> fromYangInstanceIdentifier(@Nonnull final YangInstanceIdentifier dom) {
222         return codecRegistry.fromYangInstanceIdentifier(dom);
223     }
224
225     @SuppressWarnings("rawtypes")
226     @Nonnull
227     @Override
228     public ContainerNode toNormalizedNodeNotification(@Nonnull final Notification data) {
229         return codecRegistry.toNormalizedNodeNotification(data);
230     }
231
232     @Nonnull
233     @Override
234     public ContainerNode toNormalizedNodeOperationData(@Nonnull final TreeNode data) {
235         return codecRegistry.toNormalizedNodeOperationData(data);
236     }
237
238     /**
239      * Returns a Binding-Aware instance identifier from normalized instance-identifier if it is possible to
240      * create representation.
241      *
242      * <p>
243      * Returns Optional.empty for cases where target is mixin node except augmentation.
244      *
245      */
246     public Optional<InstanceIdentifier<? extends TreeNode>> toBinding(final YangInstanceIdentifier normalized)
247             throws DeserializationException {
248         try {
249             return Optional.ofNullable(codecRegistry.fromYangInstanceIdentifier(normalized));
250         } catch (final IllegalArgumentException e) {
251             return Optional.empty();
252         }
253     }
254
255     /**
256      * DOM to Binding.
257      *
258      * @param normalized
259      *            - DOM object
260      * @return Binding object
261      * @throws DeserializationException
262      *            If fail to deserialize
263      */
264     @SuppressWarnings("unchecked")
265     public Optional<Entry<InstanceIdentifier<? extends TreeNode>, TreeNode>>
266             toBinding(@Nonnull final Entry<YangInstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
267                     throws DeserializationException {
268         try {
269             final Entry<InstanceIdentifier<? extends TreeNode>, TreeNode> binding =
270                     Entry.class.cast(codecRegistry.fromNormalizedNode(normalized.getKey(), normalized.getValue()));
271             return Optional.ofNullable(binding);
272         } catch (final IllegalArgumentException e) {
273             return Optional.empty();
274         }
275     }
276
277     @Override
278     public void onGlobalContextUpdated(final SchemaContext arg0) {
279         runtimeContext = BindingRuntimeContext.create(classLoadingStrategy, arg0);
280         codecRegistry.onBindingRuntimeContextUpdated(runtimeContext);
281         if (futureSchema != null) {
282             futureSchema.onRuntimeContextUpdated(runtimeContext);
283         }
284     }
285
286     /**
287      * Prepare deserialize function of Binding identifier to DOM.
288      *
289      * @param path
290      *            - Binding identifier
291      * @return DOM function
292      */
293     public <T extends TreeNode> Function<Optional<NormalizedNode<?, ?>>, Optional<T>>
294             deserializeFunction(final InstanceIdentifier<T> path) {
295         return codecRegistry.deserializeFunction(path);
296     }
297
298     /**
299      * Get codec registry.
300      *
301      * @return codec registry
302      */
303     public BindingNormalizedNodeCodecRegistry getCodecRegistry() {
304         return codecRegistry;
305     }
306
307     @Override
308     public void close() {
309         // NOOP Intentionally
310     }
311
312     /**
313      * Get codec factory.
314      *
315      * @return codec factory
316      */
317     public BindingNormalizedNodeCodecRegistry getCodecFactory() {
318         return codecRegistry;
319     }
320
321     /**
322      * Resolve method with path of specific RPC as binding object.
323      *
324      * @param key
325      *            - RPC as binding object
326      * @return map of method with path of specific RPC
327      */
328     @Deprecated
329     public ImmutableBiMap<Method, SchemaPath> getRPCMethodToSchemaPath(final Class<?> key) {
330         final Module module = getModuleBlocking(key);
331         final ImmutableBiMap.Builder<Method, SchemaPath> ret = ImmutableBiMap.builder();
332         try {
333             for (final RpcDefinition rpcDef : module.getRpcs()) {
334                 final Method method = runtimeContext.findOperationMethod(key, rpcDef);
335                 ret.put(method, rpcDef.getPath());
336             }
337         } catch (final NoSuchMethodException e) {
338             throw new IllegalStateException("RPC defined in model does not have representation in generated class.", e);
339         }
340         return ret.build();
341     }
342
343     /**
344      * Get Action schema path.
345      *
346      * @param type
347      *            - Action implementation class type
348      * @return schema path of Action
349      */
350     public SchemaPath getActionPath(final Class<? extends Action<?, ?, ?, ?>> type) {
351         final ActionDefinition schema = runtimeContext.getActionDefinition(type);
352         checkArgument(schema != null, "Failed to find schema for %s", type);
353         return schema.getPath();
354     }
355
356     /**
357      * Get RPC schema path.
358      *
359      * @param type
360      *            - RPC implementation class type
361      * @return schema path of RPC
362      */
363     public SchemaPath getRpcPath(final Class<? extends Rpc<?, ?>> type) {
364         final RpcDefinition schema = runtimeContext.getRpcDefinition(type);
365         checkArgument(schema != null, "Failed to find schema for %s", type);
366         return schema.getPath();
367     }
368
369     public RpcDefinition getRpcDefinition(final Class<? extends Rpc<?, ?>> type) {
370         final RpcDefinition schema = runtimeContext.getRpcDefinition(type);
371         checkArgument(schema != null, "Failed to find schema for %s", type);
372         return schema;
373     }
374
375     /**
376      * Resolve method with definition of specific RPC as binding object.
377      *
378      * @param key
379      *            - RPC as binding object
380      * @return map of method with definition of specific RPC
381      */
382     public ImmutableBiMap<Method, OperationDefinition> getRPCMethodToSchema(final Class<?> key) {
383         final Module module = getModuleBlocking(key);
384         final ImmutableBiMap.Builder<Method, OperationDefinition> ret = ImmutableBiMap.builder();
385         try {
386             for (final RpcDefinition rpcDef : module.getRpcs()) {
387                 final Method method = runtimeContext.findOperationMethod(key, rpcDef);
388                 ret.put(method, rpcDef);
389             }
390         } catch (final NoSuchMethodException e) {
391             throw new IllegalStateException("RPC defined in model does not have representation in generated class.", e);
392         }
393         return ret.build();
394     }
395
396     /**
397      * Resolve method with definition of specific action as binding object.
398      *
399      * @param key
400      *            - action as binding object
401      * @return map of method with definition of specific action
402      */
403     public ImmutableBiMap<Method, OperationDefinition> getActionMethodToSchema(final Class<?> key) {
404         final Module module = getModuleBlocking(key);
405         final ImmutableBiMap.Builder<Method, OperationDefinition> ret = ImmutableBiMap.builder();
406         try {
407             for (final ActionDefinition actionDefinition : runtimeContext.getSchemaContext().getActions()) {
408                 final QName qName = actionDefinition.getQName();
409                 if (qName.getModule().equals(module.getQNameModule())) {
410                     final Method method = runtimeContext.findOperationMethod(key, actionDefinition);
411                     ret.put(method, actionDefinition);
412                 }
413             }
414         } catch (final NoSuchMethodException e) {
415             throw new IllegalStateException("Action defined in model does not have representation in generated class.",
416                     e);
417         }
418         return ret.build();
419     }
420
421     private Module getModuleBlocking(final Class<?> modeledClass) {
422         final QNameModule moduleName = BindingReflections.getQNameModule(modeledClass);
423         BindingRuntimeContext localRuntimeContext = runtimeContext;
424         Module module = localRuntimeContext == null ? null
425                 : localRuntimeContext.getSchemaContext().findModule(moduleName).get();
426         if (module == null && futureSchema != null && futureSchema.waitForSchema(moduleName)) {
427             localRuntimeContext = runtimeContext;
428             Preconditions.checkState(localRuntimeContext != null, "BindingRuntimeContext is not available.");
429             module = localRuntimeContext.getSchemaContext().findModule(moduleName).get();
430         }
431         Preconditions.checkState(module != null, "Schema for %s is not available.", modeledClass);
432         return module;
433     }
434
435     private void waitForSchema(final Collection<Class<?>> binding, final MissingSchemaException exception) {
436         if (futureSchema != null) {
437             LOG.warn("Blocking thread to wait for schema convergence updates for {} {}", futureSchema.getDuration(),
438                     futureSchema.getUnit());
439             if (!futureSchema.waitForSchema(binding)) {
440                 return;
441             }
442         }
443         throw exception;
444     }
445
446     @Override
447     public BindingTreeCodec create(final BindingRuntimeContext context) {
448         return codecRegistry.create(context);
449     }
450
451     @Override
452     public BindingTreeCodec create(final SchemaContext context, final Class<?>... bindingClasses) {
453         return codecRegistry.create(context, bindingClasses);
454     }
455
456     /**
457      * Get subtree codec of DOM identifier.
458      *
459      * @param domIdentifier
460      *            - DOM identifier
461      * @return codec for subtree
462      */
463     @Nonnull
464     public Map.Entry<InstanceIdentifier<?>, BindingTreeNodeCodec<?>>
465             getSubtreeCodec(final YangInstanceIdentifier domIdentifier) {
466
467         final BindingTreeCodec currentCodecTree = codecRegistry.getCodecContext();
468         final InstanceIdentifier<?> bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier);
469         checkArgument(bindingPath != null);
470         /**
471          * If we are able to deserialize YANG instance identifier, getSubtreeCodec must return non-null value.
472          */
473         final BindingTreeNodeCodec<?> codecContext = currentCodecTree.getSubtreeCodec(bindingPath);
474         return new SimpleEntry<>(bindingPath, codecContext);
475     }
476
477     /**
478      * Get specific notification classes as Binding objects.
479      *
480      * @param interested
481      *            - set of specific notifications paths
482      * @return notification as Binding objects according to input set of their DOM paths
483      */
484     @SuppressWarnings({ "unchecked", "rawtypes" })
485     public Set<Class<? extends Notification>> getNotificationClasses(final Set<SchemaPath> interested) {
486         final Set<Class<? extends Notification>> result = new HashSet<>();
487         final Set<NotificationDefinition> knownNotifications = runtimeContext.getSchemaContext().getNotifications();
488         for (final NotificationDefinition notification : knownNotifications) {
489             if (interested.contains(notification.getPath())) {
490                 try {
491                     result.add((Class<? extends Notification>) runtimeContext.getClassForSchema(notification));
492                 } catch (final IllegalStateException e) {
493                     // Ignore
494                     LOG.warn("Class for {} is currently not known.", notification.getPath(), e);
495                 }
496             }
497         }
498         return result;
499     }
500
501     private static Collection<Class<?>> decompose(final InstanceIdentifier<?> path) {
502         final Set<Class<?>> clazzes = new HashSet<>();
503         for (final TreeArgument<?> arg : path.getPathArguments()) {
504             clazzes.add(arg.getType());
505         }
506         return clazzes;
507     }
508
509     /**
510      * Resolve DOM object on specific DOM identifier.
511      *
512      * @param parentPath
513      *            - DOM identifier
514      * @return DOM object
515      */
516     public NormalizedNode<?, ?> instanceIdentifierToNode(final YangInstanceIdentifier parentPath) {
517         return ImmutableNodes.fromInstanceId(runtimeContext.getSchemaContext(), parentPath);
518     }
519
520     /**
521      * Get default DOM object on path for list.
522      *
523      * @param parentMapPath
524      *            - path
525      * @return specific DOM object
526      */
527     public NormalizedNode<?, ?> getDefaultNodeFor(final YangInstanceIdentifier parentMapPath) {
528         final BindingTreeNodeCodec<?> mapCodec = codecRegistry.getCodecContext().getSubtreeCodec(parentMapPath);
529         final Object schema = mapCodec.getSchema();
530         if (schema instanceof ListSchemaNode) {
531             final ListSchemaNode castedSchema = (ListSchemaNode) schema;
532             if (castedSchema.isUserOrdered()) {
533                 return Builders.orderedMapBuilder(castedSchema).build();
534             } else {
535                 return Builders.mapBuilder(castedSchema).build();
536             }
537         }
538         throw new IllegalArgumentException("Path does not point to list schema node");
539     }
540
541     /**
542      * Binding subtree identifiers to DOM subtree identifiers.
543      *
544      * @param subtrees
545      *            - binding subtree
546      * @return DOM subtree
547      */
548     public Collection<DOMDataTreeIdentifier>
549             toDOMDataTreeIdentifiers(final Collection<DataTreeIdentifier<?>> subtrees) {
550         final Set<DOMDataTreeIdentifier> ret = new HashSet<>(subtrees.size());
551
552         for (final DataTreeIdentifier<?> subtree : subtrees) {
553             ret.add(toDOMDataTreeIdentifier(subtree));
554         }
555         return ret;
556     }
557
558     //FIXME: avoid the duplication of the function above.
559     public <P extends TreeNode> Set<DOMDataTreeIdentifier>
560             toDOMDataTreeIdentifiers(final Set<DataTreeIdentifier<P>> subtrees) {
561         final Set<DOMDataTreeIdentifier> ret = new HashSet<>(subtrees.size());
562
563         for (final DataTreeIdentifier<?> subtree : subtrees) {
564             ret.add(toDOMDataTreeIdentifier(subtree));
565         }
566         return ret;
567     }
568
569     /**
570      * Create new DOM data tree identifier from Binding data tree identifier.
571      *
572      * @param path
573      *            - binding data tree identifier
574      * @return DOM data tree identifier
575      */
576     public DOMDataTreeIdentifier toDOMDataTreeIdentifier(final DataTreeIdentifier<?> path) {
577         final YangInstanceIdentifier domPath = toYangInstanceIdentifierBlocking(path.getRootIdentifier());
578         return new DOMDataTreeIdentifier(path.getDatastoreType(), domPath);
579     }
580 }
581