8723fdf82a931b846482914fd5e85e69e8c10cf1
[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.Iterator;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Map.Entry;
17
18 import javax.annotation.Nullable;
19
20 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
21 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
22 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
23 import org.opendaylight.yangtools.yang.binding.Augmentation;
24 import org.opendaylight.yangtools.yang.binding.DataContainer;
25 import org.opendaylight.yangtools.yang.binding.DataObject;
26 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
27 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
28 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
29 import org.opendaylight.yangtools.yang.binding.util.ClassLoaderUtils;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
35 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
38 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
41 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
42 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.google.common.base.Function;
49 import com.google.common.base.Optional;
50 import com.google.common.base.Preconditions;
51 import com.google.common.base.Supplier;
52 import com.google.common.collect.ImmutableList;
53 import com.google.common.collect.Iterables;
54
55 public class BindingToNormalizedNodeCodec implements SchemaContextListener {
56
57     private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
58
59     private final BindingIndependentMappingService bindingToLegacy;
60     private DataNormalizer legacyToNormalized;
61
62     public BindingToNormalizedNodeCodec(final BindingIndependentMappingService mappingService) {
63         super();
64         this.bindingToLegacy = mappingService;
65     }
66
67     public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalized(
68             final InstanceIdentifier<? extends DataObject> binding) {
69
70         // Used instance-identifier codec do not support serialization of last
71         // path
72         // argument if it is Augmentation (behaviour expected by old datastore)
73         // in this case, we explicitly check if last argument is augmentation
74         // to process it separately
75         if (isAugmentationIdentifier(binding)) {
76             return toNormalizedAugmented(binding);
77         }
78         return toNormalizedImpl(binding);
79     }
80
81     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
82             final InstanceIdentifier<? extends DataObject> bindingPath, final DataObject bindingObject) {
83         return toNormalizedNode(toBindingEntry(bindingPath, bindingObject));
84
85     }
86
87     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
88             final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> binding) {
89         Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode> legacyEntry = bindingToLegacy
90                 .toDataDom(binding);
91         Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalizedEntry = legacyToNormalized
92                 .toNormalized(legacyEntry);
93         LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}", binding,
94                 legacyEntry, normalizedEntry);
95         if (isAugmentation(binding.getKey().getTargetType())) {
96
97             for (DataContainerChild<? extends PathArgument, ?> child : ((DataContainerNode<?>) normalizedEntry
98                     .getValue()).getValue()) {
99                 if (child instanceof AugmentationNode) {
100                     ImmutableList<PathArgument> childArgs = ImmutableList.<PathArgument> builder()
101                             .addAll(normalizedEntry.getKey().getPathArguments()).add(child.getIdentifier()).build();
102                     org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(
103                             childArgs);
104                     return toDOMEntry(childPath, child);
105                 }
106             }
107
108         }
109         return normalizedEntry;
110
111     }
112
113     /**
114      *
115      * Returns a Binding-Aware instance identifier from normalized
116      * instance-identifier if it is possible to create representation.
117      *
118      * Returns Optional.absent for cases where target is mixin node except
119      * augmentation.
120      *
121      */
122     public Optional<InstanceIdentifier<? extends DataObject>> toBinding(
123             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
124                     throws DeserializationException {
125
126         PathArgument lastArgument = Iterables.getLast(normalized.getPathArguments());
127         // Used instance-identifier codec do not support serialization of last
128         // path
129         // argument if it is AugmentationIdentifier (behaviour expected by old
130         // datastore)
131         // in this case, we explicitly check if last argument is augmentation
132         // to process it separately
133         if (lastArgument instanceof AugmentationIdentifier) {
134             return toBindingAugmented(normalized);
135         }
136         return toBindingImpl(normalized);
137     }
138
139     private Optional<InstanceIdentifier<? extends DataObject>> toBindingAugmented(
140             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
141                     throws DeserializationException {
142         Optional<InstanceIdentifier<? extends DataObject>> potential = toBindingImpl(normalized);
143         // Shorthand check, if codec already supports deserialization
144         // of AugmentationIdentifier we will return
145         if (potential.isPresent() && isAugmentationIdentifier(potential.get())) {
146             return potential;
147         }
148
149         int normalizedCount = getAugmentationCount(normalized);
150         AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPathArguments());
151
152         // Here we employ small trick - Binding-aware Codec injects an pointer
153         // to augmentation class
154         // if child is referenced - so we will reference child and then shorten
155         // path.
156         LOG.trace("Looking for candidates to match {}", normalized);
157         for (QName child : lastArgument.getPossibleChildNames()) {
158             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = normalized.node(child);
159             try {
160                 if (isNotRepresentable(childPath)) {
161                     LOG.trace("Path {} is not BI-representable, skipping it", childPath);
162                     continue;
163                 }
164             } catch (DataNormalizationException e) {
165                 LOG.warn("Failed to denormalize path {}, skipping it", childPath, e);
166                 continue;
167             }
168
169             Optional<InstanceIdentifier<? extends DataObject>> baId = toBindingImpl(childPath);
170             if (!baId.isPresent()) {
171                 LOG.debug("No binding-aware identifier found for path {}, skipping it", childPath);
172                 continue;
173             }
174
175             InstanceIdentifier<? extends DataObject> potentialPath = shortenToLastAugment(baId.get());
176             int potentialAugmentCount = getAugmentationCount(potentialPath);
177             if (potentialAugmentCount == normalizedCount) {
178                 LOG.trace("Found matching path {}", potentialPath);
179                 return Optional.<InstanceIdentifier<? extends DataObject>> of(potentialPath);
180             }
181
182             LOG.trace("Skipping mis-matched potential path {}", potentialPath);
183         }
184
185         LOG.trace("Failed to find augmentation matching {}", normalized);
186         return Optional.absent();
187     }
188
189     private Optional<InstanceIdentifier<? extends DataObject>> toBindingImpl(
190             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
191                     throws DeserializationException {
192         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath;
193
194         try {
195             if (isNotRepresentable(normalized)) {
196                 return Optional.absent();
197             }
198             legacyPath = legacyToNormalized.toLegacy(normalized);
199         } catch (DataNormalizationException e) {
200             throw new IllegalStateException("Could not denormalize path.", e);
201         }
202         LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}",
203                 legacyPath, normalized);
204         return Optional.<InstanceIdentifier<? extends DataObject>> of(bindingToLegacy.fromDataDom(legacyPath));
205     }
206
207     private boolean isNotRepresentable(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
208             throws DataNormalizationException {
209         DataNormalizationOperation<?> op = findNormalizationOperation(normalized);
210         if( op.isMixin() && op.getIdentifier() instanceof NodeIdentifier) {
211             return true;
212         }
213         if(op.isLeaf()) {
214             return true;
215         }
216         return false;
217     }
218
219     private DataNormalizationOperation<?> findNormalizationOperation(
220             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
221                     throws DataNormalizationException {
222         DataNormalizationOperation<?> current = legacyToNormalized.getRootOperation();
223         for (PathArgument arg : normalized.getPathArguments()) {
224             current = current.getChild(arg);
225         }
226         return current;
227     }
228
229     private static final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> toBindingEntry(
230             final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> key,
231             final DataObject value) {
232         return new SimpleEntry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>(
233                 key, value);
234     }
235
236     private static final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toDOMEntry(
237             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier key,
238             final NormalizedNode<?, ?> value) {
239         return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>(
240                 key, value);
241     }
242
243     public DataObject toBinding(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> normalizedNode)
244             throws DeserializationException {
245         CompositeNode legacy = null;
246         if (isAugmentationIdentifier(path) && normalizedNode instanceof AugmentationNode) {
247             QName augIdentifier = BindingReflections.findQName(path.getTargetType());
248             ContainerNode virtualNode = Builders.containerBuilder() //
249                     .withNodeIdentifier(new NodeIdentifier(augIdentifier)) //
250                     .withChild((DataContainerChild<?, ?>) normalizedNode) //
251                     .build();
252             legacy = (CompositeNode) DataNormalizer.toLegacy(virtualNode);
253         } else {
254             legacy = (CompositeNode) DataNormalizer.toLegacy(normalizedNode);
255         }
256
257         return bindingToLegacy.dataObjectFromDataDom(path, legacy);
258     }
259
260     public DataNormalizer getDataNormalizer() {
261         return legacyToNormalized;
262     }
263
264     public Optional<Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
265             final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
266                     throws DeserializationException {
267         Optional<InstanceIdentifier<? extends DataObject>> potentialPath = toBinding(normalized.getKey());
268         if (potentialPath.isPresent()) {
269             InstanceIdentifier<? extends DataObject> bindingPath = potentialPath.get();
270             DataObject bindingData = toBinding(bindingPath, normalized.getValue());
271             if (bindingData == null) {
272                 LOG.warn("Failed to deserialize {} to Binding format. Binding path is: {}", normalized, bindingPath);
273             }
274             return Optional.of(toBindingEntry(bindingPath, bindingData));
275         } else {
276             return Optional.absent();
277         }
278     }
279
280     @Override
281     public void onGlobalContextUpdated(final SchemaContext arg0) {
282         legacyToNormalized = new DataNormalizer(arg0);
283     }
284
285     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedAugmented(
286             final InstanceIdentifier<?> augPath) {
287         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed = toNormalizedImpl(augPath);
288         // If used instance identifier codec added supports for deserialization
289         // of last AugmentationIdentifier we will just reuse it
290         if (isAugmentationIdentifier(processed)) {
291             return processed;
292         }
293         // Here we employ small trick - DataNormalizer injects augmentation
294         // identifier if child is
295         // also part of the path (since using a child we can safely identify
296         // augmentation)
297         // so, we scan augmentation for children add it to path
298         // and use original algorithm, then shorten it to last augmentation
299         for (@SuppressWarnings("rawtypes")
300         Class augChild : getAugmentationChildren(augPath.getTargetType())) {
301             @SuppressWarnings("unchecked")
302             InstanceIdentifier<?> childPath = augPath.child(augChild);
303             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = toNormalizedImpl(childPath);
304             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potentialDiscovered = shortenToLastAugmentation(normalized);
305             if (potentialDiscovered != null) {
306                 return potentialDiscovered;
307             }
308         }
309         return processed;
310     }
311
312     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier shortenToLastAugmentation(
313             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) {
314         int position = 0;
315         int foundPosition = -1;
316         for (PathArgument arg : normalized.getPathArguments()) {
317             position++;
318             if (arg instanceof AugmentationIdentifier) {
319                 foundPosition = position;
320             }
321         }
322         if (foundPosition > 0) {
323             Iterable<PathArgument> shortened = Iterables.limit(normalized.getPathArguments(), foundPosition);
324             return org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(shortened);
325         }
326         return null;
327     }
328
329     private InstanceIdentifier<? extends DataObject> shortenToLastAugment(
330             final InstanceIdentifier<? extends DataObject> binding) {
331         int position = 0;
332         int foundPosition = -1;
333         for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : binding.getPathArguments()) {
334             position++;
335             if (isAugmentation(arg.getType())) {
336                 foundPosition = position;
337             }
338         }
339         return InstanceIdentifier.create(Iterables.limit(binding.getPathArguments(), foundPosition));
340     }
341
342     private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedImpl(
343             final InstanceIdentifier<? extends DataObject> binding) {
344         final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy
345                 .toDataDom(binding);
346         final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized
347                 .toNormalized(legacyPath);
348         return normalized;
349     }
350
351     @SuppressWarnings("unchecked")
352     private Iterable<Class<? extends DataObject>> getAugmentationChildren(final Class<?> targetType) {
353         List<Class<? extends DataObject>> ret = new LinkedList<>();
354         for (Method method : targetType.getMethods()) {
355             Class<?> entity = getYangModeledType(method);
356             if (entity != null) {
357                 ret.add((Class<? extends DataObject>) entity);
358             }
359         }
360         return ret;
361     }
362
363     @SuppressWarnings({ "rawtypes", "unchecked" })
364     private Class<? extends DataObject> getYangModeledType(final Method method) {
365         if (method.getName().equals("getClass") || !method.getName().startsWith("get")
366                 || method.getParameterTypes().length > 0) {
367             return null;
368         }
369
370         Class<?> returnType = method.getReturnType();
371         if (DataContainer.class.isAssignableFrom(returnType)) {
372             return (Class) returnType;
373         } else if (List.class.isAssignableFrom(returnType)) {
374             try {
375                 return ClassLoaderUtils.withClassLoader(method.getDeclaringClass().getClassLoader(),
376                         new Supplier<Class>() {
377                     @Override
378                     public Class get() {
379                         Type listResult = ClassLoaderUtils.getFirstGenericParameter(method
380                                 .getGenericReturnType());
381                         if (listResult instanceof Class
382                                 && DataObject.class.isAssignableFrom((Class) listResult)) {
383                             return (Class<?>) listResult;
384                         }
385                         return null;
386                     }
387
388                 });
389             } catch (Exception e) {
390                 LOG.debug("Could not get YANG modeled entity for {}", method, e);
391                 return null;
392             }
393
394         }
395         return null;
396     }
397
398     @SuppressWarnings({ "unchecked", "rawtypes" })
399     private static InstanceIdentifier<?> toWildcarded(final InstanceIdentifier<?> orig) {
400         List<org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument> wildArgs = new LinkedList<>();
401         for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : orig.getPathArguments()) {
402             wildArgs.add(new Item(arg.getType()));
403         }
404         return InstanceIdentifier.create(wildArgs);
405     }
406
407     private static boolean isAugmentation(final Class<? extends DataObject> type) {
408         return Augmentation.class.isAssignableFrom(type);
409     }
410
411     private static boolean isAugmentationIdentifier(final InstanceIdentifier<?> potential) {
412         return Augmentation.class.isAssignableFrom(potential.getTargetType());
413     }
414
415     private boolean isAugmentationIdentifier(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed) {
416         return Iterables.getLast(processed.getPathArguments()) instanceof AugmentationIdentifier;
417     }
418
419     private static int getAugmentationCount(final InstanceIdentifier<?> potential) {
420         int count = 0;
421         for(org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : potential.getPathArguments()) {
422             if(isAugmentation(arg.getType())) {
423                 count++;
424             }
425
426         }
427         return count;
428     }
429
430     private static int getAugmentationCount(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potential) {
431         int count = 0;
432         for(PathArgument arg : potential.getPathArguments()) {
433             if(arg instanceof AugmentationIdentifier) {
434                 count++;
435             }
436         }
437         return count;
438     }
439
440     public Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>>  deserializeFunction(final InstanceIdentifier<?> path) {
441         return new DeserializeFunction(this, path);
442     }
443
444     private static class DeserializeFunction implements Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>> {
445
446         private final BindingToNormalizedNodeCodec codec;
447         private final InstanceIdentifier<?> path;
448
449         public DeserializeFunction(final BindingToNormalizedNodeCodec codec, final InstanceIdentifier<?> path) {
450             super();
451             this.codec = Preconditions.checkNotNull(codec, "Codec must not be null");
452             this.path = Preconditions.checkNotNull(path, "Path must not be null");
453         }
454
455         @Nullable
456         @Override
457         public Optional<DataObject> apply(@Nullable final Optional<NormalizedNode<?, ?>> normalizedNode) {
458             if (normalizedNode.isPresent()) {
459                 final DataObject dataObject;
460                 try {
461                     dataObject = codec.toBinding(path, normalizedNode.get());
462                 } catch (DeserializationException e) {
463                     LOG.warn("Failed to create dataobject from node {}", normalizedNode.get(), e);
464                     throw new IllegalStateException("Failed to create dataobject", e);
465                 }
466
467                 if (dataObject != null) {
468                     return Optional.of(dataObject);
469                 }
470             }
471             return Optional.absent();
472         }
473     }
474
475     /**
476      * Returns an default object according to YANG schema for supplied path.
477      *
478      * @param path DOM Path
479      * @return Node with defaults set on.
480      */
481     public NormalizedNode<?, ?> getDefaultNodeFor(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier path) {
482         Iterator<PathArgument> iterator = path.getPathArguments().iterator();
483         DataNormalizationOperation<?> currentOp = legacyToNormalized.getRootOperation();
484         while (iterator.hasNext()) {
485             PathArgument currentArg = iterator.next();
486             try {
487                 currentOp = currentOp.getChild(currentArg);
488             } catch (DataNormalizationException e) {
489                 throw new IllegalArgumentException(String.format("Invalid child encountered in path %s", path), e);
490             }
491         }
492         return currentOp.createDefault(path.getLastPathArgument());
493     }
494 }