2 * Copyright (c) 2015 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.yangtools.yang.data.impl.leafref;
10 import com.google.common.collect.ImmutableList;
11 import com.google.common.collect.ImmutableSet;
12 import com.google.common.collect.Iterables;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
20 import java.util.Map.Entry;
21 import java.util.Optional;
23 import java.util.stream.Collectors;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
28 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
29 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
31 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
38 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 // FIXME: 3.0.0: Rename to LeafRefValidation
47 public final class LeafRefValidatation {
49 private static final Logger LOG = LoggerFactory.getLogger(LeafRefValidatation.class);
50 private static final String FAILED = " -> FAILED";
51 private static final String SUCCESS = " -> OK";
53 private final Set<LeafRefContext> validatedLeafRefCtx = new HashSet<>();
54 private final List<String> errorsMessages = new ArrayList<>();
55 private final NormalizedNode<?, ?> root;
57 private LeafRefValidatation(final NormalizedNode<?, ?> root) {
61 public static void validate(final DataTreeCandidate tree, final LeafRefContext rootLeafRefCtx)
62 throws LeafRefDataValidationFailedException {
63 final Optional<NormalizedNode<?, ?>> root = tree.getRootNode().getDataAfter();
64 if (root.isPresent()) {
65 new LeafRefValidatation(root.get()).validateChildren(rootLeafRefCtx, tree.getRootNode().getChildNodes());
69 private void validateChildren(final LeafRefContext rootLeafRefCtx, final Collection<DataTreeCandidateNode> children)
70 throws LeafRefDataValidationFailedException {
71 for (final DataTreeCandidateNode dataTreeCandidateNode : children) {
72 if (dataTreeCandidateNode.getModificationType() != ModificationType.UNMODIFIED) {
73 final PathArgument identifier = dataTreeCandidateNode.getIdentifier();
74 final QName childQName = identifier.getNodeType();
76 final LeafRefContext referencedByCtx = rootLeafRefCtx.getReferencedChildByName(childQName);
77 final LeafRefContext referencingCtx = rootLeafRefCtx.getReferencingChildByName(childQName);
78 if (referencedByCtx != null || referencingCtx != null) {
79 final YangInstanceIdentifier yangInstanceIdentifier = YangInstanceIdentifier
80 .create(dataTreeCandidateNode.getIdentifier());
81 validateNode(dataTreeCandidateNode, referencedByCtx, referencingCtx, yangInstanceIdentifier);
86 if (!errorsMessages.isEmpty()) {
87 final StringBuilder message = new StringBuilder();
89 for (final String errorMessage : errorsMessages) {
90 message.append(errorMessage);
93 throw new LeafRefDataValidationFailedException(message.toString(), errCount);
97 private void validateNode(final DataTreeCandidateNode node, final LeafRefContext referencedByCtx,
98 final LeafRefContext referencingCtx, final YangInstanceIdentifier current) {
100 if (node.getModificationType() == ModificationType.WRITE && node.getDataAfter().isPresent()) {
101 final Optional<NormalizedNode<?, ?>> dataAfter = node.getDataAfter();
102 final NormalizedNode<?, ?> normalizedNode = dataAfter.get();
103 validateNodeData(normalizedNode, referencedByCtx, referencingCtx,
104 node.getModificationType(), current);
108 if (node.getModificationType() == ModificationType.DELETE && referencedByCtx != null) {
109 final Optional<NormalizedNode<?, ?>> dataBefor = node.getDataBefore();
110 final NormalizedNode<?, ?> normalizedNode = dataBefor.get();
111 validateNodeData(normalizedNode, referencedByCtx, null,
112 node.getModificationType(), current);
116 final Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
117 for (final DataTreeCandidateNode childNode : childNodes) {
118 if (childNode.getModificationType() != ModificationType.UNMODIFIED) {
119 final LeafRefContext childReferencedByCtx = getReferencedByCtxChild(referencedByCtx, childNode);
120 final LeafRefContext childReferencingCtx = getReferencingCtxChild(referencingCtx, childNode);
122 if (childReferencedByCtx != null || childReferencingCtx != null) {
123 final YangInstanceIdentifier childYangInstanceIdentifier = current.node(childNode.getIdentifier());
124 validateNode(childNode, childReferencedByCtx,childReferencingCtx, childYangInstanceIdentifier);
130 private static LeafRefContext getReferencingCtxChild(final LeafRefContext referencingCtx,
131 final DataTreeCandidateNode childNode) {
132 if (referencingCtx == null) {
136 final QName childQName = childNode.getIdentifier().getNodeType();
137 LeafRefContext childReferencingCtx = referencingCtx.getReferencingChildByName(childQName);
138 if (childReferencingCtx == null) {
139 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
140 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
141 childReferencingCtx = referencingCtx;
145 return childReferencingCtx;
148 private static LeafRefContext getReferencedByCtxChild(final LeafRefContext referencedByCtx,
149 final DataTreeCandidateNode childNode) {
150 if (referencedByCtx == null) {
154 final QName childQName = childNode.getIdentifier().getNodeType();
155 LeafRefContext childReferencedByCtx = referencedByCtx.getReferencedChildByName(childQName);
156 if (childReferencedByCtx == null) {
157 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
158 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
159 childReferencedByCtx = referencedByCtx;
163 return childReferencedByCtx;
166 private void validateNodeData(final NormalizedNode<?, ?> node, final LeafRefContext referencedByCtx,
167 final LeafRefContext referencingCtx, final ModificationType modificationType,
168 final YangInstanceIdentifier current) {
170 if (node instanceof LeafNode) {
171 final LeafNode<?> leaf = (LeafNode<?>) node;
173 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
174 validateLeafRefTargetNodeData(leaf, referencedByCtx, modificationType);
176 if (referencingCtx != null && referencingCtx.isReferencing()) {
177 validateLeafRefNodeData(leaf, referencingCtx, modificationType, current);
183 if (node instanceof LeafSetNode) {
184 if (referencedByCtx == null && referencingCtx == null) {
188 final LeafSetNode<?> leafSet = (LeafSetNode<?>) node;
189 for (final NormalizedNode<?, ?> leafSetEntry : leafSet.getValue()) {
190 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
191 validateLeafRefTargetNodeData(leafSetEntry, referencedByCtx, modificationType);
193 if (referencingCtx != null && referencingCtx.isReferencing()) {
194 validateLeafRefNodeData(leafSetEntry, referencingCtx, modificationType, current);
201 if (node instanceof ChoiceNode) {
202 final ChoiceNode choice = (ChoiceNode) node;
203 for (final DataContainerChild<? extends PathArgument, ?> dataContainerChild : choice.getValue()) {
204 final QName qname = dataContainerChild.getNodeType();
206 final LeafRefContext childReferencedByCtx;
207 if (referencedByCtx != null) {
208 childReferencedByCtx = findReferencedByCtxUnderChoice(referencedByCtx, qname);
210 childReferencedByCtx = null;
213 final LeafRefContext childReferencingCtx;
214 if (referencingCtx != null) {
215 childReferencingCtx = findReferencingCtxUnderChoice(referencingCtx, qname);
217 childReferencingCtx = null;
220 if (childReferencedByCtx != null || childReferencingCtx != null) {
221 final YangInstanceIdentifier childYangInstanceIdentifier = current
222 .node(dataContainerChild.getIdentifier());
223 validateNodeData(dataContainerChild, childReferencedByCtx,
224 childReferencingCtx, modificationType, childYangInstanceIdentifier);
227 } else if (node instanceof DataContainerNode) {
228 final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
230 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
231 if (child instanceof AugmentationNode) {
232 validateNodeData(child, referencedByCtx, referencingCtx, modificationType, current
233 .node(child.getIdentifier()));
237 final QName qname = child.getNodeType();
238 final LeafRefContext childReferencedByCtx;
239 if (referencedByCtx != null) {
240 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
242 childReferencedByCtx = null;
245 final LeafRefContext childReferencingCtx;
246 if (referencingCtx != null) {
247 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
249 childReferencingCtx = null;
252 if (childReferencedByCtx != null || childReferencingCtx != null) {
253 final YangInstanceIdentifier childYangInstanceIdentifier = current
254 .node(child.getIdentifier());
255 validateNodeData(child, childReferencedByCtx,
256 childReferencingCtx, modificationType, childYangInstanceIdentifier);
259 } else if (node instanceof MapNode) {
260 final MapNode map = (MapNode) node;
262 for (final MapEntryNode mapEntry : map.getValue()) {
263 final YangInstanceIdentifier mapEntryYangInstanceIdentifier = current.node(mapEntry.getIdentifier());
264 for (final DataContainerChild<? extends PathArgument, ?> mapEntryNode : mapEntry.getValue()) {
265 if (mapEntryNode instanceof AugmentationNode) {
266 validateNodeData(mapEntryNode, referencedByCtx, referencingCtx, modificationType, current
267 .node(mapEntryNode.getIdentifier()));
271 final QName qname = mapEntryNode.getNodeType();
272 final LeafRefContext childReferencedByCtx;
273 if (referencedByCtx != null) {
274 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
276 childReferencedByCtx = null;
279 final LeafRefContext childReferencingCtx;
280 if (referencingCtx != null) {
281 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
283 childReferencingCtx = null;
286 if (childReferencedByCtx != null || childReferencingCtx != null) {
287 validateNodeData(mapEntryNode, childReferencedByCtx, childReferencingCtx, modificationType,
288 mapEntryYangInstanceIdentifier.node(mapEntryNode.getIdentifier()));
293 // FIXME: check UnkeyedListNode case
296 private static LeafRefContext findReferencingCtxUnderChoice(
297 final LeafRefContext referencingCtx, final QName qname) {
299 for (final LeafRefContext child : referencingCtx.getReferencingChilds().values()) {
300 final LeafRefContext referencingChildByName = child.getReferencingChildByName(qname);
301 if (referencingChildByName != null) {
302 return referencingChildByName;
309 private static LeafRefContext findReferencedByCtxUnderChoice(
310 final LeafRefContext referencedByCtx, final QName qname) {
312 for (final LeafRefContext child : referencedByCtx.getReferencedByChilds().values()) {
313 final LeafRefContext referencedByChildByName = child.getReferencedChildByName(qname);
314 if (referencedByChildByName != null) {
315 return referencedByChildByName;
322 private void validateLeafRefTargetNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext
323 referencedByCtx, final ModificationType modificationType) {
324 if (!validatedLeafRefCtx.add(referencedByCtx)) {
325 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}] -> SKIP: Already "
326 + "validated", modificationType, referencedByCtx.getNodeName(), leaf.getValue());
330 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}]", modificationType,
331 referencedByCtx.getNodeName(), leaf.getValue());
332 final Set<LeafRefContext> leafRefs = referencedByCtx.getAllReferencedByLeafRefCtxs().values().stream()
333 .filter(LeafRefContext::isReferencing).collect(Collectors.toSet());
334 if (leafRefs.isEmpty()) {
338 final Set<Object> leafRefTargetNodeValues = extractRootValues(referencedByCtx);
339 leafRefs.forEach(leafRefContext -> {
340 extractRootValues(leafRefContext).forEach(leafRefsValue -> {
341 if (leafRefTargetNodeValues.contains(leafRefsValue)) {
342 LOG.trace("Valid leafref value [{}] {}", leafRefsValue, SUCCESS);
346 LOG.debug("Invalid leafref value [{}] allowed values {} by validation of leafref TARGET node: {} path "
347 + "of invalid LEAFREF node: {} leafRef target path: {} {}", leafRefsValue,
348 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
349 leafRefContext.getAbsoluteLeafRefTargetPath(), FAILED);
350 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s by validation of leafref"
351 + " TARGET node: %s path of invalid LEAFREF node: %s leafRef target path: %s %s", leafRefsValue,
352 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
353 leafRefContext.getAbsoluteLeafRefTargetPath(),
359 private Set<Object> extractRootValues(final LeafRefContext context) {
360 return computeValues(root, context.getLeafRefNodePath().getPathFromRoot(), null);
363 private void validateLeafRefNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext referencingCtx,
364 final ModificationType modificationType, final YangInstanceIdentifier current) {
365 final Set<Object> values = computeValues(root, referencingCtx.getAbsoluteLeafRefTargetPath().getPathFromRoot(),
367 if (values.contains(leaf.getValue())) {
368 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
369 referencingCtx.getNodeName(), leaf.getValue(), SUCCESS);
373 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
374 referencingCtx.getNodeName(), leaf.getValue(), FAILED);
375 LOG.debug("Invalid leafref value [{}] allowed values {} of LEAFREF node: {} leafRef target path: {}",
376 leaf.getValue(), values, leaf.getNodeType(), referencingCtx.getAbsoluteLeafRefTargetPath());
377 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s of LEAFREF node: %s leafRef "
378 + "target path: %s", leaf.getValue(), values, leaf.getNodeType(),
379 referencingCtx.getAbsoluteLeafRefTargetPath()));
382 private Set<Object> computeValues(final NormalizedNode<?, ?> node, final Iterable<QNameWithPredicate> path,
383 final YangInstanceIdentifier current) {
384 final HashSet<Object> values = new HashSet<>();
385 addValues(values, node, ImmutableList.of(), path, current);
389 private void addValues(final Set<Object> values, final NormalizedNode<?, ?> node,
390 final List<QNamePredicate> nodePredicates, final Iterable<QNameWithPredicate> path,
391 final YangInstanceIdentifier current) {
392 if (node instanceof ValueNode) {
393 values.add(node.getValue());
396 if (node instanceof LeafSetNode<?>) {
397 for (final NormalizedNode<?, ?> entry : ((LeafSetNode<?>) node).getValue()) {
398 values.add(entry.getValue());
403 final Iterator<QNameWithPredicate> iterator = path.iterator();
404 if (!iterator.hasNext()) {
408 final QNameWithPredicate next = iterator.next();
409 final QName qname = next.getQName();
410 final PathArgument pathArgument = new NodeIdentifier(qname);
411 if (node instanceof DataContainerNode) {
412 final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
413 final Optional<DataContainerChild<? extends PathArgument, ?>> child = dataContainerNode
414 .getChild(pathArgument);
416 if (child.isPresent()) {
417 addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
419 for (final ChoiceNode choiceNode : getChoiceNodes(dataContainerNode)) {
420 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
424 } else if (node instanceof MapNode) {
425 final MapNode map = (MapNode) node;
426 if (nodePredicates.isEmpty() || current == null) {
427 final Iterable<MapEntryNode> value = map.getValue();
428 for (final MapEntryNode mapEntryNode : value) {
429 final Optional<DataContainerChild<? extends PathArgument, ?>> child = mapEntryNode
430 .getChild(pathArgument);
432 if (child.isPresent()) {
433 addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
435 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
436 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
441 final Map<QName, Set<?>> keyValues = new HashMap<>();
442 for (QNamePredicate predicate : nodePredicates) {
443 final QName identifier = predicate.getIdentifier();
444 final LeafRefPath predicatePathKeyExpression = predicate.getPathKeyExpression();
445 final Set<?> pathKeyExprValues = getPathKeyExpressionValues(predicatePathKeyExpression, current);
447 keyValues.put(identifier, pathKeyExprValues);
450 for (final MapEntryNode mapEntryNode : map.getValue()) {
451 if (isMatchingPredicate(mapEntryNode, keyValues)) {
452 final Optional<DataContainerChild<? extends PathArgument, ?>> child = mapEntryNode
453 .getChild(pathArgument);
455 if (child.isPresent()) {
456 addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
458 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
459 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
468 private static Iterable<ChoiceNode> getChoiceNodes(final DataContainerNode<?> dataContainerNode) {
469 final List<ChoiceNode> choiceNodes = new ArrayList<>();
470 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
471 if (child instanceof ChoiceNode) {
472 choiceNodes.add((ChoiceNode) child);
478 private static boolean isMatchingPredicate(final MapEntryNode mapEntryNode,
479 final Map<QName, Set<?>> allowedKeyValues) {
480 for (final Entry<QName, Object> entryKeyValue : mapEntryNode.getIdentifier().getKeyValues().entrySet()) {
481 final Set<?> allowedValues = allowedKeyValues.get(entryKeyValue.getKey());
482 if (allowedValues != null && !allowedValues.contains(entryKeyValue.getValue())) {
490 private Set<?> getPathKeyExpressionValues(final LeafRefPath predicatePathKeyExpression,
491 final YangInstanceIdentifier current) {
492 return findParentNode(Optional.of(root), current)
493 .map(parent -> computeValues(parent, nextLevel(predicatePathKeyExpression.getPathFromRoot()), null))
494 .orElse(ImmutableSet.of());
497 private static Optional<NormalizedNode<?, ?>> findParentNode(
498 final Optional<NormalizedNode<?, ?>> root, final YangInstanceIdentifier path) {
499 Optional<NormalizedNode<?, ?>> currentNode = root;
500 final Iterator<PathArgument> pathIterator = path.getPathArguments().iterator();
501 while (pathIterator.hasNext()) {
502 final PathArgument childPathArgument = pathIterator.next();
503 if (pathIterator.hasNext() && currentNode.isPresent()) {
504 currentNode = NormalizedNodes.getDirectChild(currentNode.get(), childPathArgument);
509 return Optional.empty();
512 private static Iterable<QNameWithPredicate> nextLevel(final Iterable<QNameWithPredicate> path) {
513 return Iterables.skip(path, 1);