bc060f2c788ba82b2b97e49a9b6d7b1d357a22b7
[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 java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Function;
13 import com.google.common.base.Optional;
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 com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterators;
21 import java.lang.reflect.Method;
22 import java.net.URI;
23 import java.util.AbstractMap.SimpleEntry;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.HashSet;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Set;
30 import java.util.concurrent.TimeUnit;
31 import java.util.stream.Collectors;
32 import javax.annotation.Nonnull;
33 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
34 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
35 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeFactory;
36 import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTreeNode;
37 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
38 import org.opendaylight.mdsal.binding.dom.codec.impl.BindingNormalizedNodeCodecRegistry;
39 import org.opendaylight.mdsal.binding.dom.codec.impl.MissingSchemaException;
40 import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
41 import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
42 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
43 import org.opendaylight.yangtools.yang.binding.BindingMapping;
44 import org.opendaylight.yangtools.yang.binding.DataContainer;
45 import org.opendaylight.yangtools.yang.binding.DataObject;
46 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument;
48 import org.opendaylight.yangtools.yang.binding.Notification;
49 import org.opendaylight.yangtools.yang.binding.RpcService;
50 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
51 import org.opendaylight.yangtools.yang.common.QNameModule;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
55 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
56 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
57 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
58 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
60 import org.opendaylight.yangtools.yang.model.api.Module;
61 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
62 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
63 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
64 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
65 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
66 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
67 import org.opendaylight.yangtools.yang.model.api.meta.StatementSource;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 public final class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory,
72         BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable {
73
74     private static final long WAIT_DURATION_SEC = 5;
75     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
76
77     private final LoadingCache<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
78             .softValues().build(new CacheLoader<InstanceIdentifier<?>, YangInstanceIdentifier>() {
79                 @Override
80                 public YangInstanceIdentifier load(@Nonnull final InstanceIdentifier<?> key) {
81                     return toYangInstanceIdentifierBlocking(key);
82                 }
83             });
84     private final BindingNormalizedNodeCodecRegistry codecRegistry;
85     private final ClassLoadingStrategy classLoadingStrategy;
86     private final FutureSchema futureSchema;
87
88     public BindingToNormalizedNodeCodec(final ClassLoadingStrategy classLoadingStrategy,
89             final BindingNormalizedNodeCodecRegistry codecRegistry) {
90         this(classLoadingStrategy, codecRegistry, false);
91     }
92
93     public BindingToNormalizedNodeCodec(final ClassLoadingStrategy classLoadingStrategy,
94             final BindingNormalizedNodeCodecRegistry codecRegistry, final boolean waitForSchema) {
95         this.classLoadingStrategy = Preconditions.checkNotNull(classLoadingStrategy, "classLoadingStrategy");
96         this.codecRegistry = Preconditions.checkNotNull(codecRegistry, "codecRegistry");
97         this.futureSchema = FutureSchema.create(WAIT_DURATION_SEC, TimeUnit.SECONDS, waitForSchema);
98     }
99
100     YangInstanceIdentifier toYangInstanceIdentifierBlocking(final InstanceIdentifier<? extends DataObject> binding) {
101         try {
102             return codecRegistry.toYangInstanceIdentifier(binding);
103         } catch (final MissingSchemaException e) {
104             waitForSchema(decompose(binding), e);
105             return codecRegistry.toYangInstanceIdentifier(binding);
106         }
107     }
108
109     /**
110      * Translates supplied Binding Instance Identifier into NormalizedNode
111      * instance identifier.
112      *
113      * @param binding
114      *            Binding Instance Identifier
115      * @return DOM Instance Identifier
116      * @throws IllegalArgumentException
117      *             If supplied Instance Identifier is not valid.
118      */
119     public YangInstanceIdentifier toNormalized(final InstanceIdentifier<? extends DataObject> binding) {
120         return codecRegistry.toYangInstanceIdentifier(binding);
121     }
122
123     @Override
124     public YangInstanceIdentifier toYangInstanceIdentifier(@Nonnull final InstanceIdentifier<?> binding) {
125         return codecRegistry.toYangInstanceIdentifier(binding);
126     }
127
128
129     YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier<?> binding) {
130         return iiCache .getUnchecked(binding);
131     }
132
133     @Override
134     public <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
135             final InstanceIdentifier<T> path, final T data) {
136         try {
137             return codecRegistry.toNormalizedNode(path, data);
138         } catch (final MissingSchemaException e) {
139             waitForSchema(decompose(path), e);
140             return codecRegistry.toNormalizedNode(path, data);
141         }
142     }
143
144     /**
145      * Converts Binding Map.Entry to DOM Map.Entry.
146      *
147      * <p>
148      * Same as {@link #toNormalizedNode(InstanceIdentifier, DataObject)}.
149      *
150      * @param binding Map Entry with InstanceIdentifier as key and DataObject as value.
151      * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode}
152      *         as value.
153      */
154     @SuppressWarnings({"unchecked", "rawtypes"})
155     public Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
156             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding) {
157         return toNormalizedNode((InstanceIdentifier) binding.getKey(), binding.getValue());
158     }
159
160     @Override
161     public Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(@Nonnull final YangInstanceIdentifier path,
162             final NormalizedNode<?, ?> data) {
163         return codecRegistry.fromNormalizedNode(path, data);
164     }
165
166     @Override
167     public Notification fromNormalizedNodeNotification(@Nonnull final SchemaPath path,
168             @Nonnull final ContainerNode data) {
169         return codecRegistry.fromNormalizedNodeNotification(path, data);
170     }
171
172     @Override
173     public DataObject fromNormalizedNodeRpcData(@Nonnull final SchemaPath path, @Nonnull final ContainerNode data) {
174         return codecRegistry.fromNormalizedNodeRpcData(path, data);
175     }
176
177     @Override
178     public InstanceIdentifier<?> fromYangInstanceIdentifier(@Nonnull final YangInstanceIdentifier dom) {
179         return codecRegistry.fromYangInstanceIdentifier(dom);
180     }
181
182     @Override
183     public ContainerNode toNormalizedNodeNotification(@Nonnull final Notification data) {
184         return codecRegistry.toNormalizedNodeNotification(data);
185     }
186
187     @Override
188     public ContainerNode toNormalizedNodeRpcData(@Nonnull final DataContainer data) {
189         return codecRegistry.toNormalizedNodeRpcData(data);
190     }
191
192     /**
193      * Returns a Binding-Aware instance identifier from normalized
194      * instance-identifier if it is possible to create representation.
195      *
196      * <p>
197      * Returns Optional.absent for cases where target is mixin node except
198      * augmentation.
199      */
200     public Optional<InstanceIdentifier<? extends DataObject>> toBinding(final YangInstanceIdentifier normalized)
201                     throws DeserializationException {
202         try {
203             return Optional.fromNullable(codecRegistry.fromYangInstanceIdentifier(normalized));
204         } catch (final IllegalArgumentException e) {
205             return Optional.absent();
206         }
207     }
208
209     public Optional<Entry<InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
210             @Nonnull final Entry<YangInstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
211                     throws DeserializationException {
212         try {
213             /*
214              * This cast is required, due to generics behaviour in openjdk / oracle javac.
215              *
216              * <p>
217              * InstanceIdentifier has definition InstanceIdentifier<T extends DataObject>,
218              * this means '?' is always Â <? extends DataObject>. Eclipse compiler
219              * is able to determine this relationship and treats
220              * Entry<InstanceIdentifier<?>, DataObject> and Entry<InstanceIdentifier<? extends DataObject, DataObject>
221              * as assignable. However openjdk / oracle javac treats this two types
222              * as incompatible and issues a compile error.
223              *
224              * <p>
225              * It is safe to lose generic information and cast it to other generic signature.
226              */
227             @SuppressWarnings("unchecked")
228             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding = Entry.class.cast(
229                     codecRegistry.fromNormalizedNode(normalized.getKey(), normalized.getValue()));
230             return Optional.fromNullable(binding);
231         } catch (final IllegalArgumentException e) {
232             return Optional.absent();
233         }
234     }
235
236     @Override
237     public void onGlobalContextUpdated(final SchemaContext context) {
238         final BindingRuntimeContext runtimeContext = BindingRuntimeContext.create(classLoadingStrategy, context);
239         codecRegistry.onBindingRuntimeContextUpdated(runtimeContext);
240         futureSchema.onRuntimeContextUpdated(runtimeContext);
241     }
242
243     public <T extends DataObject> Function<Optional<NormalizedNode<?, ?>>, Optional<T>>
244             deserializeFunction(final InstanceIdentifier<T> path) {
245         return codecRegistry.deserializeFunction(path);
246     }
247
248     public BindingNormalizedNodeCodecRegistry getCodecRegistry() {
249         return codecRegistry;
250     }
251
252     @Override
253     public void close() {
254         // NOOP Intentionally
255     }
256
257     public BindingNormalizedNodeCodecRegistry getCodecFactory() {
258         return codecRegistry;
259     }
260
261     // FIXME: This should be probably part of Binding Runtime context
262     public ImmutableBiMap<Method, SchemaPath> getRpcMethodToSchemaPath(final Class<? extends RpcService> key) {
263         final Module module = getModuleBlocking(key);
264         final ImmutableBiMap.Builder<Method, SchemaPath> ret = ImmutableBiMap.builder();
265         try {
266             for (final RpcDefinition rpcDef : module.getRpcs()) {
267                 final Method method = findRpcMethod(key, rpcDef);
268                 ret.put(method, rpcDef.getPath());
269             }
270         } catch (final NoSuchMethodException e) {
271             throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e);
272         }
273         return ret.build();
274     }
275
276     protected ImmutableBiMap<Method, RpcDefinition> getRpcMethodToSchema(final Class<? extends RpcService> key) {
277         final Module module = getModuleBlocking(key);
278         final ImmutableBiMap.Builder<Method, RpcDefinition> ret = ImmutableBiMap.builder();
279         try {
280             for (final RpcDefinition rpcDef : module.getRpcs()) {
281                 final Method method = findRpcMethod(key, rpcDef);
282                 ret.put(method, rpcDef);
283             }
284         } catch (final NoSuchMethodException e) {
285             throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e);
286         }
287         return ret.build();
288     }
289
290     private Module getModuleBlocking(final Class<?> modeledClass) {
291         final QNameModule moduleName = BindingReflections.getQNameModule(modeledClass);
292         final URI namespace = moduleName.getNamespace();
293         final Date revision = moduleName.getRevision();
294         BindingRuntimeContext localRuntimeContext = runtimeContext();
295         Module module = localRuntimeContext == null ? null :
296             localRuntimeContext.getSchemaContext().findModuleByNamespaceAndRevision(namespace, revision);
297         if (module == null && futureSchema.waitForSchema(namespace,revision)) {
298             localRuntimeContext = runtimeContext();
299             Preconditions.checkState(localRuntimeContext != null, "BindingRuntimeContext is not available.");
300             module = localRuntimeContext.getSchemaContext().findModuleByNamespaceAndRevision(namespace, revision);
301         }
302         Preconditions.checkState(module != null, "Schema for %s is not available.", modeledClass);
303         return module;
304     }
305
306     private void waitForSchema(final Collection<Class<?>> binding, final MissingSchemaException exception) {
307         LOG.warn("Blocking thread to wait for schema convergence updates for {} {}", futureSchema.getDuration(),
308             futureSchema.getUnit());
309         if (!futureSchema.waitForSchema(binding)) {
310             throw exception;
311         }
312     }
313
314     private Method findRpcMethod(final Class<? extends RpcService> key, final RpcDefinition rpcDef)
315             throws NoSuchMethodException {
316         final String methodName = BindingMapping.getMethodName(rpcDef.getQName());
317         if (rpcDef.getInput() != null && isExplicitStatement(rpcDef.getInput())) {
318             final Class<?> inputClz = runtimeContext().getClassForSchema(rpcDef.getInput());
319             return key.getMethod(methodName, inputClz);
320         }
321         return key.getMethod(methodName);
322     }
323
324     private static boolean isExplicitStatement(final ContainerSchemaNode node) {
325         return node instanceof EffectiveStatement
326                 && ((EffectiveStatement<?, ?>) node).getDeclared().getStatementSource() == StatementSource.DECLARATION;
327     }
328
329     @Override
330     public BindingCodecTree create(final BindingRuntimeContext context) {
331         return codecRegistry.create(context);
332     }
333
334     @Override
335     public BindingCodecTree create(final SchemaContext context, final Class<?>... bindingClasses) {
336         return codecRegistry.create(context, bindingClasses);
337     }
338
339     @Nonnull
340     protected Map.Entry<InstanceIdentifier<?>, BindingCodecTreeNode<?>> getSubtreeCodec(
341             final YangInstanceIdentifier domIdentifier) {
342
343         final BindingCodecTree currentCodecTree = codecRegistry.getCodecContext();
344         final InstanceIdentifier<?> bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier);
345         Preconditions.checkArgument(bindingPath != null);
346         /**
347          * If we are able to deserialize YANG instance identifier, getSubtreeCodec must
348          * return non-null value.
349          */
350         final BindingCodecTreeNode<?> codecContext = currentCodecTree.getSubtreeCodec(bindingPath);
351         return new SimpleEntry<>(bindingPath, codecContext);
352     }
353
354     @SuppressWarnings("unchecked")
355     public Set<Class<? extends Notification>> getNotificationClasses(final Set<SchemaPath> interested) {
356         final Set<Class<? extends Notification>> result = new HashSet<>();
357         final BindingRuntimeContext runtimeContext = runtimeContext();
358         for (final NotificationDefinition notification : runtimeContext.getSchemaContext().getNotifications()) {
359             if (interested.contains(notification.getPath())) {
360                 try {
361                     result.add((Class<? extends Notification>) runtimeContext.getClassForSchema(notification));
362                 } catch (final IllegalStateException e) {
363                     // Ignore
364                     LOG.warn("Class for {} is currently not known.", notification.getPath(), e);
365                 }
366             }
367         }
368         return result;
369     }
370
371     private BindingRuntimeContext runtimeContext() {
372         return futureSchema.runtimeContext();
373     }
374
375     private static Collection<Class<?>> decompose(final InstanceIdentifier<?> path) {
376         return ImmutableSet.copyOf(Iterators.transform(path.getPathArguments().iterator(), PathArgument::getType));
377     }
378
379     protected NormalizedNode<?, ?> instanceIdentifierToNode(final YangInstanceIdentifier parentPath) {
380         return ImmutableNodes.fromInstanceId(runtimeContext().getSchemaContext(), parentPath);
381     }
382
383     public NormalizedNode<?, ?> getDefaultNodeFor(final YangInstanceIdentifier parentMapPath) {
384         final BindingCodecTreeNode<?> mapCodec = requireNonNull(
385                 codecRegistry.getCodecContext().getSubtreeCodec(parentMapPath),
386                 "Codec not found for yang instance identifier: " + parentMapPath);
387         final Object schema = mapCodec.getSchema();
388         if (schema instanceof ListSchemaNode) {
389             final ListSchemaNode castedSchema = (ListSchemaNode) schema;
390             return castedSchema.isUserOrdered() ? Builders.orderedMapBuilder(castedSchema).build()
391                     : Builders.mapBuilder(castedSchema).build();
392         }
393         throw new IllegalArgumentException("Path does not point to list schema node");
394     }
395
396     protected Collection<DOMDataTreeIdentifier> toDOMDataTreeIdentifiers(
397             final Collection<DataTreeIdentifier<?>> subtrees) {
398         return subtrees.stream().map(this::toDOMDataTreeIdentifier).collect(Collectors.toSet());
399     }
400
401     protected DOMDataTreeIdentifier toDOMDataTreeIdentifier(final DataTreeIdentifier<?> path) {
402         final YangInstanceIdentifier domPath = toYangInstanceIdentifierBlocking(path.getRootIdentifier());
403         return new DOMDataTreeIdentifier(path.getDatastoreType(), domPath);
404     }
405 }