Remove BindingToNormalizedNodeCodec.deserializeFunction()
[mdsal.git] / binding / mdsal-binding-dom-adapter / src / main / java / org / opendaylight / mdsal / binding / dom / adapter / BindingToNormalizedNodeCodec.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.dom.adapter;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.annotations.Beta;
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 com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterators;
21 import java.lang.reflect.Method;
22 import java.time.Instant;
23 import java.util.AbstractMap.SimpleEntry;
24 import java.util.Collection;
25 import java.util.HashSet;
26 import java.util.Map.Entry;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.concurrent.TimeUnit;
30 import java.util.stream.Collectors;
31 import javax.annotation.PreDestroy;
32 import javax.inject.Inject;
33 import javax.inject.Singleton;
34 import org.eclipse.jdt.annotation.NonNull;
35 import org.opendaylight.binding.runtime.api.BindingRuntimeContext;
36 import org.opendaylight.binding.runtime.api.BindingRuntimeGenerator;
37 import org.opendaylight.binding.runtime.api.ClassLoadingStrategy;
38 import org.opendaylight.binding.runtime.api.DefaultBindingRuntimeContext;
39 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
40 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
41 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeFactory;
42 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
43 import org.opendaylight.mdsal.binding.dom.codec.api.BindingLazyContainerNode;
44 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
45 import org.opendaylight.mdsal.binding.dom.codec.api.MissingSchemaException;
46 import org.opendaylight.mdsal.binding.dom.codec.impl.BindingNormalizedNodeCodecRegistry;
47 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
48 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
49 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
50 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
51 import org.opendaylight.yangtools.concepts.ListenerRegistration;
52 import org.opendaylight.yangtools.yang.binding.Action;
53 import org.opendaylight.yangtools.yang.binding.DataContainer;
54 import org.opendaylight.yangtools.yang.binding.DataObject;
55 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
56 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
57 import org.opendaylight.yangtools.yang.binding.Notification;
58 import org.opendaylight.yangtools.yang.binding.RpcInput;
59 import org.opendaylight.yangtools.yang.binding.RpcOutput;
60 import org.opendaylight.yangtools.yang.binding.RpcService;
61 import org.opendaylight.yangtools.yang.common.QNameModule;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
63 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
64 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
65 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
66 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
67 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
68 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
69 import org.opendaylight.yangtools.yang.model.api.Module;
70 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
71 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
72 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
73 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
74 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78 /**
79  * A combinations of {@link BindingCodecTreeFactory} and {@link BindingNormalizedNodeSerializer}, with internal
80  * caching of instance identifiers.
81  *
82  * <p>
83  * NOTE: this class is non-final to allow controller adapter migration without duplicated code.
84  */
85 @Singleton
86 public class BindingToNormalizedNodeCodec implements BindingNormalizedNodeSerializer, SchemaContextListener,
87         AutoCloseable {
88
89     private static final long WAIT_DURATION_SEC = 5;
90     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
91
92     private final LoadingCache<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
93             .softValues().build(new CacheLoader<InstanceIdentifier<?>, YangInstanceIdentifier>() {
94                 @Override
95                 public YangInstanceIdentifier load(final InstanceIdentifier<?> key) {
96                     return toYangInstanceIdentifierBlocking(key);
97                 }
98             });
99     private final BindingNormalizedNodeCodecRegistry codecRegistry;
100     private final ClassLoadingStrategy classLoadingStrategy;
101     private final BindingRuntimeGenerator generator;
102     private final FutureSchema futureSchema;
103
104     private ListenerRegistration<?> listenerRegistration;
105
106     @Inject
107     public BindingToNormalizedNodeCodec(final BindingRuntimeGenerator generator,
108             final ClassLoadingStrategy classLoadingStrategy, final BindingNormalizedNodeCodecRegistry codecRegistry) {
109         this(generator, classLoadingStrategy, codecRegistry, false);
110     }
111
112     @Beta
113     public BindingToNormalizedNodeCodec(final BindingRuntimeContext runtimeContext) {
114         generator = (final SchemaContext context) -> {
115             throw new UnsupportedOperationException("Static context assigned");
116         };
117         classLoadingStrategy = runtimeContext.getStrategy();
118         codecRegistry = new BindingNormalizedNodeCodecRegistry(runtimeContext);
119         // TODO: this should have a specialized constructor or not be needed
120         futureSchema = FutureSchema.create(0, TimeUnit.SECONDS, false);
121         futureSchema.onRuntimeContextUpdated(runtimeContext);
122     }
123
124     public BindingToNormalizedNodeCodec(final BindingRuntimeGenerator generator,
125             final ClassLoadingStrategy classLoadingStrategy, final BindingNormalizedNodeCodecRegistry codecRegistry,
126             final boolean waitForSchema) {
127         this.generator = requireNonNull(generator, "generator");
128         this.classLoadingStrategy = requireNonNull(classLoadingStrategy, "classLoadingStrategy");
129         this.codecRegistry = requireNonNull(codecRegistry, "codecRegistry");
130         this.futureSchema = FutureSchema.create(WAIT_DURATION_SEC, TimeUnit.SECONDS, waitForSchema);
131     }
132
133     public static BindingToNormalizedNodeCodec newInstance(final BindingRuntimeGenerator generator,
134             final ClassLoadingStrategy classLoadingStrategy, final DOMSchemaService schemaService) {
135         final BindingNormalizedNodeCodecRegistry codecRegistry = new BindingNormalizedNodeCodecRegistry();
136         BindingToNormalizedNodeCodec instance = new BindingToNormalizedNodeCodec(generator, classLoadingStrategy,
137             codecRegistry, true);
138         instance.listenerRegistration = schemaService.registerSchemaContextListener(instance);
139         return instance;
140     }
141
142     protected YangInstanceIdentifier toYangInstanceIdentifierBlocking(
143             final InstanceIdentifier<? extends DataObject> binding) {
144         try {
145             return codecRegistry.toYangInstanceIdentifier(binding);
146         } catch (final MissingSchemaException e) {
147             waitForSchema(decompose(binding), e);
148             return codecRegistry.toYangInstanceIdentifier(binding);
149         }
150     }
151
152     /**
153      * Translates supplied Binding Instance Identifier into NormalizedNode
154      * instance identifier.
155      *
156      * @param binding
157      *            Binding Instance Identifier
158      * @return DOM Instance Identifier
159      * @throws IllegalArgumentException
160      *             If supplied Instance Identifier is not valid.
161      */
162     public final YangInstanceIdentifier toNormalized(final InstanceIdentifier<? extends DataObject> binding) {
163         return codecRegistry.toYangInstanceIdentifier(binding);
164     }
165
166     @Override
167     public final YangInstanceIdentifier toYangInstanceIdentifier(final InstanceIdentifier<?> binding) {
168         return codecRegistry.toYangInstanceIdentifier(binding);
169     }
170
171     protected YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier<?> binding) {
172         return iiCache.getUnchecked(binding);
173     }
174
175     @Override
176     public final <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
177             final InstanceIdentifier<T> path, final T data) {
178         try {
179             return codecRegistry.toNormalizedNode(path, data);
180         } catch (final MissingSchemaException e) {
181             waitForSchema(decompose(path), e);
182             return codecRegistry.toNormalizedNode(path, data);
183         }
184     }
185
186     /**
187      * Converts Binding Map.Entry to DOM Map.Entry.
188      *
189      * <p>
190      * Same as {@link #toNormalizedNode(InstanceIdentifier, DataObject)}.
191      *
192      * @param binding Map Entry with InstanceIdentifier as key and DataObject as value.
193      * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode}
194      *         as value.
195      */
196     @SuppressWarnings({"unchecked", "rawtypes"})
197     public final Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
198             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding) {
199         return toNormalizedNode((InstanceIdentifier) binding.getKey(), binding.getValue());
200     }
201
202     @Override
203     public final Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(final YangInstanceIdentifier path,
204             final NormalizedNode<?, ?> data) {
205         return codecRegistry.fromNormalizedNode(path, data);
206     }
207
208     @Override
209     public final Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data) {
210         return codecRegistry.fromNormalizedNodeNotification(path, data);
211     }
212
213     @Override
214     public final Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data,
215             final Instant eventInstant) {
216         return codecRegistry.fromNormalizedNodeNotification(path, data, eventInstant);
217     }
218
219     @Override
220     public final DataObject fromNormalizedNodeRpcData(final SchemaPath path, final ContainerNode data) {
221         return codecRegistry.fromNormalizedNodeRpcData(path, data);
222     }
223
224     @Override
225     public <T extends RpcInput> T fromNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
226             final ContainerNode input) {
227         return codecRegistry.fromNormalizedNodeActionInput(action, input);
228     }
229
230     @Override
231     public <T extends RpcOutput> T fromNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
232             final ContainerNode output) {
233         return codecRegistry.fromNormalizedNodeActionOutput(action, output);
234     }
235
236     @Override
237     public final InstanceIdentifier<?> fromYangInstanceIdentifier(final YangInstanceIdentifier dom) {
238         return codecRegistry.fromYangInstanceIdentifier(dom);
239     }
240
241     @Override
242     public final ContainerNode toNormalizedNodeNotification(final Notification data) {
243         return codecRegistry.toNormalizedNodeNotification(data);
244     }
245
246     @Override
247     public final ContainerNode toNormalizedNodeRpcData(final DataContainer data) {
248         return codecRegistry.toNormalizedNodeRpcData(data);
249     }
250
251     @Override
252     public ContainerNode toNormalizedNodeActionInput(final Class<? extends Action<?, ?, ?>> action,
253             final RpcInput input) {
254         return codecRegistry.toNormalizedNodeActionInput(action, input);
255     }
256
257     @Override
258     public ContainerNode toNormalizedNodeActionOutput(final Class<? extends Action<?, ?, ?>> action,
259             final RpcOutput output) {
260         return codecRegistry.toNormalizedNodeActionOutput(action, output);
261     }
262
263     @Override
264     public BindingLazyContainerNode<RpcInput> toLazyNormalizedNodeActionInput(
265             final Class<? extends Action<?, ?, ?>> action, final NodeIdentifier identifier, final RpcInput input) {
266         return codecRegistry.toLazyNormalizedNodeActionInput(action, identifier, input);
267     }
268
269     @Override
270     public BindingLazyContainerNode<RpcOutput> toLazyNormalizedNodeActionOutput(
271             final Class<? extends Action<?, ?, ?>> action, final NodeIdentifier identifier, final RpcOutput output) {
272         return codecRegistry.toLazyNormalizedNodeActionOutput(action, identifier, output);
273     }
274
275     /**
276      * Returns a Binding-Aware instance identifier from normalized
277      * instance-identifier if it is possible to create representation.
278      *
279      * <p>
280      * Returns Optional.empty for cases where target is mixin node except
281      * augmentation.
282      */
283     public final Optional<InstanceIdentifier<? extends DataObject>> toBinding(final YangInstanceIdentifier normalized)
284                     throws DeserializationException {
285         try {
286             return Optional.ofNullable(codecRegistry.fromYangInstanceIdentifier(normalized));
287         } catch (final IllegalArgumentException e) {
288             return Optional.empty();
289         }
290     }
291
292     public final Optional<Entry<InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
293             final @NonNull Entry<YangInstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
294                     throws DeserializationException {
295         try {
296             /*
297              * This cast is required, due to generics behaviour in openjdk / oracle javac.
298              *
299              * <p>
300              * InstanceIdentifier has definition InstanceIdentifier<T extends DataObject>,
301              * this means '?' is always Â <? extends DataObject>. Eclipse compiler
302              * is able to determine this relationship and treats
303              * Entry<InstanceIdentifier<?>, DataObject> and Entry<InstanceIdentifier<? extends DataObject, DataObject>
304              * as assignable. However openjdk / oracle javac treats this two types
305              * as incompatible and issues a compile error.
306              *
307              * <p>
308              * It is safe to lose generic information and cast it to other generic signature.
309              */
310             @SuppressWarnings("unchecked")
311             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding = Entry.class.cast(
312                     codecRegistry.fromNormalizedNode(normalized.getKey(), normalized.getValue()));
313             return Optional.ofNullable(binding);
314         } catch (final IllegalArgumentException e) {
315             return Optional.empty();
316         }
317     }
318
319     @Override
320     public void onGlobalContextUpdated(final SchemaContext context) {
321         final BindingRuntimeContext runtimeContext = DefaultBindingRuntimeContext.create(
322             generator.generateTypeMapping(context), classLoadingStrategy);
323         codecRegistry.onBindingRuntimeContextUpdated(runtimeContext);
324         futureSchema.onRuntimeContextUpdated(runtimeContext);
325     }
326
327     public final BindingNormalizedNodeCodecRegistry getCodecRegistry() {
328         return codecRegistry;
329     }
330
331     @Override
332     @PreDestroy
333     public void close() {
334         if (listenerRegistration != null) {
335             listenerRegistration.close();
336         }
337     }
338
339     public final BindingNormalizedNodeCodecRegistry getCodecFactory() {
340         return codecRegistry;
341     }
342
343     // FIXME: This should be probably part of Binding Runtime context
344     public final ImmutableBiMap<Method, SchemaPath> getRpcMethodToSchemaPath(final Class<? extends RpcService> key) {
345         final Module module = getModuleBlocking(key);
346         final ImmutableBiMap.Builder<Method, SchemaPath> ret = ImmutableBiMap.builder();
347         try {
348             for (final RpcDefinition rpcDef : module.getRpcs()) {
349                 final Method method = findRpcMethod(key, rpcDef);
350                 ret.put(method, rpcDef.getPath());
351             }
352         } catch (final NoSuchMethodException e) {
353             throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e);
354         }
355         return ret.build();
356     }
357
358     protected ImmutableBiMap<Method, RpcDefinition> getRpcMethodToSchema(final Class<? extends RpcService> key) {
359         final Module module = getModuleBlocking(key);
360         final ImmutableBiMap.Builder<Method, RpcDefinition> ret = ImmutableBiMap.builder();
361         try {
362             for (final RpcDefinition rpcDef : module.getRpcs()) {
363                 final Method method = findRpcMethod(key, rpcDef);
364                 ret.put(method, rpcDef);
365             }
366         } catch (final NoSuchMethodException e) {
367             throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e);
368         }
369         return ret.build();
370     }
371
372     private Module getModuleBlocking(final Class<?> modeledClass) {
373         final QNameModule moduleName = BindingReflections.getQNameModule(modeledClass);
374         BindingRuntimeContext localRuntimeContext = runtimeContext();
375         Module module = localRuntimeContext == null ? null :
376             localRuntimeContext.getSchemaContext().findModule(moduleName).orElse(null);
377         if (module == null && futureSchema.waitForSchema(moduleName)) {
378             localRuntimeContext = runtimeContext();
379             checkState(localRuntimeContext != null, "BindingRuntimeContext is not available.");
380             module = localRuntimeContext.getSchemaContext().findModule(moduleName).orElse(null);
381         }
382         if (module != null) {
383             return module;
384         }
385
386         LOG.debug("Schema for {} is not available; expected module name: {}; BindingRuntimeContext: {}",
387                 modeledClass, moduleName, localRuntimeContext);
388         throw new IllegalStateException(String.format("Schema for %s is not available; expected module name: %s; "
389                 + "full BindingRuntimeContext available in debug log", modeledClass, moduleName));
390     }
391
392     private void waitForSchema(final Collection<Class<?>> binding, final MissingSchemaException exception) {
393         LOG.warn("Blocking thread to wait for schema convergence updates for {} {}", futureSchema.getDuration(),
394             futureSchema.getUnit());
395         if (!futureSchema.waitForSchema(binding)) {
396             throw exception;
397         }
398     }
399
400     private Method findRpcMethod(final Class<? extends RpcService> key, final RpcDefinition rpcDef)
401             throws NoSuchMethodException {
402         final String methodName = BindingMapping.getRpcMethodName(rpcDef.getQName());
403         final Class<?> inputClz = runtimeContext().getClassForSchema(rpcDef.getInput());
404         return key.getMethod(methodName, inputClz);
405     }
406
407     protected @NonNull Entry<InstanceIdentifier<?>, BindingDataObjectCodecTreeNode<?>> getSubtreeCodec(
408             final YangInstanceIdentifier domIdentifier) {
409
410         final BindingCodecTree currentCodecTree = codecRegistry.getCodecContext();
411         final InstanceIdentifier<?> bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier);
412         checkArgument(bindingPath != null);
413         /**
414          * If we are able to deserialize YANG instance identifier, getSubtreeCodec must
415          * return non-null value.
416          */
417         final BindingDataObjectCodecTreeNode<?> codecContext = currentCodecTree.getSubtreeCodec(bindingPath);
418         return new SimpleEntry<>(bindingPath, codecContext);
419     }
420
421     @SuppressWarnings("unchecked")
422     public final Set<Class<? extends Notification>> getNotificationClasses(final Set<SchemaPath> interested) {
423         final Set<Class<? extends Notification>> result = new HashSet<>();
424         final BindingRuntimeContext runtimeContext = runtimeContext();
425         for (final NotificationDefinition notification : runtimeContext.getSchemaContext().getNotifications()) {
426             if (interested.contains(notification.getPath())) {
427                 try {
428                     result.add((Class<? extends Notification>) runtimeContext.getClassForSchema(notification));
429                 } catch (final IllegalStateException e) {
430                     // Ignore
431                     LOG.warn("Class for {} is currently not known.", notification.getPath(), e);
432                 }
433             }
434         }
435         return result;
436     }
437
438     SchemaPath getActionPath(final Class<? extends Action<?, ?, ?>> type) {
439         final ActionDefinition schema = runtimeContext().getActionDefinition(type);
440         checkArgument(schema != null, "Failed to find schema for %s", type);
441         return schema.getPath();
442     }
443
444     private BindingRuntimeContext runtimeContext() {
445         return futureSchema.runtimeContext();
446     }
447
448     private static Collection<Class<?>> decompose(final InstanceIdentifier<?> path) {
449         return ImmutableSet.copyOf(Iterators.transform(path.getPathArguments().iterator(), PathArgument::getType));
450     }
451
452     protected NormalizedNode<?, ?> instanceIdentifierToNode(final YangInstanceIdentifier parentPath) {
453         return ImmutableNodes.fromInstanceId(runtimeContext().getSchemaContext(), parentPath);
454     }
455
456     protected Collection<DOMDataTreeIdentifier> toDOMDataTreeIdentifiers(
457             final Collection<DataTreeIdentifier<?>> subtrees) {
458         return subtrees.stream().map(this::toDOMDataTreeIdentifier).collect(Collectors.toSet());
459     }
460
461     protected DOMDataTreeIdentifier toDOMDataTreeIdentifier(final DataTreeIdentifier<?> path) {
462         final YangInstanceIdentifier domPath = toYangInstanceIdentifierBlocking(path.getRootIdentifier());
463         return new DOMDataTreeIdentifier(path.getDatastoreType(), domPath);
464     }
465 }