2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.md.sal.binding.impl;
10 import java.lang.reflect.Method;
11 import java.util.AbstractMap.SimpleEntry;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.Map.Entry;
17 import javax.annotation.Nullable;
19 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
20 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
21 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
22 import org.opendaylight.yangtools.yang.binding.Augmentation;
23 import org.opendaylight.yangtools.yang.binding.BindingMapping;
24 import org.opendaylight.yangtools.yang.binding.DataObject;
25 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
26 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
27 import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.common.QNameModule;
30 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
33 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
34 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
40 import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
41 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
42 import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
43 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
46 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
50 import com.google.common.base.Function;
51 import com.google.common.base.Optional;
52 import com.google.common.base.Preconditions;
53 import com.google.common.base.Predicate;
54 import com.google.common.collect.ImmutableList;
55 import com.google.common.collect.Iterables;
57 public class BindingToNormalizedNodeCodec implements SchemaContextListener {
59 private static final Logger LOG = LoggerFactory.getLogger(BindingToNormalizedNodeCodec.class);
61 private final BindingIndependentMappingService bindingToLegacy;
62 private DataNormalizer legacyToNormalized;
64 public BindingToNormalizedNodeCodec(final BindingIndependentMappingService mappingService) {
66 this.bindingToLegacy = mappingService;
69 public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalized(
70 final InstanceIdentifier<? extends DataObject> binding) {
72 // Used instance-identifier codec do not support serialization of last
74 // argument if it is Augmentation (behaviour expected by old datastore)
75 // in this case, we explicitly check if last argument is augmentation
76 // to process it separately
77 if (isAugmentationIdentifier(binding)) {
78 return toNormalizedAugmented(binding);
80 return toNormalizedImpl(binding);
83 public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
84 final InstanceIdentifier<? extends DataObject> bindingPath, final DataObject bindingObject) {
85 return toNormalizedNode(toBindingEntry(bindingPath, bindingObject));
89 public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
90 final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> binding) {
91 Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode> legacyEntry = bindingToLegacy
93 Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalizedEntry = legacyToNormalized
94 .toNormalized(legacyEntry);
95 LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}", binding,
96 legacyEntry, normalizedEntry);
97 if (isAugmentation(binding.getKey().getTargetType())) {
99 for (DataContainerChild<? extends PathArgument, ?> child : ((DataContainerNode<?>) normalizedEntry
100 .getValue()).getValue()) {
101 if (child instanceof AugmentationNode) {
102 ImmutableList<PathArgument> childArgs = ImmutableList.<PathArgument> builder()
103 .addAll(normalizedEntry.getKey().getPathArguments()).add(child.getIdentifier()).build();
104 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
106 return toDOMEntry(childPath, child);
111 return normalizedEntry;
117 * Returns a Binding-Aware instance identifier from normalized
118 * instance-identifier if it is possible to create representation.
120 * Returns Optional.absent for cases where target is mixin node except
124 public Optional<InstanceIdentifier<? extends DataObject>> toBinding(
125 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
126 throws DeserializationException {
128 PathArgument lastArgument = Iterables.getLast(normalized.getPathArguments());
129 // Used instance-identifier codec do not support serialization of last
131 // argument if it is AugmentationIdentifier (behaviour expected by old
133 // in this case, we explicitly check if last argument is augmentation
134 // to process it separately
135 if (lastArgument instanceof AugmentationIdentifier) {
136 return toBindingAugmented(normalized);
138 return toBindingImpl(normalized);
141 private Optional<InstanceIdentifier<? extends DataObject>> toBindingAugmented(
142 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
143 throws DeserializationException {
144 Optional<InstanceIdentifier<? extends DataObject>> potential = toBindingImpl(normalized);
145 // Shorthand check, if codec already supports deserialization
146 // of AugmentationIdentifier we will return
147 if (potential.isPresent() && isAugmentationIdentifier(potential.get())) {
151 int normalizedCount = getAugmentationCount(normalized);
152 AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPathArguments());
154 // Here we employ small trick - Binding-aware Codec injects an pointer
155 // to augmentation class
156 // if child is referenced - so we will reference child and then shorten
158 LOG.trace("Looking for candidates to match {}", normalized);
159 for (QName child : lastArgument.getPossibleChildNames()) {
160 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = normalized.node(child);
162 if (isNotRepresentable(childPath)) {
163 LOG.trace("Path {} is not BI-representable, skipping it", childPath);
166 } catch (DataNormalizationException e) {
167 LOG.warn("Failed to denormalize path {}, skipping it", childPath, e);
171 Optional<InstanceIdentifier<? extends DataObject>> baId = toBindingImpl(childPath);
172 if (!baId.isPresent()) {
173 LOG.debug("No binding-aware identifier found for path {}, skipping it", childPath);
177 InstanceIdentifier<? extends DataObject> potentialPath = shortenToLastAugment(baId.get());
178 int potentialAugmentCount = getAugmentationCount(potentialPath);
179 if (potentialAugmentCount == normalizedCount) {
180 LOG.trace("Found matching path {}", potentialPath);
181 return Optional.<InstanceIdentifier<? extends DataObject>> of(potentialPath);
184 LOG.trace("Skipping mis-matched potential path {}", potentialPath);
187 LOG.trace("Failed to find augmentation matching {}", normalized);
188 return Optional.absent();
191 private Optional<InstanceIdentifier<? extends DataObject>> toBindingImpl(
192 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
193 throws DeserializationException {
194 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath;
197 if (isNotRepresentable(normalized)) {
198 return Optional.absent();
200 legacyPath = legacyToNormalized.toLegacy(normalized);
201 } catch (DataNormalizationException e) {
202 throw new IllegalStateException("Could not denormalize path.", e);
204 LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}",
205 legacyPath, normalized);
206 return Optional.<InstanceIdentifier<? extends DataObject>> of(bindingToLegacy.fromDataDom(legacyPath));
209 private boolean isNotRepresentable(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
210 throws DataNormalizationException {
211 DataNormalizationOperation<?> op = findNormalizationOperation(normalized);
212 if (op.isMixin() && op.getIdentifier() instanceof NodeIdentifier) {
221 private DataNormalizationOperation<?> findNormalizationOperation(
222 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
223 throws DataNormalizationException {
224 DataNormalizationOperation<?> current = legacyToNormalized.getRootOperation();
225 for (PathArgument arg : normalized.getPathArguments()) {
226 current = current.getChild(arg);
231 private static final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> toBindingEntry(
232 final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> key,
233 final DataObject value) {
234 return new SimpleEntry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>(
238 private static final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toDOMEntry(
239 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier key, final NormalizedNode<?, ?> value) {
240 return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>(key,
244 public DataObject toBinding(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> normalizedNode)
245 throws DeserializationException {
246 CompositeNode legacy = null;
247 if (isAugmentationIdentifier(path) && normalizedNode instanceof AugmentationNode) {
248 QName augIdentifier = BindingReflections.findQName(path.getTargetType());
249 ContainerNode virtualNode = Builders.containerBuilder() //
250 .withNodeIdentifier(new NodeIdentifier(augIdentifier)) //
251 .withChild((DataContainerChild<?, ?>) normalizedNode) //
253 legacy = (CompositeNode) DataNormalizer.toLegacy(virtualNode);
255 legacy = (CompositeNode) DataNormalizer.toLegacy(normalizedNode);
258 return bindingToLegacy.dataObjectFromDataDom(path, legacy);
261 public DataNormalizer getDataNormalizer() {
262 return legacyToNormalized;
265 public Optional<Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>> toBinding(
266 final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized)
267 throws DeserializationException {
268 Optional<InstanceIdentifier<? extends DataObject>> potentialPath = toBinding(normalized.getKey());
269 if (potentialPath.isPresent()) {
270 InstanceIdentifier<? extends DataObject> bindingPath = potentialPath.get();
271 DataObject bindingData = toBinding(bindingPath, normalized.getValue());
272 if (bindingData == null) {
273 LOG.warn("Failed to deserialize {} to Binding format. Binding path is: {}", normalized, bindingPath);
275 return Optional.of(toBindingEntry(bindingPath, bindingData));
277 return Optional.absent();
282 public void onGlobalContextUpdated(final SchemaContext arg0) {
283 legacyToNormalized = new DataNormalizer(arg0);
286 private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedAugmented(
287 final InstanceIdentifier<?> augPath) {
288 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed = toNormalizedImpl(augPath);
289 // If used instance identifier codec added supports for deserialization
290 // of last AugmentationIdentifier we will just reuse it
291 if (isAugmentationIdentifier(processed)) {
294 Optional<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> additionalSerialized;
295 additionalSerialized = toNormalizedAugmentedUsingChildContainers(augPath, processed);
297 if (additionalSerialized.isPresent()) {
298 return additionalSerialized.get();
300 additionalSerialized = toNormalizedAugmentedUsingChildLeafs(augPath, processed);
301 if (additionalSerialized.isPresent()) {
302 return additionalSerialized.get();
304 throw new IllegalStateException("Unabled to construct augmentation identfier for " + augPath);
308 * Tries to find correct augmentation identifier using children leafs
310 * This method uses normalized Instance Identifier of parent node to fetch
311 * schema and {@link BindingReflections#getModuleInfo(Class)} to learn about
312 * augmentation namespace, specificly, in which module it was defined.
314 * Then it uses it to filter all available augmentations for parent by
315 * module. After that it walks augmentations in particular module and
316 * pick-up first which at least one leaf name matches supplied augmentation.
317 * We could do this safely since YANG explicitly states that no any existing
318 * augmentations must differ in leaf fully qualified names.
322 * Binding Aware Path which ends with augment
327 private Optional<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> toNormalizedAugmentedUsingChildLeafs(
328 final InstanceIdentifier<?> augPath,
329 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier parentPath) {
331 DataNormalizationOperation<?> parentOp = legacyToNormalized.getOperation(parentPath);
332 if(!parentOp.getDataSchemaNode().isPresent()) {
333 return Optional.absent();
335 DataSchemaNode parentSchema = parentOp.getDataSchemaNode().get();
336 if (parentSchema instanceof AugmentationTarget) {
337 Set<AugmentationSchema> augmentations = ((AugmentationTarget) parentSchema).getAvailableAugmentations();
338 LOG.info("Augmentations for {}, {}", augPath, augmentations);
339 Optional<AugmentationSchema> schema = findAugmentation(augPath.getTargetType(), augmentations);
340 if (schema.isPresent()) {
341 AugmentationIdentifier augmentationIdentifier = DataNormalizationOperation
342 .augmentationIdentifierFrom(schema.get());
343 return Optional.of(parentPath.node(augmentationIdentifier));
346 } catch (DataNormalizationException e) {
347 throw new IllegalArgumentException(e);
349 return Optional.absent();
353 * Creates instance identifier for augmentation child, tries to serialize it
354 * Instance Identifier is then shortened to last augmentation.
356 * This is for situations, where underlying codec is implementing hydrogen
357 * style DOM APIs (which did not supported {@link AugmentationIdentifier}.)
361 * Path to parent node
364 @SuppressWarnings("rawtypes")
365 private Optional<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> toNormalizedAugmentedUsingChildContainers(
366 final InstanceIdentifier<?> augPath,
367 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier parentPath) {
368 for (Class augChild : BindingReflections.getChildrenClasses(augPath.getTargetType())) {
369 @SuppressWarnings("unchecked")
370 InstanceIdentifier<?> childPath = augPath.child(augChild);
371 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = toNormalizedImpl(childPath);
372 org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potentialDiscovered = shortenToLastAugmentation(
373 normalized, parentPath);
374 if (potentialDiscovered != null) {
375 return Optional.of(potentialDiscovered);
378 return Optional.absent();
381 private Optional<AugmentationSchema> findAugmentation(final Class targetType,
382 final Set<AugmentationSchema> augmentations) {
383 YangModuleInfo moduleInfo;
385 moduleInfo = BindingReflections.getModuleInfo(targetType);
386 } catch (Exception e) {
387 throw new IllegalStateException(e);
389 Iterable<AugmentationSchema> filtered = filteredByModuleInfo(augmentations,
390 BindingReflections.getModuleQName(moduleInfo).getModule());
392 Set<String> targetTypeGetters = getYangModeledGetters(targetType);
393 for (AugmentationSchema schema : filtered) {
394 for (DataSchemaNode child : schema.getChildNodes()) {
395 String getterName = "get" + BindingMapping.getClassName(child.getQName());
396 if (targetTypeGetters.contains(getterName)) {
397 return Optional.of(schema);
401 return Optional.absent();
404 private static Iterable<AugmentationSchema> filteredByModuleInfo(final Iterable<AugmentationSchema> augmentations,
405 final QNameModule module) {
406 return Iterables.filter(augmentations, new Predicate<AugmentationSchema>() {
408 public boolean apply(final AugmentationSchema schema) {
409 final Set<DataSchemaNode> childNodes = schema.getChildNodes();
410 return !schema.getChildNodes().isEmpty()
411 && module.equals(Iterables.get(childNodes, 0).getQName().getModule());
416 public static final Set<String> getYangModeledGetters(final Class<?> targetType) {
417 HashSet<String> ret = new HashSet<String>();
418 for (Method method : targetType.getMethods()) {
419 if (isYangModeledGetter(method)) {
420 ret.add(method.getName());
428 * Returns true if supplied method represent getter for YANG modeled value
431 * Method to be tested
432 * @return true if method represent getter for YANG Modeled value.
434 private static final boolean isYangModeledGetter(final Method method) {
435 return !method.getName().equals("getClass") && !method.getName().equals("getImplementedInterface")
436 && method.getName().startsWith("get") && method.getParameterTypes().length == 0;
439 private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier shortenToLastAugmentation(
440 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized,
441 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier parentPath) {
442 int parentSize = Iterables.size(parentPath.getPathArguments());
444 int foundPosition = -1;
445 for (PathArgument arg : normalized.getPathArguments()) {
447 if (arg instanceof AugmentationIdentifier) {
448 foundPosition = position;
451 if (foundPosition > 0 && foundPosition > parentSize) {
452 Iterable<PathArgument> shortened = Iterables.limit(normalized.getPathArguments(), foundPosition);
453 return org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(shortened);
458 private InstanceIdentifier<? extends DataObject> shortenToLastAugment(
459 final InstanceIdentifier<? extends DataObject> binding) {
461 int foundPosition = -1;
462 for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : binding.getPathArguments()) {
464 if (isAugmentation(arg.getType())) {
465 foundPosition = position;
468 return InstanceIdentifier.create(Iterables.limit(binding.getPathArguments(), foundPosition));
471 private org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalizedImpl(
472 final InstanceIdentifier<? extends DataObject> binding) {
473 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy
475 final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized
476 .toNormalized(legacyPath);
480 private static boolean isAugmentation(final Class<? extends DataObject> type) {
481 return Augmentation.class.isAssignableFrom(type);
484 private static boolean isAugmentationIdentifier(final InstanceIdentifier<?> potential) {
485 return Augmentation.class.isAssignableFrom(potential.getTargetType());
488 private boolean isAugmentationIdentifier(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed) {
489 return Iterables.getLast(processed.getPathArguments()) instanceof AugmentationIdentifier;
492 private static int getAugmentationCount(final InstanceIdentifier<?> potential) {
494 for (org.opendaylight.yangtools.yang.binding.InstanceIdentifier.PathArgument arg : potential.getPathArguments()) {
495 if (isAugmentation(arg.getType())) {
502 private static int getAugmentationCount(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potential) {
504 for (PathArgument arg : potential.getPathArguments()) {
505 if (arg instanceof AugmentationIdentifier) {
512 public Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>> deserializeFunction(
513 final InstanceIdentifier<?> path) {
514 return new DeserializeFunction(this, path);
517 private static class DeserializeFunction implements Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>> {
519 private final BindingToNormalizedNodeCodec codec;
520 private final InstanceIdentifier<?> path;
522 public DeserializeFunction(final BindingToNormalizedNodeCodec codec, final InstanceIdentifier<?> path) {
524 this.codec = Preconditions.checkNotNull(codec, "Codec must not be null");
525 this.path = Preconditions.checkNotNull(path, "Path must not be null");
530 public Optional<DataObject> apply(@Nullable final Optional<NormalizedNode<?, ?>> normalizedNode) {
531 if (normalizedNode.isPresent()) {
532 final DataObject dataObject;
534 dataObject = codec.toBinding(path, normalizedNode.get());
535 } catch (DeserializationException e) {
536 LOG.warn("Failed to create dataobject from node {}", normalizedNode.get(), e);
537 throw new IllegalStateException("Failed to create dataobject", e);
540 if (dataObject != null) {
541 return Optional.of(dataObject);
544 return Optional.absent();
549 * Returns an default object according to YANG schema for supplied path.
553 * @return Node with defaults set on.
555 public NormalizedNode<?, ?> getDefaultNodeFor(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier path) {
556 Iterator<PathArgument> iterator = path.getPathArguments().iterator();
557 DataNormalizationOperation<?> currentOp = legacyToNormalized.getRootOperation();
558 while (iterator.hasNext()) {
559 PathArgument currentArg = iterator.next();
561 currentOp = currentOp.getChild(currentArg);
562 } catch (DataNormalizationException e) {
563 throw new IllegalArgumentException(String.format("Invalid child encountered in path %s", path), e);
566 return currentOp.createDefault(path.getLastPathArgument());