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