Merge "Bug 2404: RPC and Notification support for Binding Data Codec"
[yangtools.git] / code-generator / binding-data-codec / src / main / java / org / opendaylight / yangtools / binding / data / codec / impl / BindingCodecContext.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.yangtools.binding.data.codec.impl;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.collect.ImmutableMap;
12 import com.google.common.collect.Iterables;
13 import java.lang.reflect.Constructor;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.ParameterizedType;
17 import java.lang.reflect.Type;
18 import java.util.AbstractMap.SimpleEntry;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.LinkedHashMap;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.concurrent.Callable;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
30 import org.opendaylight.yangtools.binding.data.codec.impl.NodeCodecContext.CodecContextFactory;
31 import org.opendaylight.yangtools.concepts.Codec;
32 import org.opendaylight.yangtools.concepts.Immutable;
33 import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
34 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType;
35 import org.opendaylight.yangtools.util.ClassLoaderUtils;
36 import org.opendaylight.yangtools.yang.binding.BaseIdentity;
37 import org.opendaylight.yangtools.yang.binding.BindingMapping;
38 import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
39 import org.opendaylight.yangtools.yang.binding.DataContainer;
40 import org.opendaylight.yangtools.yang.binding.Identifiable;
41 import org.opendaylight.yangtools.yang.binding.Identifier;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.IdentifiableItem;
44 import org.opendaylight.yangtools.yang.binding.Notification;
45 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
49 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
50 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
51 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
55 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
56 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
57 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
58 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
59 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 final class BindingCodecContext implements CodecContextFactory, Immutable {
67     private static final Logger LOG = LoggerFactory.getLogger(BindingCodecContext.class);
68     private static final String GETTER_PREFIX = "get";
69
70     private final Codec<YangInstanceIdentifier, InstanceIdentifier<?>> instanceIdentifierCodec =
71             new InstanceIdentifierCodec();
72     private final Codec<QName, Class<?>> identityCodec;
73     private final BindingRuntimeContext context;
74     private final SchemaRootCodecContext root;
75
76     public BindingCodecContext(final BindingRuntimeContext context) {
77         this.context = Preconditions.checkNotNull(context, "Binding Runtime Context is required.");
78         this.root = SchemaRootCodecContext.create(this);
79         this.identityCodec = new IdentityCodec(context);
80     }
81
82     @Override
83     public BindingRuntimeContext getRuntimeContext() {
84         return context;
85     }
86
87     Codec<YangInstanceIdentifier, InstanceIdentifier<?>> getInstanceIdentifierCodec() {
88         return instanceIdentifierCodec;
89     }
90
91     public Codec<QName, Class<?>> getIdentityCodec() {
92         return identityCodec;
93     }
94
95     public Entry<YangInstanceIdentifier, BindingStreamEventWriter> newWriter(final InstanceIdentifier<?> path,
96             final NormalizedNodeStreamWriter domWriter) {
97         final LinkedList<YangInstanceIdentifier.PathArgument> yangArgs = new LinkedList<>();
98         final DataContainerCodecContext<?> codecContext = getCodecContextNode(path, yangArgs);
99         final BindingStreamEventWriter writer = new BindingToNormalizedStreamWriter(codecContext, domWriter);
100         return new SimpleEntry<>(YangInstanceIdentifier.create(yangArgs), writer);
101     }
102
103     public BindingStreamEventWriter newWriterWithoutIdentifier(final InstanceIdentifier<?> path,
104             final NormalizedNodeStreamWriter domWriter) {
105         return new BindingToNormalizedStreamWriter(getCodecContextNode(path, null), domWriter);
106     }
107
108     BindingStreamEventWriter newRpcWriter(final Class<? extends DataContainer> rpcInputOrOutput,
109             final NormalizedNodeStreamWriter domWriter) {
110         final NodeCodecContext schema = root.getRpc(rpcInputOrOutput);
111         return new BindingToNormalizedStreamWriter(schema, domWriter);
112     }
113
114     BindingStreamEventWriter newNotificationWriter(final Class<? extends Notification> notification,
115             final NormalizedNodeStreamWriter domWriter) {
116         final NodeCodecContext schema = root.getNotification(notification);
117         return new BindingToNormalizedStreamWriter(schema, domWriter);
118     }
119
120     public DataContainerCodecContext<?> getCodecContextNode(final InstanceIdentifier<?> binding,
121             final List<YangInstanceIdentifier.PathArgument> builder) {
122         DataContainerCodecContext<?> currentNode = root;
123         for (final InstanceIdentifier.PathArgument bindingArg : binding.getPathArguments()) {
124             currentNode = currentNode.getIdentifierChild(bindingArg, builder);
125         }
126         return currentNode;
127     }
128
129     /**
130      * Multi-purpose utility function. Traverse the codec tree, looking for
131      * the appropriate codec for the specified {@link YangInstanceIdentifier}.
132      * As a side-effect, gather all traversed binding {@link InstanceIdentifier.PathArgument}s
133      * into the supplied collection.
134      *
135      * @param dom {@link YangInstanceIdentifier} which is to be translated
136      * @param bindingArguments Collection for traversed path arguments
137      * @return Codec for target node, or @null if the node does not have a
138      *         binding representation (choice, case, leaf).
139      */
140     @Nullable NodeCodecContext getCodecContextNode(final @Nonnull YangInstanceIdentifier dom,
141             final @Nonnull Collection<InstanceIdentifier.PathArgument> bindingArguments) {
142         NodeCodecContext currentNode = root;
143         ListNodeCodecContext currentList = null;
144
145         for (final YangInstanceIdentifier.PathArgument domArg : dom.getPathArguments()) {
146             Preconditions.checkArgument(currentNode instanceof DataContainerCodecContext<?>, "Unexpected child of non-container node %s", currentNode);
147             final DataContainerCodecContext<?> previous = (DataContainerCodecContext<?>) currentNode;
148             final NodeCodecContext nextNode = previous.getYangIdentifierChild(domArg);
149
150             /*
151              * List representation in YANG Instance Identifier consists of two
152              * arguments: first is list as a whole, second is list as an item so
153              * if it is /list it means list as whole, if it is /list/list - it
154              * is wildcarded and if it is /list/list[key] it is concrete item,
155              * all this variations are expressed in Binding Aware Instance
156              * Identifier as Item or IdentifiableItem
157              */
158             if (currentList != null) {
159                 Preconditions.checkArgument(currentList == nextNode, "List should be referenced two times in YANG Instance Identifier %s", dom);
160
161                 // We entered list, so now we have all information to emit
162                 // list path using second list argument.
163                 bindingArguments.add(currentList.getBindingPathArgument(domArg));
164                 currentList = null;
165                 currentNode = nextNode;
166             } else if (nextNode instanceof ListNodeCodecContext) {
167                 // We enter list, we do not update current Node yet,
168                 // since we need to verify
169                 currentList = (ListNodeCodecContext) nextNode;
170             } else if (nextNode instanceof ChoiceNodeCodecContext) {
171                 // We do not add path argument for choice, since
172                 // it is not supported by binding instance identifier.
173                 currentNode = nextNode;
174             } else if (nextNode instanceof DataContainerCodecContext<?>) {
175                 bindingArguments.add(((DataContainerCodecContext<?>) nextNode).getBindingPathArgument(domArg));
176                 currentNode = nextNode;
177             } else if (nextNode instanceof LeafNodeCodecContext) {
178                 LOG.debug("Instance identifier referencing a leaf is not representable (%s)", dom);
179                 return null;
180             }
181         }
182
183         // Algorithm ended in list as whole representation
184         // we sill need to emit identifier for list
185         if (currentNode instanceof ChoiceNodeCodecContext) {
186             LOG.debug("Instance identifier targeting a choice is not representable (%s)", dom);
187             return null;
188         }
189         if (currentNode instanceof CaseNodeCodecContext) {
190             LOG.debug("Instance identifier targeting a case is not representable (%s)", dom);
191             return null;
192         }
193
194         if (currentList != null) {
195             bindingArguments.add(currentList.getBindingPathArgument(null));
196             return currentList;
197         }
198         return currentNode;
199     }
200
201     NotificationCodecContext getNotificationContext(final SchemaPath notification) {
202         return root.getNotification(notification);
203     }
204
205     ContainerNodeCodecContext getRpcDataContext(final SchemaPath path) {
206         return root.getRpc(path);
207     }
208
209     @Override
210     public ImmutableMap<String, LeafNodeCodecContext> getLeafNodes(final Class<?> parentClass,
211             final DataNodeContainer childSchema) {
212         final HashMap<String, DataSchemaNode> getterToLeafSchema = new HashMap<>();
213         for (final DataSchemaNode leaf : childSchema.getChildNodes()) {
214             final TypeDefinition<?> typeDef;
215             if (leaf instanceof LeafSchemaNode) {
216                 typeDef = ((LeafSchemaNode) leaf).getType();
217             } else if (leaf instanceof LeafListSchemaNode) {
218                 typeDef = ((LeafListSchemaNode) leaf).getType();
219             } else {
220                 continue;
221             }
222
223             final String getterName = getGetterName(leaf.getQName(), typeDef);
224             getterToLeafSchema.put(getterName, leaf);
225         }
226         return getLeafNodesUsingReflection(parentClass, getterToLeafSchema);
227     }
228
229     private String getGetterName(final QName qName, TypeDefinition<?> typeDef) {
230         final String suffix = BindingMapping.getClassName(qName);
231
232         while (typeDef.getBaseType() != null) {
233             typeDef = typeDef.getBaseType();
234         }
235         if (typeDef instanceof BooleanTypeDefinition || typeDef instanceof EmptyTypeDefinition) {
236             return "is" + suffix;
237         }
238         return GETTER_PREFIX + suffix;
239     }
240
241     private ImmutableMap<String, LeafNodeCodecContext> getLeafNodesUsingReflection(final Class<?> parentClass,
242             final Map<String, DataSchemaNode> getterToLeafSchema) {
243         final Map<String, LeafNodeCodecContext> leaves = new HashMap<>();
244         for (final Method method : parentClass.getMethods()) {
245             if (method.getParameterTypes().length == 0) {
246                 final DataSchemaNode schema = getterToLeafSchema.get(method.getName());
247                 final Class<?> valueType;
248                 if (schema instanceof LeafSchemaNode) {
249                     valueType = method.getReturnType();
250                 } else if (schema instanceof LeafListSchemaNode) {
251                     final Type genericType = ClassLoaderUtils.getFirstGenericParameter(method.getGenericReturnType());
252
253                     if (genericType instanceof Class<?>) {
254                         valueType = (Class<?>) genericType;
255                     } else if (genericType instanceof ParameterizedType) {
256                         valueType = (Class<?>) ((ParameterizedType) genericType).getRawType();
257                     } else {
258                         throw new IllegalStateException("Unexpected return type " + genericType);
259                     }
260                 } else {
261                     continue; // We do not have schema for leaf, so we will ignore it (eg. getClass, getImplementedInterface).
262                 }
263                 final Codec<Object, Object> codec = getCodec(valueType, schema);
264                 final LeafNodeCodecContext leafNode = new LeafNodeCodecContext(schema, codec, method);
265                 leaves.put(schema.getQName().getLocalName(), leafNode);
266             }
267         }
268         return ImmutableMap.copyOf(leaves);
269     }
270
271
272
273     private Codec<Object, Object> getCodec(final Class<?> valueType, final DataSchemaNode schema) {
274         final TypeDefinition<?> instantiatedType;
275         if (schema instanceof LeafSchemaNode) {
276             instantiatedType = ((LeafSchemaNode) schema).getType();
277         } else if (schema instanceof LeafListSchemaNode) {
278             instantiatedType = ((LeafListSchemaNode) schema).getType();
279         } else {
280             throw new IllegalArgumentException("Unsupported leaf node type " + schema.getClass());
281         }
282         if (Class.class.equals(valueType)) {
283             @SuppressWarnings({ "unchecked", "rawtypes" })
284             final Codec<Object, Object> casted = (Codec) identityCodec;
285             return casted;
286         } else if (InstanceIdentifier.class.equals(valueType)) {
287             @SuppressWarnings({ "unchecked", "rawtypes" })
288             final Codec<Object, Object> casted = (Codec) instanceIdentifierCodec;
289             return casted;
290         } else if (Boolean.class.equals(valueType)) {
291             if(instantiatedType instanceof EmptyTypeDefinition) {
292                 return ValueTypeCodec.EMPTY_CODEC;
293             }
294         } else if (BindingReflections.isBindingClass(valueType)) {
295                             return getCodec(valueType, instantiatedType);
296         }
297         return ValueTypeCodec.NOOP_CODEC;
298     }
299
300     private Codec<Object, Object> getCodec(final Class<?> valueType, final TypeDefinition<?> instantiatedType) {
301         @SuppressWarnings("rawtypes")
302         TypeDefinition rootType = instantiatedType;
303         while (rootType.getBaseType() != null) {
304             rootType = rootType.getBaseType();
305         }
306         if (rootType instanceof IdentityrefTypeDefinition) {
307             return ValueTypeCodec.encapsulatedValueCodecFor(valueType, identityCodec);
308         } else if (rootType instanceof InstanceIdentifierTypeDefinition) {
309             return ValueTypeCodec.encapsulatedValueCodecFor(valueType, instanceIdentifierCodec);
310         } else if (rootType instanceof UnionTypeDefinition) {
311             final Callable<UnionTypeCodec> loader = UnionTypeCodec.loader(valueType, (UnionTypeDefinition) rootType);
312             try {
313                 return loader.call();
314             } catch (final Exception e) {
315                 throw new IllegalStateException("Unable to load codec for " + valueType, e);
316             }
317         } else if(rootType instanceof LeafrefTypeDefinition) {
318             final Entry<GeneratedType, Object> typeWithSchema = context.getTypeWithSchema(valueType);
319             final Object schema = typeWithSchema.getValue();
320             Preconditions.checkState(schema instanceof TypeDefinition<?>);
321             return getCodec(valueType, (TypeDefinition<?>) schema);
322         }
323         return ValueTypeCodec.getCodecFor(valueType, instantiatedType);
324     }
325
326     private class InstanceIdentifierCodec implements Codec<YangInstanceIdentifier, InstanceIdentifier<?>> {
327
328         @Override
329         public YangInstanceIdentifier serialize(final InstanceIdentifier<?> input) {
330             final List<YangInstanceIdentifier.PathArgument> domArgs = new ArrayList<>();
331             getCodecContextNode(input, domArgs);
332             return YangInstanceIdentifier.create(domArgs);
333         }
334
335         @Override
336         public InstanceIdentifier<?> deserialize(final YangInstanceIdentifier input) {
337             final List<InstanceIdentifier.PathArgument> builder = new ArrayList<>();
338             final NodeCodecContext codec = getCodecContextNode(input, builder);
339             if (codec == null) {
340                 return null;
341             }
342             if (codec instanceof ListNodeCodecContext && Iterables.getLast(builder) instanceof InstanceIdentifier.Item) {
343                 // We ended up in list, but without key, which means it represent list as a whole,
344                 // which is not binding representable.
345                 return null;
346             }
347             return InstanceIdentifier.create(builder);
348         }
349     }
350
351     private static class IdentityCodec implements Codec<QName, Class<?>> {
352         private final BindingRuntimeContext context;
353
354         IdentityCodec(final BindingRuntimeContext context) {
355             this.context = Preconditions.checkNotNull(context);
356         }
357
358         @Override
359         public Class<?> deserialize(final QName input) {
360             Preconditions.checkArgument(input != null, "Input must not be null.");
361             return context.getIdentityClass(input);
362         }
363
364         @Override
365         public QName serialize(final Class<?> input) {
366             Preconditions.checkArgument(BaseIdentity.class.isAssignableFrom(input));
367             return BindingReflections.findQName(input);
368         }
369     }
370
371     private static class ValueContext {
372
373         Method getter;
374         Codec<Object, Object> codec;
375
376         public ValueContext(final Class<?> identifier, final LeafNodeCodecContext leaf) {
377             final String getterName = GETTER_PREFIX
378                     + BindingMapping.getClassName(leaf.getDomPathArgument().getNodeType());
379             try {
380                 getter = identifier.getMethod(getterName);
381             } catch (NoSuchMethodException | SecurityException e) {
382                 throw new IllegalStateException(e);
383             }
384             codec = leaf.getValueCodec();
385         }
386
387         public Object getAndSerialize(final Object obj) {
388             try {
389                 final Object value = getter.invoke(obj);
390                 Preconditions.checkArgument(value != null,
391                         "All keys must be specified for %s. Missing key is %s. Supplied key is %s",
392                         getter.getDeclaringClass(), getter.getName(), obj);
393                 return codec.serialize(value);
394             } catch (IllegalAccessException | InvocationTargetException e) {
395                 throw new IllegalArgumentException(e);
396             }
397         }
398
399         public Object deserialize(final Object obj) {
400             return codec.deserialize(obj);
401         }
402
403     }
404
405     private static class IdentifiableItemCodec implements Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> {
406
407         private final Map<QName, ValueContext> keyValueContexts;
408         private final ListSchemaNode schema;
409         private final Constructor<? extends Identifier<?>> constructor;
410         private final Class<?> identifiable;
411
412         public IdentifiableItemCodec(final ListSchemaNode schema, final Class<? extends Identifier<?>> keyClass,
413                 final Class<?> identifiable, final Map<QName, ValueContext> keyValueContexts) {
414             this.schema = schema;
415             this.identifiable = identifiable;
416             this.constructor = getConstructor(keyClass);
417
418             /*
419              * We need to re-index to make sure we instantiate nodes in the order in which
420              * they are defined.
421              */
422             final Map<QName, ValueContext> keys = new LinkedHashMap<>();
423             for (final QName qname : schema.getKeyDefinition()) {
424                 keys.put(qname, keyValueContexts.get(qname));
425             }
426             this.keyValueContexts = ImmutableMap.copyOf(keys);
427         }
428
429         @Override
430         public IdentifiableItem<?, ?> deserialize(final NodeIdentifierWithPredicates input) {
431             final Collection<QName> keys = schema.getKeyDefinition();
432             final ArrayList<Object> bindingValues = new ArrayList<>(keys.size());
433             for (final QName key : keys) {
434                 final Object yangValue = input.getKeyValues().get(key);
435                 bindingValues.add(keyValueContexts.get(key).deserialize(yangValue));
436             }
437
438             final Identifier<?> identifier;
439             try {
440                 identifier = constructor.newInstance(bindingValues.toArray());
441             } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
442                 throw new IllegalStateException(String.format("Failed to instantiate key class %s", constructor.getDeclaringClass()), e);
443             }
444
445             @SuppressWarnings({ "rawtypes", "unchecked" })
446             final IdentifiableItem identifiableItem = new IdentifiableItem(identifiable, identifier);
447             return identifiableItem;
448         }
449
450         @Override
451         public NodeIdentifierWithPredicates serialize(final IdentifiableItem<?, ?> input) {
452             final Object value = input.getKey();
453
454             final Map<QName, Object> values = new LinkedHashMap<>();
455             for (final Entry<QName, ValueContext> valueCtx : keyValueContexts.entrySet()) {
456                 values.put(valueCtx.getKey(), valueCtx.getValue().getAndSerialize(value));
457             }
458             return new NodeIdentifierWithPredicates(schema.getQName(), values);
459         }
460     }
461
462     @SuppressWarnings("unchecked")
463     private static Constructor<? extends Identifier<?>> getConstructor(final Class<? extends Identifier<?>> clazz) {
464         for (@SuppressWarnings("rawtypes") final Constructor constr : clazz.getConstructors()) {
465             final Class<?>[] parameters = constr.getParameterTypes();
466             if (!clazz.equals(parameters[0])) {
467                 // It is not copy constructor;
468                 return constr;
469             }
470         }
471         throw new IllegalArgumentException("Supplied class " + clazz + "does not have required constructor.");
472     }
473
474     @Override
475     public Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> getPathArgumentCodec(final Class<?> listClz,
476             final ListSchemaNode schema) {
477         final Class<? extends Identifier<?>> identifier = ClassLoaderUtils.findFirstGenericArgument(listClz,
478                 Identifiable.class);
479         final Map<QName, ValueContext> valueCtx = new HashMap<>();
480         for (final LeafNodeCodecContext leaf : getLeafNodes(identifier, schema).values()) {
481             final QName name = leaf.getDomPathArgument().getNodeType();
482             valueCtx.put(name, new ValueContext(identifier, leaf));
483         }
484         return new IdentifiableItemCodec(schema, identifier, listClz, valueCtx);
485     }
486
487
488
489
490
491 }