Bug 2351 Speed-up Binding-to-Binding routed RPC Invocation.
[controller.git] / opendaylight / md-sal / sal-binding-broker / 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.util.AbstractMap.SimpleEntry;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 import java.util.Set;
24 import javax.annotation.Nonnull;
25 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
26 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
27 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
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.sal.binding.generator.impl.GeneratedClassLoadingStrategy;
34 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
35 import org.opendaylight.yangtools.yang.binding.BindingMapping;
36 import org.opendaylight.yangtools.yang.binding.DataContainer;
37 import org.opendaylight.yangtools.yang.binding.DataObject;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.opendaylight.yangtools.yang.binding.Notification;
40 import org.opendaylight.yangtools.yang.binding.RpcService;
41 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
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.YangInstanceIdentifier.PathArgument;
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.model.api.Module;
49 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
50 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
51 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
52 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
53 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
54
55 public final class BindingToNormalizedNodeCodec implements BindingCodecTreeFactory, BindingNormalizedNodeSerializer, SchemaContextListener, AutoCloseable {
56
57     private final BindingNormalizedNodeCodecRegistry codecRegistry;
58     private DataNormalizer legacyToNormalized;
59     private final GeneratedClassLoadingStrategy classLoadingStrategy;
60     private BindingRuntimeContext runtimeContext;
61     private final LoadingCache<InstanceIdentifier<?>, YangInstanceIdentifier> iiCache = CacheBuilder.newBuilder()
62             .softValues().build(new CacheLoader<InstanceIdentifier<?>, YangInstanceIdentifier>() {
63
64                 @Override
65                 public YangInstanceIdentifier load(final InstanceIdentifier<?> key) throws Exception {
66                     return toYangInstanceIdentifier(key);
67                 }
68
69             });
70
71     public BindingToNormalizedNodeCodec(final GeneratedClassLoadingStrategy classLoadingStrategy,
72             final BindingNormalizedNodeCodecRegistry codecRegistry) {
73         this.classLoadingStrategy = Preconditions.checkNotNull(classLoadingStrategy,"classLoadingStrategy");
74         this.codecRegistry = Preconditions.checkNotNull(codecRegistry,"codecRegistry");
75
76     }
77
78     /**
79      * Translates supplied Binding Instance Identifier into NormalizedNode
80      * instance identifier.
81      *
82      * @param binding
83      *            Binding Instance Identifier
84      * @return DOM Instance Identifier
85      * @throws IllegalArgumentException
86      *             If supplied Instance Identifier is not valid.
87      */
88     public YangInstanceIdentifier toNormalized(final InstanceIdentifier<? extends DataObject> binding) {
89         return codecRegistry.toYangInstanceIdentifier(binding);
90     }
91
92     @Override
93     public YangInstanceIdentifier toYangInstanceIdentifier(final InstanceIdentifier<?> binding) {
94         return codecRegistry.toYangInstanceIdentifier(binding);
95     }
96
97
98     YangInstanceIdentifier toYangInstanceIdentifierCached(final InstanceIdentifier<?> binding) {
99         return iiCache .getUnchecked(binding);
100     }
101
102     @Override
103     public <T extends DataObject> Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
104             final InstanceIdentifier<T> path, final T data) {
105         return codecRegistry.toNormalizedNode(path, data);
106     }
107
108     /**
109      * Converts Binding Map.Entry to DOM Map.Entry
110      *
111      * Same as {@link #toNormalizedNode(InstanceIdentifier, DataObject)}.
112      *
113      * @param binding Map Entry with InstanceIdentifier as key and DataObject as value.
114      * @return DOM Map Entry with {@link YangInstanceIdentifier} as key and {@link NormalizedNode}
115      *         as value.
116      */
117     @SuppressWarnings({"unchecked", "rawtypes"})
118     public Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
119             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding) {
120         return toNormalizedNode((InstanceIdentifier) binding.getKey(),binding.getValue());
121     }
122
123     @Override
124     public Entry<InstanceIdentifier<?>, DataObject> fromNormalizedNode(final YangInstanceIdentifier path,
125             final NormalizedNode<?, ?> data) {
126         return codecRegistry.fromNormalizedNode(path, data);
127     }
128
129     @Override
130     public Notification fromNormalizedNodeNotification(final SchemaPath path, final ContainerNode data) {
131         return codecRegistry.fromNormalizedNodeNotification(path, data);
132     }
133
134     @Override
135     public DataObject fromNormalizedNodeRpcData(final SchemaPath path, final ContainerNode data) {
136         return codecRegistry.fromNormalizedNodeRpcData(path, data);
137     }
138
139     @Override
140     public InstanceIdentifier<?> fromYangInstanceIdentifier(final YangInstanceIdentifier dom) {
141         return codecRegistry.fromYangInstanceIdentifier(dom);
142     }
143
144     @Override
145     public ContainerNode toNormalizedNodeNotification(final Notification data) {
146         return codecRegistry.toNormalizedNodeNotification(data);
147     }
148
149     @Override
150     public ContainerNode toNormalizedNodeRpcData(final DataContainer data) {
151         return codecRegistry.toNormalizedNodeRpcData(data);
152     }
153
154     /**
155      *
156      * Returns a Binding-Aware instance identifier from normalized
157      * instance-identifier if it is possible to create representation.
158      *
159      * Returns Optional.absent for cases where target is mixin node except
160      * augmentation.
161      *
162      */
163     public Optional<InstanceIdentifier<? extends DataObject>> toBinding(final YangInstanceIdentifier normalized)
164                     throws DeserializationException {
165         try {
166             return Optional.<InstanceIdentifier<? extends DataObject>>fromNullable(codecRegistry.fromYangInstanceIdentifier(normalized));
167         } catch (final IllegalArgumentException e) {
168             return Optional.absent();
169         }
170     }
171
172     public DataNormalizer getDataNormalizer() {
173         return legacyToNormalized;
174     }
175
176     public Optional<Entry<InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
177             final @Nonnull Entry<YangInstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
178                     throws DeserializationException {
179         try {
180             /*
181              * This cast is required, due to generics behaviour in openjdk / oracle javac
182              *
183              * InstanceIdentifier has definition InstanceIdentifier<T extends DataObject>,
184              * this means '?' is always  <? extends DataObject>. Eclipse compiler
185              * is able to determine this relationship and treats
186              * Entry<InstanceIdentifier<?>,DataObject> and Entry<InstanceIdentifier<? extends DataObject,DataObject>
187              * as assignable. However openjdk / oracle javac treats this two types
188              * as incompatible and issues a compile error.
189              *
190              * It is safe to  loose generic information and cast it to other generic signature.
191              *
192              */
193             @SuppressWarnings({ "unchecked", "rawtypes" })
194             final Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding = Entry.class.cast(codecRegistry.fromNormalizedNode(normalized.getKey(), normalized.getValue()));
195             return Optional.fromNullable(binding);
196         } catch (final IllegalArgumentException e) {
197             return Optional.absent();
198         }
199     }
200
201     @Override
202     public void onGlobalContextUpdated(final SchemaContext arg0) {
203         legacyToNormalized = new DataNormalizer (arg0);
204         runtimeContext = BindingRuntimeContext.create(classLoadingStrategy, arg0);
205         codecRegistry.onBindingRuntimeContextUpdated(runtimeContext);
206     }
207
208     public <T extends DataObject> Function<Optional<NormalizedNode<?, ?>>, Optional<T>>  deserializeFunction(final InstanceIdentifier<T> path) {
209         return codecRegistry.deserializeFunction(path);
210     }
211
212     /**
213      * Returns an default object according to YANG schema for supplied path.
214      *
215      * @param path DOM Path
216      * @return Node with defaults set on.
217      */
218     public NormalizedNode<?, ?> getDefaultNodeFor(final YangInstanceIdentifier path) {
219         final Iterator<PathArgument> iterator = path.getPathArguments().iterator();
220         DataNormalizationOperation<?> currentOp = legacyToNormalized.getRootOperation();
221         while (iterator.hasNext()) {
222             final PathArgument currentArg = iterator.next();
223             try {
224                 currentOp = currentOp.getChild(currentArg);
225             } catch (final DataNormalizationException e) {
226                 throw new IllegalArgumentException(String.format("Invalid child encountered in path %s", path), e);
227             }
228         }
229         return currentOp.createDefault(path.getLastPathArgument());
230     }
231
232     public BindingNormalizedNodeCodecRegistry getCodecRegistry() {
233         return codecRegistry;
234     }
235
236     @Override
237     public void close() {
238         // NOOP Intentionally
239     }
240
241     public BindingNormalizedNodeCodecRegistry getCodecFactory() {
242         return codecRegistry;
243     }
244
245     // FIXME: This should be probably part of Binding Runtime context
246     public ImmutableBiMap<Method, SchemaPath> getRpcMethodToSchemaPath(final Class<? extends RpcService> key) {
247         final QNameModule moduleName = BindingReflections.getQNameModule(key);
248         final Module module = runtimeContext.getSchemaContext().findModuleByNamespaceAndRevision(moduleName.getNamespace(), moduleName.getRevision());
249         final ImmutableBiMap.Builder<Method, SchemaPath> ret = ImmutableBiMap.<Method, SchemaPath>builder();
250         try {
251             for (final RpcDefinition rpcDef : module.getRpcs()) {
252                 final Method method = findRpcMethod(key, rpcDef);
253                 ret.put(method, rpcDef.getPath());
254             }
255         } catch (final NoSuchMethodException e) {
256             throw new IllegalStateException("Rpc defined in model does not have representation in generated class.", e);
257         }
258         return ret.build();
259     }
260
261     protected ImmutableBiMap<Method, RpcDefinition> getRpcMethodToSchema(final Class<? extends RpcService> key) {
262         final QNameModule moduleName = BindingReflections.getQNameModule(key);
263         final Module module = runtimeContext.getSchemaContext().findModuleByNamespaceAndRevision(moduleName.getNamespace(), moduleName.getRevision());
264         final ImmutableBiMap.Builder<Method, RpcDefinition> ret = ImmutableBiMap.builder();
265         try {
266             for (final RpcDefinition rpcDef : module.getRpcs()) {
267                 final Method method = findRpcMethod(key, rpcDef);
268                 ret.put(method, rpcDef);
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     private Method findRpcMethod(final Class<? extends RpcService> key, final RpcDefinition rpcDef) throws NoSuchMethodException {
277         final String methodName = BindingMapping.getMethodName(rpcDef.getQName());
278         if(rpcDef.getInput() != null) {
279             final Class<?> inputClz = runtimeContext.getClassForSchema(rpcDef.getInput());
280             return key.getMethod(methodName, inputClz);
281         }
282         return key.getMethod(methodName);
283     }
284
285     @Override
286     public BindingCodecTree create(final BindingRuntimeContext context) {
287         return codecRegistry.create(context);
288     }
289
290     @Override
291     public BindingCodecTree create(final SchemaContext context, final Class<?>... bindingClasses) {
292         return codecRegistry.create(context, bindingClasses);
293     }
294
295     @Nonnull
296     protected Map.Entry<InstanceIdentifier<?>, BindingCodecTreeNode<?>> getSubtreeCodec(
297             final YangInstanceIdentifier domIdentifier) {
298
299         final BindingCodecTree currentCodecTree = codecRegistry.getCodecContext();
300         final InstanceIdentifier<?> bindingPath = codecRegistry.fromYangInstanceIdentifier(domIdentifier);
301         Preconditions.checkArgument(bindingPath != null);
302         /**
303          * If we are able to deserialize YANG instance identifier, getSubtreeCodec must
304          * return non-null value.
305          */
306         final BindingCodecTreeNode<?> codecContext = currentCodecTree.getSubtreeCodec(bindingPath);
307         return new SimpleEntry<InstanceIdentifier<?>, BindingCodecTreeNode<?>>(bindingPath, codecContext);
308     }
309
310     public Set<Class<? extends Notification>> getNotificationClasses(final Set<SchemaPath> interested) {
311         final Set<Class<? extends Notification>> result = new HashSet<>();
312         final Set<NotificationDefinition> knownNotifications = runtimeContext.getSchemaContext().getNotifications();
313         for (final NotificationDefinition notification : knownNotifications) {
314             if (interested.contains(notification.getPath())) {
315                 try {
316                     result.add((Class<? extends Notification>) runtimeContext.getClassForSchema(notification));
317                 } catch (final IllegalStateException e) {
318                     // Ignore
319                 }
320             }
321         }
322         return result;
323     }
324
325 }