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