Merge "Bug 981: Fixed accidental shaddowing of data in Data Change Event."
[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 java.lang.reflect.Method;
11 import java.lang.reflect.Type;
12 import java.util.AbstractMap.SimpleEntry;
13 import java.util.LinkedList;
14 import java.util.List;
15 import java.util.Map.Entry;
16 import java.util.concurrent.Callable;
17
18 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
19 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
20 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
21 import org.opendaylight.yangtools.concepts.util.ClassLoaderUtils;
22 import org.opendaylight.yangtools.yang.binding.Augmentation;
23 import org.opendaylight.yangtools.yang.binding.DataContainer;
24 import org.opendaylight.yangtools.yang.binding.DataObject;
25 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
26 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
27 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
36 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
39 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
40 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.google.common.base.Optional;
47 import com.google.common.collect.ImmutableList;
48 import com.google.common.collect.Iterables;
49
50 public class BindingToNormalizedNodeCodec implements SchemaContextListener {
51
52     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
53
54     private final BindingIndependentMappingService bindingToLegacy;
55     private DataNormalizer legacyToNormalized;
56
57     public BindingToNormalizedNodeCodec(final BindingIndependentMappingService mappingService) {
58         super();
59         this.bindingToLegacy = mappingService;
60     }
61
62     public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalized(
63             final InstanceIdentifier<? extends DataObject> binding) {
64
65         // Used instance-identifier codec do not support serialization of last
66         // path
67         // argument if it is Augmentation (behaviour expected by old datastore)
68         // in this case, we explicitly check if last argument is augmentation
69         // to process it separately
70         if (isAugmentationIdentifier(binding)) {
71             return toNormalizedAugmented(binding);
72         }
73         return toNormalizedImpl(binding);
74     }
75
76     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
77             final InstanceIdentifier<? extends DataObject> bindingPath, final DataObject bindingObject) {
78         return toNormalizedNode(toEntry(bindingPath, bindingObject));
79
80     }
81
82     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
83             final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> binding) {
84         Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode> legacyEntry = bindingToLegacy
85                 .toDataDom(binding);
86         Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalizedEntry = legacyToNormalized
87                 .toNormalized(legacyEntry);
88         LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}", binding,
89                 legacyEntry, normalizedEntry);
90         if (Augmentation.class.isAssignableFrom(binding.getKey().getTargetType())) {
91
92             for (DataContainerChild<? extends PathArgument, ?> child : ((DataContainerNode<?>) normalizedEntry
93                     .getValue()).getValue()) {
94                 if (child instanceof AugmentationNode) {
95                     ImmutableList<PathArgument> childArgs = ImmutableList.<PathArgument> builder()
96                             .addAll(normalizedEntry.getKey().getPath()).add(child.getIdentifier()).build();
97                     org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(
98                             childArgs);
99                     return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>(
100                             childPath, child);
101                 }
102             }
103
104         }
105         return normalizedEntry;
106
107     }
108
109     /**
110      *
111      * Returns a Binding-Aware instance identifier from normalized
112      * instance-identifier if it is possible to create representation.
113      *
114      * Returns Optional.absent for cases where target is mixin node except
115      * augmentation.
116      *
117      */
118     public Optional<InstanceIdentifier<? extends DataObject>> toBinding(
119             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
120             throws DeserializationException {
121
122         PathArgument lastArgument = Iterables.getLast(normalized.getPath());
123         // Used instance-identifier codec do not support serialization of last
124         // path
125         // argument if it is AugmentationIdentifier (behaviour expected by old
126         // datastore)
127         // in this case, we explicitly check if last argument is augmentation
128         // to process it separately
129         if (lastArgument instanceof AugmentationIdentifier) {
130             return toBindingAugmented(normalized);
131         }
132         return toBindingImpl(normalized);
133     }
134
135     private Optional<InstanceIdentifier<? extends DataObject>> toBindingAugmented(
136             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
137             throws DeserializationException {
138         Optional<InstanceIdentifier<? extends DataObject>> potential = toBindingImpl(normalized);
139         // Shorthand check, if codec already supports deserialization
140         // of AugmentationIdentifier we will return
141         if (potential.isPresent() && isAugmentationIdentifier(potential.get())) {
142             return potential;
143         }
144
145         AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPath());
146
147         // Here we employ small trick - Binding-aware Codec injects an pointer
148         // to augmentation class
149         // if child is referenced - so we will reference child and then shorten
150         // path.
151         for (QName child : lastArgument.getPossibleChildNames()) {
152             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(
153                     ImmutableList.<PathArgument> builder().addAll(normalized.getPath()).add(new NodeIdentifier(child))
154                             .build());
155             try {
156                 if (!isChoiceOrCasePath(childPath)) {
157                     InstanceIdentifier<? extends DataObject> potentialPath = shortenToLastAugment(toBindingImpl(
158                             childPath).get());
159                     return Optional.<InstanceIdentifier<? extends DataObject>> of(potentialPath);
160                 }
161             } catch (Exception e) {
162                 LOG.trace("Unable to deserialize aug. child path for {}", childPath, e);
163             }
164         }
165         return toBindingImpl(normalized);
166     }
167
168     private Optional<InstanceIdentifier<? extends DataObject>> toBindingImpl(
169             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
170             throws DeserializationException {
171         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath;
172
173         try {
174             if (isChoiceOrCasePath(normalized)) {
175                 return Optional.absent();
176             }
177             legacyPath = legacyToNormalized.toLegacy(normalized);
178         } catch (DataNormalizationException e) {
179             throw new IllegalStateException("Could not denormalize path.", e);
180         }
181         LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}",
182                 legacyPath, normalized);
183         return Optional.<InstanceIdentifier<? extends DataObject>> of(bindingToLegacy.fromDataDom(legacyPath));
184     }
185
186     private boolean isChoiceOrCasePath(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
187             throws DataNormalizationException {
188         DataNormalizationOperation<?> op = findNormalizationOperation(normalized);
189         return op.isMixin() && op.getIdentifier() instanceof NodeIdentifier;
190     }
191
192     private DataNormalizationOperation<?> findNormalizationOperation(
193             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
194             throws DataNormalizationException {
195         DataNormalizationOperation<?> current = legacyToNormalized.getRootOperation();
196         for (PathArgument arg : normalized.getPath()) {
197             current = current.getChild(arg);
198         }
199         return current;
200     }
201
202     private static final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> toEntry(
203             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> key,
204             final DataObject value) {
205         return new SimpleEntry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>(
206                 key, value);
207     }
208
209     public DataObject toBinding(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> normalizedNode)
210             throws DeserializationException {
211         CompositeNode legacy = null;
212         if (isAugmentationIdentifier(path) && normalizedNode instanceof AugmentationNode) {
213             QName augIdentifier = BindingReflections.findQName(path.getTargetType());
214             ContainerNode virtualNode = Builders.containerBuilder() //
215                     .withNodeIdentifier(new NodeIdentifier(augIdentifier)) //
216                     .withChild((DataContainerChild<?, ?>) normalizedNode) //
217                     .build();
218             legacy = (CompositeNode) DataNormalizer.toLegacy(virtualNode);
219         } else {
220             legacy = (CompositeNode) DataNormalizer.toLegacy(normalizedNode);
221         }
222
223         return bindingToLegacy.dataObjectFromDataDom(path, legacy);
224     }
225
226     public DataNormalizer getDataNormalizer() {
227         return legacyToNormalized;
228     }
229
230     public Optional<Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
231             final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
232             throws DeserializationException {
233         Optional<InstanceIdentifier<? extends DataObject>> potentialPath = toBinding(normalized.getKey());
234         if (potentialPath.isPresent()) {
235             InstanceIdentifier<? extends DataObject> bindingPath = potentialPath.get();
236             DataObject bindingData = toBinding(bindingPath, normalized.getValue());
237             if (bindingData == null) {
238                 LOG.warn("Failed to deserialize {} to Binding format. Binding path is: {}", normalized, bindingPath);
239             }
240             return Optional.of(toEntry(bindingPath, bindingData));
241         } else {
242             return Optional.absent();
243         }
244     }
245
246     @Override
247     public void onGlobalContextUpdated(final SchemaContext arg0) {
248         legacyToNormalized = new DataNormalizer(arg0);
249     }
250
251     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedAugmented(
252             final InstanceIdentifier<?> augPath) {
253         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed = toNormalizedImpl(augPath);
254         // If used instance identifier codec added supports for deserialization
255         // of last AugmentationIdentifier we will just reuse it
256         if (isAugmentationIdentifier(processed)) {
257             return processed;
258         }
259         // Here we employ small trick - DataNormalizer injecst augmentation
260         // identifier if child is
261         // also part of the path (since using a child we can safely identify
262         // augmentation)
263         // so, we scan augmentation for children add it to path
264         // and use original algorithm, then shorten it to last augmentation
265         for (@SuppressWarnings("rawtypes")
266         Class augChild : getAugmentationChildren(augPath.getTargetType())) {
267             @SuppressWarnings("unchecked")
268             InstanceIdentifier<?> childPath = augPath.child(augChild);
269             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = toNormalizedImpl(childPath);
270             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potentialDiscovered = shortenToLastAugmentation(normalized);
271             if (potentialDiscovered != null) {
272                 return potentialDiscovered;
273             }
274         }
275         return processed;
276     }
277
278     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier shortenToLastAugmentation(
279             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) {
280         int position = 0;
281         int foundPosition = -1;
282         for (PathArgument arg : normalized.getPath()) {
283             position++;
284             if (arg instanceof AugmentationIdentifier) {
285                 foundPosition = position;
286             }
287         }
288         if (foundPosition > 0) {
289             return new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(normalized.getPath().subList(0,
290                     foundPosition));
291         }
292         return null;
293     }
294
295     private InstanceIdentifier<? extends DataObject> shortenToLastAugment(
296             final InstanceIdentifier<? extends DataObject> binding) {
297         int position = 0;
298         int foundPosition = -1;
299         for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : binding.getPathArguments()) {
300             position++;
301             if (isAugmentation(arg.getType())) {
302                 foundPosition = position;
303             }
304         }
305         return InstanceIdentifier.create(Iterables.limit(binding.getPathArguments(), foundPosition));
306     }
307
308     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedImpl(
309             final InstanceIdentifier<? extends DataObject> binding) {
310         final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy
311                 .toDataDom(binding);
312         final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized
313                 .toNormalized(legacyPath);
314         return normalized;
315     }
316
317     @SuppressWarnings("unchecked")
318     private Iterable<Class<? extends DataObject>> getAugmentationChildren(final Class<?> targetType) {
319         List<Class<? extends DataObject>> ret = new LinkedList<>();
320         for (Method method : targetType.getMethods()) {
321             Class<?> entity = getYangModeledType(method);
322             if (entity != null) {
323                 ret.add((Class<? extends DataObject>) entity);
324             }
325         }
326         return ret;
327     }
328
329     @SuppressWarnings({ "rawtypes", "unchecked" })
330     private Class<? extends DataObject> getYangModeledType(final Method method) {
331         if (method.getName().equals("getClass") || !method.getName().startsWith("get")
332                 || method.getParameterTypes().length > 0) {
333             return null;
334         }
335
336         Class<?> returnType = method.getReturnType();
337         if (DataContainer.class.isAssignableFrom(returnType)) {
338             return (Class) returnType;
339         } else if (List.class.isAssignableFrom(returnType)) {
340             try {
341                 return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(),
342                         new Callable<Class>() {
343
344                             @SuppressWarnings("rawtypes")
345                             @Override
346                             public Class call() throws Exception {
347                                 Type listResult = ClassLoaderUtils.getFirstGenericParameter(method
348                                         .getGenericReturnType());
349                                 if (listResult instanceof Class
350                                         && DataObject.class.isAssignableFrom((Class) listResult)) {
351                                     return (Class<?>) listResult;
352                                 }
353                                 return null;
354                             }
355
356                         });
357             } catch (Exception e) {
358                 LOG.debug("Could not get YANG modeled entity for {}", method, e);
359                 return null;
360             }
361
362         }
363         return null;
364     }
365
366     @SuppressWarnings({ "unchecked", "rawtypes" })
367     private static InstanceIdentifier<?> toWildcarded(final InstanceIdentifier<?> orig) {
368         List<org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument> wildArgs = new LinkedList<>();
369         for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : orig.getPathArguments()) {
370             wildArgs.add(new Item(arg.getType()));
371         }
372         return InstanceIdentifier.create(wildArgs);
373     }
374
375     private static boolean isAugmentation(final Class<? extends DataObject> type) {
376         return Augmentation.class.isAssignableFrom(type);
377     }
378
379     private static boolean isAugmentationIdentifier(final InstanceIdentifier<?> potential) {
380         return Augmentation.class.isAssignableFrom(potential.getTargetType());
381     }
382
383     private boolean isAugmentationIdentifier(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed) {
384         return Iterables.getLast(processed.getPath()) instanceof AugmentationIdentifier;
385     }
386 }