Binding v2 runtime - adapters - impl - operations
[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.net.URI;
20 import java.util.AbstractMap.SimpleEntry;
21 import java.util.Collection;
22 import java.util.Date;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.concurrent.TimeUnit;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import org.opendaylight.mdsal.binding.javav2.api.DataTreeIdentifier;
31 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.BindingTreeCodec;
32 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.BindingTreeNodeCodec;
33 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.factory.BindingTreeCodecFactory;
34 import org.opendaylight.mdsal.binding.javav2.dom.codec.api.serializer.BindingNormalizedNodeSerializer;
35 import org.opendaylight.mdsal.binding.javav2.generator.impl.GeneratedClassLoadingStrategy;
36 import org.opendaylight.mdsal.binding.javav2.runtime.context.BindingRuntimeContext;
37 import org.opendaylight.mdsal.binding.javav2.runtime.reflection.BindingReflections;
38 import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
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.dom.api.DOMDataTreeIdentifier;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.common.QNameModule;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
48 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
49 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
50 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
51 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
52 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.Module;
54 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
55 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
56 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
57 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
58 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
59 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * Codec for serialize/deserialize Binding and DOM data.
65  */
66 @Beta
67 public final class BindingToNormalizedNodeCodec
68         implements BindingTreeCodecFactory, BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable {
69
70     private static final long WAIT_DURATION_SEC = 5;
71     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
72
73     private final BindingNormalizedNodeCodecRegistry codecRegistry;
74     private final GeneratedClassLoadingStrategy classLoadingStrategy;
75     private final FutureSchema futureSchema;
76     private final LoadingCache<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
77             .softValues().build(new CacheLoader<InstanceIdentifier<?>, YangInstanceIdentifier>() {
78
79                 @Override
80                 public YangInstanceIdentifier load(@Nonnull final InstanceIdentifier<?> key) throws Exception {
81                     return toYangInstanceIdentifierBlocking(key);
82                 }
83
84             });
85
86     private volatile BindingRuntimeContext runtimeContext;
87
88     /**
89      * Init class without waiting for schema.
90      *
91      * @param classLoadingStrategy
92      *            - class loader
93      * @param codecRegistry
94      *            - codec registry
95      */
96     public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy,
97             final BindingNormalizedNodeCodecRegistry codecRegistry) {
98         this(classLoadingStrategy, codecRegistry, false);
99     }
100
101     /**
102      * Init class with waiting for schema.
103      *
104      * @param classLoadingStrategy
105      *            - class loader
106      * @param codecRegistry
107      *            - codec registry
108      * @param waitForSchema
109      *            - boolean of waiting for schema
110      */
111     public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy,
112             final BindingNormalizedNodeCodecRegistry codecRegistry, final boolean waitForSchema) {
113         this.classLoadingStrategy = Preconditions.checkNotNull(classLoadingStrategy, "classLoadingStrategy");
114         this.codecRegistry = Preconditions.checkNotNull(codecRegistry, "codecRegistry");
115         this.futureSchema = waitForSchema ? new FutureSchema(WAIT_DURATION_SEC, TimeUnit.SECONDS) : null;
116     }
117
118     /**
119      * Translates supplied Binding Instance Identifier into NormalizedNode instance identifier with waiting
120      * for schema.
121      *
122      * @param binding
123      *            - Binding Instance Identifier
124      * @return DOM Instance Identifier
125      */
126     public YangInstanceIdentifier toYangInstanceIdentifierBlocking(final InstanceIdentifier<? extends TreeNode> binding) {
127         try {
128             return codecRegistry.toYangInstanceIdentifier(binding);
129         } catch (final MissingSchemaException e) {
130             waitForSchema(decompose(binding), e);
131             return codecRegistry.toYangInstanceIdentifier(binding);
132         }
133     }
134
135     /**
136      * Translates supplied Binding Instance Identifier into NormalizedNode instance identifier.
137      *
138      * @param binding
139      *            - Binding Instance Identifier
140      * @return DOM Instance Identifier
141      * @throws IllegalArgumentException
142      *             If supplied Instance Identifier is not valid.
143      */
144     public YangInstanceIdentifier toNormalized(final InstanceIdentifier<? extends TreeNode> binding) {
145         return codecRegistry.toYangInstanceIdentifier(binding);
146     }
147
148     @Nullable
149     @Override
150     public YangInstanceIdentifier toYangInstanceIdentifier(@Nonnull final InstanceIdentifier<?> binding) {
151         return codecRegistry.toYangInstanceIdentifier(binding);
152     }
153
154     /**
155      * Get cached DOM identifier of Binding identifier.
156      *
157      * @param binding
158      *            - binding identifier
159      * @return DOM identifier
160      */
161     public YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier<?> binding) {
162         return iiCache.getUnchecked(binding);
163     }
164
165     @Nullable
166     @Override
167     public <T extends TreeNode> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>
168             toNormalizedNode(final InstanceIdentifier<T> path, final T data) {
169         try {
170             return codecRegistry.toNormalizedNode(path, data);
171         } catch (final MissingSchemaException e) {
172             waitForSchema(decompose(path), e);
173             return codecRegistry.toNormalizedNode(path, data);
174         }
175     }
176
177     /**
178      * Converts Binding Map.Entry to DOM Map.Entry.
179      *
180      * <p>
181      * Same as {@link #toNormalizedNode(InstanceIdentifier, TreeNode)}.
182      *
183      * @param binding
184      *            Map Entry with InstanceIdentifier as key and DataObject as value.
185      * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode} as value.
186      */
187     @SuppressWarnings({ "unchecked", "rawtypes" })
188     @Nullable
189     public Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>
190             toNormalizedNode(final Entry<InstanceIdentifier<? extends TreeNode>, TreeNode> binding) {
191         return toNormalizedNode((InstanceIdentifier) binding.getKey(), binding.getValue());
192     }
193
194     @Nullable
195     @Override
196     public Entry<InstanceIdentifier<?>, TreeNode> fromNormalizedNode(@Nonnull final YangInstanceIdentifier path,
197             final NormalizedNode<?, ?> data) {
198         return codecRegistry.fromNormalizedNode(path, data);
199     }
200
201     @SuppressWarnings({ "rawtypes", "unchecked" })
202     @Nullable
203     @Override
204     public Notification fromNormalizedNodeNotification(@Nonnull final SchemaPath path,
205             @Nonnull final ContainerNode data) {
206         return codecRegistry.fromNormalizedNodeNotification(path, data);
207     }
208
209     @Nullable
210     @Override
211     public TreeNode fromNormalizedNodeOperationData(@Nonnull final SchemaPath path, @Nonnull final ContainerNode data) {
212         return codecRegistry.fromNormalizedNodeOperationData(path, data);
213     }
214
215     @Nullable
216     @Override
217     public InstanceIdentifier<?> fromYangInstanceIdentifier(@Nonnull final YangInstanceIdentifier dom) {
218         return codecRegistry.fromYangInstanceIdentifier(dom);
219     }
220
221     @SuppressWarnings("rawtypes")
222     @Nonnull
223     @Override
224     public ContainerNode toNormalizedNodeNotification(@Nonnull final Notification data) {
225         return codecRegistry.toNormalizedNodeNotification(data);
226     }
227
228     @Nonnull
229     @Override
230     public ContainerNode toNormalizedNodeOperationData(@Nonnull final TreeNode data) {
231         return codecRegistry.toNormalizedNodeOperationData(data);
232     }
233
234     /**
235      * Returns a Binding-Aware instance identifier from normalized instance-identifier if it is possible to
236      * create representation.
237      *
238      * <p>
239      * Returns Optional.absent for cases where target is mixin node except augmentation.
240      *
241      */
242     public Optional<InstanceIdentifier<? extends TreeNode>> toBinding(final YangInstanceIdentifier normalized)
243             throws DeserializationException {
244         try {
245             return Optional.fromNullable(codecRegistry.fromYangInstanceIdentifier(normalized));
246         } catch (final IllegalArgumentException e) {
247             return Optional.absent();
248         }
249     }
250
251     /**
252      * DOM to Binding.
253      *
254      * @param normalized
255      *            - DOM object
256      * @return Binding object
257      * @throws DeserializationException
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         final URI namespace = moduleName.getNamespace();
413         final Date revision = moduleName.getRevision();
414         BindingRuntimeContext localRuntimeContext = runtimeContext;
415         Module module = localRuntimeContext == null ? null
416                 : localRuntimeContext.getSchemaContext().findModuleByNamespaceAndRevision(namespace, revision);
417         if (module == null && futureSchema != null && futureSchema.waitForSchema(namespace, revision)) {
418             localRuntimeContext = runtimeContext;
419             Preconditions.checkState(localRuntimeContext != null, "BindingRuntimeContext is not available.");
420             module = localRuntimeContext.getSchemaContext().findModuleByNamespaceAndRevision(namespace, revision);
421         }
422         Preconditions.checkState(module != null, "Schema for %s is not available.", modeledClass);
423         return module;
424     }
425
426     private void waitForSchema(final Collection<Class<?>> binding, final MissingSchemaException exception) {
427         if (futureSchema != null) {
428             LOG.warn("Blocking thread to wait for schema convergence updates for {} {}", futureSchema.getDuration(),
429                     futureSchema.getUnit());
430             if (!futureSchema.waitForSchema(binding)) {
431                 return;
432             }
433         }
434         throw exception;
435     }
436
437     @Override
438     public BindingTreeCodec create(final BindingRuntimeContext context) {
439         return codecRegistry.create(context);
440     }
441
442     @Override
443     public BindingTreeCodec create(final SchemaContext context, final Class<?>... bindingClasses) {
444         return codecRegistry.create(context, bindingClasses);
445     }
446
447     /**
448      * Get subtree codec of DOM identifier.
449      *
450      * @param domIdentifier
451      *            - DOM identifier
452      * @return codec for subtree
453      */
454     @Nonnull
455     public Map.Entry<InstanceIdentifier<?>, BindingTreeNodeCodec<?>>
456             getSubtreeCodec(final YangInstanceIdentifier domIdentifier) {
457
458         final BindingTreeCodec currentCodecTree = codecRegistry.getCodecContext();
459         final InstanceIdentifier<?> bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier);
460         Preconditions.checkArgument(bindingPath != null);
461         /**
462          * If we are able to deserialize YANG instance identifier, getSubtreeCodec must return non-null value.
463          */
464         final BindingTreeNodeCodec<?> codecContext = currentCodecTree.getSubtreeCodec(bindingPath);
465         return new SimpleEntry<>(bindingPath, codecContext);
466     }
467
468     /**
469      * Get specific notification classes as Binding objects.
470      *
471      * @param interested
472      *            - set of specific notifications paths
473      * @return notification as Binding objects according to input set of their DOM paths
474      */
475     @SuppressWarnings({ "unchecked", "rawtypes" })
476     public Set<Class<? extends Notification>> getNotificationClasses(final Set<SchemaPath> interested) {
477         final Set<Class<? extends Notification>> result = new HashSet<>();
478         final Set<NotificationDefinition> knownNotifications = runtimeContext.getSchemaContext().getNotifications();
479         for (final NotificationDefinition notification : knownNotifications) {
480             if (interested.contains(notification.getPath())) {
481                 try {
482                     result.add((Class<? extends Notification>) runtimeContext.getClassForSchema(notification));
483                 } catch (final IllegalStateException e) {
484                     // Ignore
485                     LOG.warn("Class for {} is currently not known.", notification.getPath(), e);
486                 }
487             }
488         }
489         return result;
490     }
491
492     private static Collection<Class<?>> decompose(final InstanceIdentifier<?> path) {
493         final Set<Class<?>> clazzes = new HashSet<>();
494         for (final TreeArgument<?> arg : path.getPathArguments()) {
495             clazzes.add(arg.getType());
496         }
497         return clazzes;
498     }
499
500     /**
501      * Resolve DOM object on specific DOM identifier.
502      *
503      * @param parentPath
504      *            - DOM identifier
505      * @return DOM object
506      */
507     public NormalizedNode<?, ?> instanceIdentifierToNode(final YangInstanceIdentifier parentPath) {
508         return ImmutableNodes.fromInstanceId(runtimeContext.getSchemaContext(), parentPath);
509     }
510
511     /**
512      * Get default DOM object on path for list.
513      *
514      * @param parentMapPath
515      *            - path
516      * @return specific DOM object
517      */
518     public NormalizedNode<?, ?> getDefaultNodeFor(final YangInstanceIdentifier parentMapPath) {
519         final BindingTreeNodeCodec<?> mapCodec = codecRegistry.getCodecContext().getSubtreeCodec(parentMapPath);
520         final Object schema = mapCodec.getSchema();
521         if (schema instanceof ListSchemaNode) {
522             final ListSchemaNode castedSchema = (ListSchemaNode) schema;
523             if (castedSchema.isUserOrdered()) {
524                 return Builders.orderedMapBuilder(castedSchema).build();
525             } else {
526                 return Builders.mapBuilder(castedSchema).build();
527             }
528         }
529         throw new IllegalArgumentException("Path does not point to list schema node");
530     }
531
532     /**
533      * Binding subtree identifiers to DOM subtree identifiers.
534      *
535      * @param subtrees
536      *            - binding subtree
537      * @return DOM subtree
538      */
539     public Collection<DOMDataTreeIdentifier>
540             toDOMDataTreeIdentifiers(final Collection<DataTreeIdentifier<?>> subtrees) {
541         final Set<DOMDataTreeIdentifier> ret = new HashSet<>(subtrees.size());
542
543         for (final DataTreeIdentifier<?> subtree : subtrees) {
544             ret.add(toDOMDataTreeIdentifier(subtree));
545         }
546         return ret;
547     }
548
549     /**
550      * Create new DOM data tree identifier from Binding data tree identifier.
551      *
552      * @param path
553      *            - binding data tree identifier
554      * @return DOM data tree identifier
555      */
556     public DOMDataTreeIdentifier toDOMDataTreeIdentifier(final DataTreeIdentifier<?> path) {
557         final YangInstanceIdentifier domPath = toYangInstanceIdentifierBlocking(path.getRootIdentifier());
558         return new DOMDataTreeIdentifier(path.getDatastoreType(), domPath);
559     }
560 }
561