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 java.util.ArrayDeque;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Deque;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.Optional;
24 import java.util.stream.Collectors;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
30 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
31 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
32 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
39 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
42 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 // FIXME: 3.0.0: Rename to LeafRefValidation
48 public final class LeafRefValidatation {
50 private static final Logger LOG = LoggerFactory.getLogger(LeafRefValidatation.class);
51 private static final String FAILED = " -> FAILED";
52 private static final String SUCCESS = " -> OK";
54 private final Set<LeafRefContext> validatedLeafRefCtx = new HashSet<>();
55 private final List<String> errorsMessages = new ArrayList<>();
56 private final NormalizedNode<?, ?> root;
58 private LeafRefValidatation(final NormalizedNode<?, ?> root) {
62 public static void validate(final DataTreeCandidate tree, final LeafRefContext rootLeafRefCtx)
63 throws LeafRefDataValidationFailedException {
64 final Optional<NormalizedNode<?, ?>> root = tree.getRootNode().getDataAfter();
65 if (root.isPresent()) {
66 new LeafRefValidatation(root.get()).validateChildren(rootLeafRefCtx, tree.getRootNode().getChildNodes());
70 private void validateChildren(final LeafRefContext rootLeafRefCtx, final Collection<DataTreeCandidateNode> children)
71 throws LeafRefDataValidationFailedException {
72 for (final DataTreeCandidateNode dataTreeCandidateNode : children) {
73 if (dataTreeCandidateNode.getModificationType() != ModificationType.UNMODIFIED) {
74 final PathArgument identifier = dataTreeCandidateNode.getIdentifier();
75 final QName childQName = identifier.getNodeType();
77 final LeafRefContext referencedByCtx = rootLeafRefCtx.getReferencedChildByName(childQName);
78 final LeafRefContext referencingCtx = rootLeafRefCtx.getReferencingChildByName(childQName);
79 if (referencedByCtx != null || referencingCtx != null) {
80 final YangInstanceIdentifier yangInstanceIdentifier = YangInstanceIdentifier
81 .create(dataTreeCandidateNode.getIdentifier());
82 validateNode(dataTreeCandidateNode, referencedByCtx, referencingCtx, yangInstanceIdentifier);
87 if (!errorsMessages.isEmpty()) {
88 final StringBuilder message = new StringBuilder();
90 for (final String errorMessage : errorsMessages) {
91 message.append(errorMessage);
94 throw new LeafRefDataValidationFailedException(message.toString(), errCount);
98 private void validateNode(final DataTreeCandidateNode node, final LeafRefContext referencedByCtx,
99 final LeafRefContext referencingCtx, final YangInstanceIdentifier current) {
101 if (node.getModificationType() == ModificationType.WRITE && node.getDataAfter().isPresent()) {
102 final Optional<NormalizedNode<?, ?>> dataAfter = node.getDataAfter();
103 final NormalizedNode<?, ?> normalizedNode = dataAfter.get();
104 validateNodeData(normalizedNode, referencedByCtx, referencingCtx,
105 node.getModificationType(), current);
109 if (node.getModificationType() == ModificationType.DELETE && referencedByCtx != null) {
110 final Optional<NormalizedNode<?, ?>> dataBefor = node.getDataBefore();
111 final NormalizedNode<?, ?> normalizedNode = dataBefor.get();
112 validateNodeData(normalizedNode, referencedByCtx, null,
113 node.getModificationType(), current);
117 final Collection<DataTreeCandidateNode> childNodes = node.getChildNodes();
118 for (final DataTreeCandidateNode childNode : childNodes) {
119 if (childNode.getModificationType() != ModificationType.UNMODIFIED) {
120 final LeafRefContext childReferencedByCtx = getReferencedByCtxChild(referencedByCtx, childNode);
121 final LeafRefContext childReferencingCtx = getReferencingCtxChild(referencingCtx, childNode);
123 if (childReferencedByCtx != null || childReferencingCtx != null) {
124 final YangInstanceIdentifier childYangInstanceIdentifier = current.node(childNode.getIdentifier());
125 validateNode(childNode, childReferencedByCtx,childReferencingCtx, childYangInstanceIdentifier);
131 private static LeafRefContext getReferencingCtxChild(final LeafRefContext referencingCtx,
132 final DataTreeCandidateNode childNode) {
133 if (referencingCtx == null) {
137 final QName childQName = childNode.getIdentifier().getNodeType();
138 LeafRefContext childReferencingCtx = referencingCtx.getReferencingChildByName(childQName);
139 if (childReferencingCtx == null) {
140 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
141 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
142 childReferencingCtx = referencingCtx;
146 return childReferencingCtx;
149 private static LeafRefContext getReferencedByCtxChild(final LeafRefContext referencedByCtx,
150 final DataTreeCandidateNode childNode) {
151 if (referencedByCtx == null) {
155 final QName childQName = childNode.getIdentifier().getNodeType();
156 LeafRefContext childReferencedByCtx = referencedByCtx.getReferencedChildByName(childQName);
157 if (childReferencedByCtx == null) {
158 final NormalizedNode<?, ?> data = childNode.getDataAfter().get();
159 if (data instanceof MapEntryNode || data instanceof UnkeyedListEntryNode) {
160 childReferencedByCtx = referencedByCtx;
164 return childReferencedByCtx;
167 private void validateNodeData(final NormalizedNode<?, ?> node, final LeafRefContext referencedByCtx,
168 final LeafRefContext referencingCtx, final ModificationType modificationType,
169 final YangInstanceIdentifier current) {
171 if (node instanceof LeafNode) {
172 final LeafNode<?> leaf = (LeafNode<?>) node;
174 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
175 validateLeafRefTargetNodeData(leaf, referencedByCtx, modificationType);
177 if (referencingCtx != null && referencingCtx.isReferencing()) {
178 validateLeafRefNodeData(leaf, referencingCtx, modificationType, current);
184 if (node instanceof LeafSetNode) {
185 if (referencedByCtx == null && referencingCtx == null) {
189 final LeafSetNode<?> leafSet = (LeafSetNode<?>) node;
190 for (final NormalizedNode<?, ?> leafSetEntry : leafSet.getValue()) {
191 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
192 validateLeafRefTargetNodeData(leafSetEntry, referencedByCtx, modificationType);
194 if (referencingCtx != null && referencingCtx.isReferencing()) {
195 validateLeafRefNodeData(leafSetEntry, referencingCtx, modificationType, current);
202 if (node instanceof ChoiceNode) {
203 final ChoiceNode choice = (ChoiceNode) node;
204 for (final DataContainerChild<? extends PathArgument, ?> dataContainerChild : choice.getValue()) {
205 final QName qname = dataContainerChild.getNodeType();
207 final LeafRefContext childReferencedByCtx;
208 if (referencedByCtx != null) {
209 childReferencedByCtx = findReferencedByCtxUnderChoice(referencedByCtx, qname);
211 childReferencedByCtx = null;
214 final LeafRefContext childReferencingCtx;
215 if (referencingCtx != null) {
216 childReferencingCtx = findReferencingCtxUnderChoice(referencingCtx, qname);
218 childReferencingCtx = null;
221 if (childReferencedByCtx != null || childReferencingCtx != null) {
222 final YangInstanceIdentifier childYangInstanceIdentifier = current
223 .node(dataContainerChild.getIdentifier());
224 validateNodeData(dataContainerChild, childReferencedByCtx,
225 childReferencingCtx, modificationType, childYangInstanceIdentifier);
228 } else if (node instanceof DataContainerNode) {
229 final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
231 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
232 if (child instanceof AugmentationNode) {
233 validateNodeData(child, referencedByCtx, referencingCtx, modificationType, current
234 .node(child.getIdentifier()));
238 final QName qname = child.getNodeType();
239 final LeafRefContext childReferencedByCtx;
240 if (referencedByCtx != null) {
241 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
243 childReferencedByCtx = null;
246 final LeafRefContext childReferencingCtx;
247 if (referencingCtx != null) {
248 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
250 childReferencingCtx = null;
253 if (childReferencedByCtx != null || childReferencingCtx != null) {
254 final YangInstanceIdentifier childYangInstanceIdentifier = current
255 .node(child.getIdentifier());
256 validateNodeData(child, childReferencedByCtx,
257 childReferencingCtx, modificationType, childYangInstanceIdentifier);
260 } else if (node instanceof MapNode) {
261 final MapNode map = (MapNode) node;
263 for (final MapEntryNode mapEntry : map.getValue()) {
264 final YangInstanceIdentifier mapEntryYangInstanceIdentifier = current.node(mapEntry.getIdentifier());
265 for (final DataContainerChild<? extends PathArgument, ?> mapEntryNode : mapEntry.getValue()) {
266 if (mapEntryNode instanceof AugmentationNode) {
267 validateNodeData(mapEntryNode, referencedByCtx, referencingCtx, modificationType, current
268 .node(mapEntryNode.getIdentifier()));
272 final QName qname = mapEntryNode.getNodeType();
273 final LeafRefContext childReferencedByCtx;
274 if (referencedByCtx != null) {
275 childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
277 childReferencedByCtx = null;
280 final LeafRefContext childReferencingCtx;
281 if (referencingCtx != null) {
282 childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
284 childReferencingCtx = null;
287 if (childReferencedByCtx != null || childReferencingCtx != null) {
288 validateNodeData(mapEntryNode, childReferencedByCtx, childReferencingCtx, modificationType,
289 mapEntryYangInstanceIdentifier.node(mapEntryNode.getIdentifier()));
294 // FIXME: check UnkeyedListNode case
297 private static LeafRefContext findReferencingCtxUnderChoice(
298 final LeafRefContext referencingCtx, final QName qname) {
300 for (final LeafRefContext child : referencingCtx.getReferencingChilds().values()) {
301 final LeafRefContext referencingChildByName = child.getReferencingChildByName(qname);
302 if (referencingChildByName != null) {
303 return referencingChildByName;
310 private static LeafRefContext findReferencedByCtxUnderChoice(
311 final LeafRefContext referencedByCtx, final QName qname) {
313 for (final LeafRefContext child : referencedByCtx.getReferencedByChilds().values()) {
314 final LeafRefContext referencedByChildByName = child.getReferencedChildByName(qname);
315 if (referencedByChildByName != null) {
316 return referencedByChildByName;
323 private void validateLeafRefTargetNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext
324 referencedByCtx, final ModificationType modificationType) {
325 if (!validatedLeafRefCtx.add(referencedByCtx)) {
326 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}] -> SKIP: Already "
327 + "validated", modificationType, referencedByCtx.getNodeName(), leaf.getValue());
331 LOG.trace("Operation [{}] validate data of leafref TARGET node: name[{}] = value[{}]", modificationType,
332 referencedByCtx.getNodeName(), leaf.getValue());
333 final Set<LeafRefContext> leafRefs = referencedByCtx.getAllReferencedByLeafRefCtxs().values().stream()
334 .filter(LeafRefContext::isReferencing).collect(Collectors.toSet());
335 if (leafRefs.isEmpty()) {
339 final Set<Object> leafRefTargetNodeValues = extractRootValues(referencedByCtx);
340 leafRefs.forEach(leafRefContext -> {
341 extractRootValues(leafRefContext).forEach(leafRefsValue -> {
342 if (leafRefTargetNodeValues.contains(leafRefsValue)) {
343 LOG.trace("Valid leafref value [{}] {}", leafRefsValue, SUCCESS);
347 LOG.debug("Invalid leafref value [{}] allowed values {} by validation of leafref TARGET node: {} path "
348 + "of invalid LEAFREF node: {} leafRef target path: {} {}", leafRefsValue,
349 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
350 leafRefContext.getAbsoluteLeafRefTargetPath(), FAILED);
351 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s by validation of leafref"
352 + " TARGET node: %s path of invalid LEAFREF node: %s leafRef target path: %s %s", leafRefsValue,
353 leafRefTargetNodeValues, leaf.getNodeType(), leafRefContext.getCurrentNodePath(),
354 leafRefContext.getAbsoluteLeafRefTargetPath(),
360 private Set<Object> extractRootValues(final LeafRefContext context) {
361 return computeValues(root, createPath(context.getLeafRefNodePath()), null);
364 private void validateLeafRefNodeData(final NormalizedNode<?, ?> leaf, final LeafRefContext referencingCtx,
365 final ModificationType modificationType, final YangInstanceIdentifier current) {
366 final Set<Object> values = computeValues(root, createPath(referencingCtx.getAbsoluteLeafRefTargetPath()),
368 if (values.contains(leaf.getValue())) {
369 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
370 referencingCtx.getNodeName(), leaf.getValue(), SUCCESS);
374 LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
375 referencingCtx.getNodeName(), leaf.getValue(), FAILED);
376 LOG.debug("Invalid leafref value [{}] allowed values {} of LEAFREF node: {} leafRef target path: {}",
377 leaf.getValue(), values, leaf.getNodeType(), referencingCtx.getAbsoluteLeafRefTargetPath());
378 errorsMessages.add(String.format("Invalid leafref value [%s] allowed values %s of LEAFREF node: %s leafRef "
379 + "target path: %s", leaf.getValue(), values, leaf.getNodeType(),
380 referencingCtx.getAbsoluteLeafRefTargetPath()));
383 private Set<Object> computeValues(final NormalizedNode<?, ?> node, final Deque<QNameWithPredicate> path,
384 final YangInstanceIdentifier current) {
385 final HashSet<Object> values = new HashSet<>();
386 addValues(values, node, ImmutableList.of(), path, current);
390 private void addValues(final Set<Object> values, final NormalizedNode<?, ?> node,
391 final List<QNamePredicate> nodePredicates, final Deque<QNameWithPredicate> path,
392 final YangInstanceIdentifier current) {
393 if (node instanceof ValueNode) {
394 values.add(node.getValue());
397 if (node instanceof LeafSetNode<?>) {
398 for (final NormalizedNode<?, ?> entry : ((LeafSetNode<?>) node).getValue()) {
399 values.add(entry.getValue());
404 final QNameWithPredicate next = path.peek();
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<?, ?>> child = dataContainerNode.getChild(pathArgument);
414 if (child.isPresent()) {
415 addNextValues(values, child.get(), next.getQNamePredicates(), path, current);
417 for (final ChoiceNode choiceNode : getChoiceNodes(dataContainerNode)) {
418 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
421 } else if (node instanceof MapNode) {
422 final MapNode map = (MapNode) node;
423 if (nodePredicates.isEmpty() || current == null) {
424 final Iterable<MapEntryNode> value = map.getValue();
425 for (final MapEntryNode mapEntryNode : value) {
426 final Optional<DataContainerChild<?, ?>> child = mapEntryNode.getChild(pathArgument);
427 if (child.isPresent()) {
428 addNextValues(values, child.get(), next.getQNamePredicates(), path, current);
430 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
431 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
436 final Map<QName, Set<?>> keyValues = new HashMap<>();
437 for (QNamePredicate predicate : nodePredicates) {
438 keyValues.put(predicate.getIdentifier(),
439 getPathKeyExpressionValues(predicate.getPathKeyExpression(), current));
442 for (final MapEntryNode mapEntryNode : map.getValue()) {
443 if (isMatchingPredicate(mapEntryNode, keyValues)) {
444 final Optional<DataContainerChild<?, ?>> child = mapEntryNode.getChild(pathArgument);
445 if (child.isPresent()) {
446 addNextValues(values, child.get(), next.getQNamePredicates(), path, current);
448 for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
449 addValues(values, choiceNode, next.getQNamePredicates(), path, current);
458 private void addNextValues(final Set<Object> values, final NormalizedNode<?, ?> node,
459 final List<QNamePredicate> nodePredicates, final Deque<QNameWithPredicate> path,
460 final YangInstanceIdentifier current) {
461 final QNameWithPredicate element = path.pop();
463 addValues(values, node, nodePredicates, path, current);
469 private static Iterable<ChoiceNode> getChoiceNodes(final DataContainerNode<?> dataContainerNode) {
470 final List<ChoiceNode> choiceNodes = new ArrayList<>();
471 for (final DataContainerChild<? extends PathArgument, ?> child : dataContainerNode.getValue()) {
472 if (child instanceof ChoiceNode) {
473 choiceNodes.add((ChoiceNode) child);
479 private static boolean isMatchingPredicate(final MapEntryNode mapEntryNode,
480 final Map<QName, Set<?>> allowedKeyValues) {
481 for (final Entry<QName, Object> entryKeyValue : mapEntryNode.getIdentifier().getKeyValues().entrySet()) {
482 final Set<?> allowedValues = allowedKeyValues.get(entryKeyValue.getKey());
483 if (allowedValues != null && !allowedValues.contains(entryKeyValue.getValue())) {
491 private Set<?> getPathKeyExpressionValues(final LeafRefPath predicatePathKeyExpression,
492 final YangInstanceIdentifier current) {
493 return findParentNode(Optional.of(root), current).map(parent -> {
494 final Deque<QNameWithPredicate> path = createPath(predicatePathKeyExpression);
496 return computeValues(parent, path, null);
497 }).orElse(ImmutableSet.of());
500 private static Optional<NormalizedNode<?, ?>> findParentNode(
501 final Optional<NormalizedNode<?, ?>> root, final YangInstanceIdentifier path) {
502 Optional<NormalizedNode<?, ?>> currentNode = root;
503 final Iterator<PathArgument> pathIterator = path.getPathArguments().iterator();
504 while (pathIterator.hasNext()) {
505 final PathArgument childPathArgument = pathIterator.next();
506 if (pathIterator.hasNext() && currentNode.isPresent()) {
507 currentNode = NormalizedNodes.getDirectChild(currentNode.get(), childPathArgument);
512 return Optional.empty();
515 private static Deque<QNameWithPredicate> createPath(final LeafRefPath path) {
516 final Deque<QNameWithPredicate> ret = new ArrayDeque<>();
517 path.getPathTowardsRoot().forEach(ret::push);