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.Iterables;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
18 import java.util.Map.Entry;
19 import java.util.Optional;
21 import java.util.stream.Collectors;
22 import org.opendaylight.yangtools.yang.common.QName;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
27 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
28 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
29 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
36 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
39 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 public final class LeafRefValidatation {
46 private static final Logger LOG = LoggerFactory.getLogger(LeafRefValidatation.class);
47 private static final String FAILED = " -> FAILED";
48 private static final String SUCCESS = " -> OK";
50 private final Set<LeafRefContext> validatedLeafRefCtx = new HashSet<>();
51 private final List<String> errorsMessages = new ArrayList<>();
52 private final DataTreeCandidate tree;
54 private LeafRefValidatation(final DataTreeCandidate tree) {
58 public static void validate(final DataTreeCandidate tree, final LeafRefContext rootLeafRefCtx)
59 throws LeafRefDataValidationFailedException {
60 new LeafRefValidatation(tree).validate0(rootLeafRefCtx);
63 private void validate0(final LeafRefContext rootLeafRefCtx) throws LeafRefDataValidationFailedException {
64 for (final DataTreeCandidateNode dataTreeCandidateNode : tree.getRootNode().getChildNodes()) {
65 if (dataTreeCandidateNode.getModificationType() != ModificationType.UNMODIFIED) {
66 final PathArgument identifier = dataTreeCandidateNode.getIdentifier();
67 final QName childQName = identifier.getNodeType();
69 final LeafRefContext referencedByCtx = rootLeafRefCtx.getReferencedChildByName(childQName);
70 final LeafRefContext referencingCtx = rootLeafRefCtx.getReferencingChildByName(childQName);
71 if (referencedByCtx != null || referencingCtx != null) {
72 final YangInstanceIdentifier yangInstanceIdentifier = YangInstanceIdentifier
73 .create(dataTreeCandidateNode.getIdentifier());
74 validateNode(dataTreeCandidateNode, referencedByCtx, referencingCtx, yangInstanceIdentifier);
79 if (!errorsMessages.isEmpty()) {
80 final StringBuilder message = new StringBuilder();
82 for (final String errorMessage : errorsMessages) {
83 message.append(errorMessage);
86 throw new LeafRefDataValidationFailedException(message.toString(), errCount);
90 private void validateNode(final DataTreeCandidateNode node, final LeafRefContext referencedByCtx,
91 final LeafRefContext referencingCtx, final YangInstanceIdentifier current) {
93 if (node.getModificationType() == ModificationType.WRITE && node.getDataAfter().isPresent()) {
94 final Optional<NormalizedNode<?, ?>> dataAfter = node.getDataAfter();
95 final NormalizedNode<?, ?> normalizedNode = dataAfter.get();
96 validateNodeData(normalizedNode, referencedByCtx, referencingCtx,
97 node.getModificationType(), current);
101 if (node.getModificationType() == ModificationType.DELETE && referencedByCtx != null) {
102 final Optional<NormalizedNode<?, ?>> dataBefor = node.getDataBefore();
103 final NormalizedNode<?, ?> normalizedNode = dataBefor.get();
104 validateNodeData(normalizedNode, referencedByCtx, null,
105 node.getModificationType(), current);
109 final Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
110 for (final DataTreeCandidateNode childNode : childNodes) {
111 if (childNode.getModificationType() != ModificationType.UNMODIFIED) {
112 final LeafRefContext childReferencedByCtx = getReferencedByCtxChild(referencedByCtx, childNode);
113 final LeafRefContext childReferencingCtx = getReferencingCtxChild(referencingCtx, childNode);
115 if (childReferencedByCtx != null || childReferencingCtx != null) {
116 final YangInstanceIdentifier childYangInstanceIdentifier = current.node(childNode.getIdentifier());
117 validateNode(childNode, childReferencedByCtx,childReferencingCtx, childYangInstanceIdentifier);
123 private static LeafRefContext getReferencingCtxChild(final LeafRefContext referencingCtx,
124 final DataTreeCandidateNode childNode) {
125 if (referencingCtx == null) {
129 final QName childQName = childNode.getIdentifier().getNodeType();
130 LeafRefContext childReferencingCtx = referencingCtx.getReferencingChildByName(childQName);
131 if (childReferencingCtx == null) {
132 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
133 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
134 childReferencingCtx = referencingCtx;
138 return childReferencingCtx;
141 private static LeafRefContext getReferencedByCtxChild(final LeafRefContext referencedByCtx,
142 final DataTreeCandidateNode childNode) {
143 if (referencedByCtx == null) {
147 final QName childQName = childNode.getIdentifier().getNodeType();
148 LeafRefContext childReferencedByCtx = referencedByCtx.getReferencedChildByName(childQName);
149 if (childReferencedByCtx == null) {
150 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
151 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
152 childReferencedByCtx = referencedByCtx;
156 return childReferencedByCtx;
159 private void validateNodeData(final NormalizedNode<?, ?> node, final LeafRefContext referencedByCtx,
160 final LeafRefContext referencingCtx, final ModificationType modificationType,
161 final YangInstanceIdentifier current) {
163 if (node instanceof LeafNode) {
164 final LeafNode<?> leaf = (LeafNode<?>) node;
166 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
167 validateLeafRefTargetNodeData(leaf, referencedByCtx, modificationType);
169 if (referencingCtx != null && referencingCtx.isReferencing()) {
170 validateLeafRefNodeData(leaf, referencingCtx, modificationType, current);
176 if (node instanceof LeafSetNode) {
177 if (referencedByCtx == null && referencingCtx == null) {
181 final LeafSetNode<?> leafSet = (LeafSetNode<?>) node;
182 for (final NormalizedNode<?, ?> leafSetEntry : leafSet.getValue()) {
183 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
184 validateLeafRefTargetNodeData(leafSetEntry, referencedByCtx, modificationType);
186 if (referencingCtx != null && referencingCtx.isReferencing()) {
187 validateLeafRefNodeData(leafSetEntry, referencingCtx, modificationType, current);
194 if (node instanceof ChoiceNode) {
195 final ChoiceNode choice = (ChoiceNode) node;
196 for (final DataContainerChild<? extends PathArgument, ?> dataContainerChild : choice.getValue()) {
197 final QName qname = dataContainerChild.getNodeType();
199 final LeafRefContext childReferencedByCtx;
200 if (referencedByCtx != null) {
201 childReferencedByCtx = findReferencedByCtxUnderChoice(referencedByCtx, qname);
203 childReferencedByCtx = null;
206 final LeafRefContext childReferencingCtx;
207 if (referencingCtx != null) {
208 childReferencingCtx = findReferencingCtxUnderChoice(referencingCtx, qname);
210 childReferencingCtx = null;
213 if (childReferencedByCtx != null || childReferencingCtx != null) {
214 final YangInstanceIdentifier childYangInstanceIdentifier = current
215 .node(dataContainerChild.getIdentifier());
216 validateNodeData(dataContainerChild, childReferencedByCtx,
217 childReferencingCtx, modificationType, childYangInstanceIdentifier);
220 } else if (node instanceof DataContainerNode) {
221 final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
223 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
224 if (child instanceof AugmentationNode) {
225 validateNodeData(child, referencedByCtx, referencingCtx, modificationType, current
226 .node(child.getIdentifier()));
230 final QName qname = child.getNodeType();
231 final LeafRefContext childReferencedByCtx;
232 if (referencedByCtx != null) {
233 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
235 childReferencedByCtx = null;
238 final LeafRefContext childReferencingCtx;
239 if (referencingCtx != null) {
240 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
242 childReferencingCtx = null;
245 if (childReferencedByCtx != null || childReferencingCtx != null) {
246 final YangInstanceIdentifier childYangInstanceIdentifier = current
247 .node(child.getIdentifier());
248 validateNodeData(child, childReferencedByCtx,
249 childReferencingCtx, modificationType, childYangInstanceIdentifier);
252 } else if (node instanceof MapNode) {
253 final MapNode map = (MapNode) node;
255 for (final MapEntryNode mapEntry : map.getValue()) {
256 final YangInstanceIdentifier mapEntryYangInstanceIdentifier = current.node(mapEntry.getIdentifier());
257 for (final DataContainerChild<? extends PathArgument, ?> mapEntryNode : mapEntry.getValue()) {
258 if (mapEntryNode instanceof AugmentationNode) {
259 validateNodeData(mapEntryNode, referencedByCtx, referencingCtx, modificationType, current
260 .node(mapEntryNode.getIdentifier()));
264 final QName qname = mapEntryNode.getNodeType();
265 final LeafRefContext childReferencedByCtx;
266 if (referencedByCtx != null) {
267 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
269 childReferencedByCtx = null;
272 final LeafRefContext childReferencingCtx;
273 if (referencingCtx != null) {
274 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
276 childReferencingCtx = null;
279 if (childReferencedByCtx != null || childReferencingCtx != null) {
280 validateNodeData(mapEntryNode, childReferencedByCtx, childReferencingCtx, modificationType,
281 mapEntryYangInstanceIdentifier.node(mapEntryNode.getIdentifier()));
286 // FIXME: check UnkeyedListNode case
289 private static LeafRefContext findReferencingCtxUnderChoice(
290 final LeafRefContext referencingCtx, final QName qname) {
292 for (final LeafRefContext child : referencingCtx.getReferencingChilds().values()) {
293 final LeafRefContext referencingChildByName = child.getReferencingChildByName(qname);
294 if (referencingChildByName != null) {
295 return referencingChildByName;
302 private static LeafRefContext findReferencedByCtxUnderChoice(
303 final LeafRefContext referencedByCtx, final QName qname) {
305 for (final LeafRefContext child : referencedByCtx.getReferencedByChilds().values()) {
306 final LeafRefContext referencedByChildByName = child.getReferencedChildByName(qname);
307 if (referencedByChildByName != null) {
308 return referencedByChildByName;
315 private void validateLeafRefTargetNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext
316 referencedByCtx, final ModificationType modificationType) {
317 if (!validatedLeafRefCtx.add(referencedByCtx)) {
318 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}] -> SKIP: Already "
319 + "validated", modificationType, referencedByCtx.getNodeName(), leaf.getValue());
323 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}]", modificationType,
324 referencedByCtx.getNodeName(), leaf.getValue());
325 final Set<LeafRefContext> leafRefs = referencedByCtx.getAllReferencedByLeafRefCtxs().values().stream()
326 .filter(LeafRefContext::isReferencing).collect(Collectors.toSet());
327 if (leafRefs.isEmpty()) {
331 final Set<Object> leafRefTargetNodeValues = extractRootValues(referencedByCtx);
332 leafRefs.forEach(leafRefContext -> {
333 extractRootValues(leafRefContext).forEach(leafRefsValue -> {
334 if (leafRefTargetNodeValues.contains(leafRefsValue)) {
335 LOG.trace("Valid leafref value [{}] {}", leafRefsValue, SUCCESS);
339 LOG.debug("Invalid leafref value [{}] allowed values {} by validation of leafref TARGET node: {} path "
340 + "of invalid LEAFREF node: {} leafRef target path: {} {}", leafRefsValue,
341 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
342 leafRefContext.getAbsoluteLeafRefTargetPath(), FAILED);
343 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s by validation of leafref"
344 + " TARGET node: %s path of invalid LEAFREF node: %s leafRef target path: %s %s", leafRefsValue,
345 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
346 leafRefContext.getAbsoluteLeafRefTargetPath(),
352 private Set<Object> extractRootValues(final LeafRefContext context) {
353 final Set<Object> values = new HashSet<>();
354 addValues(values, tree.getRootNode().getDataAfter(), context.getLeafRefNodePath().getPathFromRoot(), null,
355 QNameWithPredicate.ROOT);
359 private void validateLeafRefNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext referencingCtx,
360 final ModificationType modificationType, final YangInstanceIdentifier current) {
361 final HashSet<Object> values = new HashSet<>();
362 addValues(values, tree.getRootNode().getDataAfter(),
363 referencingCtx.getAbsoluteLeafRefTargetPath().getPathFromRoot(), current, QNameWithPredicate.ROOT);
365 if (values.contains(leaf.getValue())) {
366 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
367 referencingCtx.getNodeName(), leaf.getValue(), SUCCESS);
371 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
372 referencingCtx.getNodeName(), leaf.getValue(), FAILED);
373 LOG.debug("Invalid leafref value [{}] allowed values {} of LEAFREF node: {} leafRef target path: {}",
374 leaf.getValue(), values, leaf.getNodeType(), referencingCtx.getAbsoluteLeafRefTargetPath());
375 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s of LEAFREF node: %s leafRef "
376 + "target path: %s", leaf.getValue(), values, leaf.getNodeType(),
377 referencingCtx.getAbsoluteLeafRefTargetPath()));
380 private void addValues(final Set<Object> values, final Optional<? extends NormalizedNode<?, ?>> optDataNode,
381 final Iterable<QNameWithPredicate> path, final YangInstanceIdentifier current,
382 final QNameWithPredicate previousQName) {
384 if (!optDataNode.isPresent()) {
387 final NormalizedNode<?, ?> node = optDataNode.get();
388 if (node instanceof ValueNode) {
389 values.add(node.getValue());
392 if (node instanceof LeafSetNode<?>) {
393 for (final NormalizedNode<?, ?> entry : ((LeafSetNode<?>) node).getValue()) {
394 values.add(entry.getValue());
399 final Iterator<QNameWithPredicate> iterator = path.iterator();
400 if (!iterator.hasNext()) {
403 final QNameWithPredicate qnameWithPredicate = iterator.next();
404 final QName qName = qnameWithPredicate.getQName();
405 final PathArgument pathArgument = new NodeIdentifier(qName);
407 if (node instanceof DataContainerNode) {
408 final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
409 final Optional<DataContainerChild<? extends PathArgument, ?>> child = dataContainerNode
410 .getChild(pathArgument);
412 if (child.isPresent()) {
413 addValues(values, child, nextLevel(path), current, qnameWithPredicate);
415 for (final ChoiceNode choiceNode : getChoiceNodes(dataContainerNode)) {
416 addValues(values, Optional.of(choiceNode), path, current,
421 } else if (node instanceof MapNode) {
422 final MapNode map = (MapNode) node;
423 final List<QNamePredicate> qNamePredicates = previousQName.getQNamePredicates();
424 if (qNamePredicates.isEmpty() || current == null) {
425 final Iterable<MapEntryNode> value = map.getValue();
426 for (final MapEntryNode mapEntryNode : value) {
427 final Optional<DataContainerChild<? extends PathArgument, ?>> child = mapEntryNode
428 .getChild(pathArgument);
430 if (child.isPresent()) {
431 addValues(values, child, nextLevel(path), current, qnameWithPredicate);
433 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
434 addValues(values, Optional.of(choiceNode), path, current, qnameWithPredicate);
439 final Map<QName, Set<?>> keyValues = new HashMap<>();
441 final Iterator<QNamePredicate> predicates = qNamePredicates.iterator();
442 while (predicates.hasNext()) {
443 final QNamePredicate predicate = predicates.next();
444 final QName identifier = predicate.getIdentifier();
445 final LeafRefPath predicatePathKeyExpression = predicate
446 .getPathKeyExpression();
448 final Set<?> pathKeyExprValues = getPathKeyExpressionValues(
449 predicatePathKeyExpression, current);
451 keyValues.put(identifier, pathKeyExprValues);
454 for (final MapEntryNode mapEntryNode : map.getValue()) {
455 if (isMatchingPredicate(mapEntryNode, keyValues)) {
456 final Optional<DataContainerChild<? extends PathArgument, ?>> child = mapEntryNode
457 .getChild(pathArgument);
459 if (child.isPresent()) {
460 addValues(values, child, nextLevel(path), current, qnameWithPredicate);
462 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
463 addValues(values, Optional.of(choiceNode), path, current, qnameWithPredicate);
472 private static Iterable<ChoiceNode> getChoiceNodes(final DataContainerNode<?> dataContainerNode) {
473 final List<ChoiceNode> choiceNodes = new ArrayList<>();
474 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
475 if (child instanceof ChoiceNode) {
476 choiceNodes.add((ChoiceNode) child);
482 private static boolean isMatchingPredicate(final MapEntryNode mapEntryNode,
483 final Map<QName, Set<?>> allowedKeyValues) {
484 for (final Entry<QName, Object> entryKeyValue : mapEntryNode.getIdentifier().getKeyValues().entrySet()) {
485 final Set<?> allowedValues = allowedKeyValues.get(entryKeyValue.getKey());
486 if (allowedValues != null && !allowedValues.contains(entryKeyValue.getValue())) {
494 private Set<?> getPathKeyExpressionValues(final LeafRefPath predicatePathKeyExpression,
495 final YangInstanceIdentifier current) {
496 final Optional<NormalizedNode<?, ?>> parent = findParentNode(tree.getRootNode().getDataAfter(), current);
497 final Set<Object> values = new HashSet<>();
498 addValues(values, parent, nextLevel(predicatePathKeyExpression.getPathFromRoot()), null,
499 QNameWithPredicate.ROOT);
504 private static Optional<NormalizedNode<?, ?>> findParentNode(
505 final Optional<NormalizedNode<?, ?>> root, final YangInstanceIdentifier path) {
506 Optional<NormalizedNode<?, ?>> currentNode = root;
507 final Iterator<PathArgument> pathIterator = path.getPathArguments().iterator();
508 while (pathIterator.hasNext()) {
509 final PathArgument childPathArgument = pathIterator.next();
510 if (pathIterator.hasNext() && currentNode.isPresent()) {
511 currentNode = NormalizedNodes.getDirectChild(currentNode.get(), childPathArgument);
516 return Optional.empty();
519 private static Iterable<QNameWithPredicate> nextLevel(final Iterable<QNameWithPredicate> path) {
520 return Iterables.skip(path, 1);