Pass down predicates instead of the entire predicate node
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / leafref / LeafRefValidatation.java
1 /*
2  * Copyright (c) 2015 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.yangtools.yang.data.impl.leafref;
9
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;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.Optional;
22 import java.util.Set;
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;
45
46 // FIXME: 3.0.0: Rename to LeafRefValidation
47 public final class LeafRefValidatation {
48
49     private static final Logger LOG = LoggerFactory.getLogger(LeafRefValidatation.class);
50     private static final String FAILED = " -> FAILED";
51     private static final String SUCCESS = " -> OK";
52
53     private final Set<LeafRefContext> validatedLeafRefCtx = new HashSet<>();
54     private final List<String> errorsMessages = new ArrayList<>();
55     private final NormalizedNode<?, ?> root;
56
57     private LeafRefValidatation(final NormalizedNode<?, ?> root) {
58         this.root = root;
59     }
60
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());
66         }
67     }
68
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();
75
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);
82                 }
83             }
84         }
85
86         if (!errorsMessages.isEmpty()) {
87             final StringBuilder message = new StringBuilder();
88             int errCount = 0;
89             for (final String errorMessage : errorsMessages) {
90                 message.append(errorMessage);
91                 errCount++;
92             }
93             throw new LeafRefDataValidationFailedException(message.toString(), errCount);
94         }
95     }
96
97     private void validateNode(final DataTreeCandidateNode node, final LeafRefContext referencedByCtx,
98         final LeafRefContext referencingCtx, final YangInstanceIdentifier current) {
99
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);
105             return;
106         }
107
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);
113             return;
114         }
115
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);
121
122                 if (childReferencedByCtx != null || childReferencingCtx != null) {
123                     final YangInstanceIdentifier childYangInstanceIdentifier = current.node(childNode.getIdentifier());
124                     validateNode(childNode, childReferencedByCtx,childReferencingCtx, childYangInstanceIdentifier);
125                 }
126             }
127         }
128     }
129
130     private static LeafRefContext getReferencingCtxChild(final LeafRefContext referencingCtx,
131             final DataTreeCandidateNode childNode) {
132         if (referencingCtx == null) {
133             return null;
134         }
135
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;
142             }
143         }
144
145         return childReferencingCtx;
146     }
147
148     private static LeafRefContext getReferencedByCtxChild(final LeafRefContext referencedByCtx,
149             final DataTreeCandidateNode childNode) {
150         if (referencedByCtx == null) {
151             return null;
152         }
153
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;
160             }
161         }
162
163         return childReferencedByCtx;
164     }
165
166     private void validateNodeData(final NormalizedNode<?, ?> node, final LeafRefContext referencedByCtx,
167             final LeafRefContext referencingCtx, final ModificationType modificationType,
168             final YangInstanceIdentifier current) {
169
170         if (node instanceof LeafNode) {
171             final LeafNode<?> leaf = (LeafNode<?>) node;
172
173             if (referencedByCtx != null && referencedByCtx.isReferenced()) {
174                 validateLeafRefTargetNodeData(leaf, referencedByCtx, modificationType);
175             }
176             if (referencingCtx != null && referencingCtx.isReferencing()) {
177                 validateLeafRefNodeData(leaf, referencingCtx, modificationType, current);
178             }
179
180             return;
181         }
182
183         if (node instanceof LeafSetNode) {
184             if (referencedByCtx == null && referencingCtx == null) {
185                 return;
186             }
187
188             final LeafSetNode<?> leafSet = (LeafSetNode<?>) node;
189             for (final NormalizedNode<?, ?> leafSetEntry : leafSet.getValue()) {
190                 if (referencedByCtx != null && referencedByCtx.isReferenced()) {
191                     validateLeafRefTargetNodeData(leafSetEntry, referencedByCtx, modificationType);
192                 }
193                 if (referencingCtx != null && referencingCtx.isReferencing()) {
194                     validateLeafRefNodeData(leafSetEntry, referencingCtx, modificationType, current);
195                 }
196             }
197
198             return;
199         }
200
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();
205
206                 final LeafRefContext childReferencedByCtx;
207                 if (referencedByCtx != null) {
208                     childReferencedByCtx = findReferencedByCtxUnderChoice(referencedByCtx, qname);
209                 } else {
210                     childReferencedByCtx = null;
211                 }
212
213                 final LeafRefContext childReferencingCtx;
214                 if (referencingCtx != null) {
215                     childReferencingCtx = findReferencingCtxUnderChoice(referencingCtx, qname);
216                 } else {
217                     childReferencingCtx = null;
218                 }
219
220                 if (childReferencedByCtx != null || childReferencingCtx != null) {
221                     final YangInstanceIdentifier childYangInstanceIdentifier = current
222                             .node(dataContainerChild.getIdentifier());
223                     validateNodeData(dataContainerChild, childReferencedByCtx,
224                             childReferencingCtx, modificationType, childYangInstanceIdentifier);
225                 }
226             }
227         } else if (node instanceof DataContainerNode) {
228             final DataContainerNode<?> dataContainerNode = (DataContainerNode<?>) node;
229
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()));
234                     return;
235                 }
236
237                 final QName qname = child.getNodeType();
238                 final LeafRefContext childReferencedByCtx;
239                 if (referencedByCtx != null) {
240                     childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
241                 } else {
242                     childReferencedByCtx = null;
243                 }
244
245                 final LeafRefContext childReferencingCtx;
246                 if (referencingCtx != null) {
247                     childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
248                 } else {
249                     childReferencingCtx = null;
250                 }
251
252                 if (childReferencedByCtx != null || childReferencingCtx != null) {
253                     final YangInstanceIdentifier childYangInstanceIdentifier = current
254                             .node(child.getIdentifier());
255                     validateNodeData(child, childReferencedByCtx,
256                             childReferencingCtx, modificationType, childYangInstanceIdentifier);
257                 }
258             }
259         } else if (node instanceof MapNode) {
260             final MapNode map = (MapNode) node;
261
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()));
268                         return;
269                     }
270
271                     final QName qname = mapEntryNode.getNodeType();
272                     final LeafRefContext childReferencedByCtx;
273                     if (referencedByCtx != null) {
274                         childReferencedByCtx = referencedByCtx.getReferencedChildByName(qname);
275                     } else {
276                         childReferencedByCtx = null;
277                     }
278
279                     final LeafRefContext childReferencingCtx;
280                     if (referencingCtx != null) {
281                         childReferencingCtx = referencingCtx.getReferencingChildByName(qname);
282                     } else {
283                         childReferencingCtx = null;
284                     }
285
286                     if (childReferencedByCtx != null || childReferencingCtx != null) {
287                         validateNodeData(mapEntryNode, childReferencedByCtx, childReferencingCtx, modificationType,
288                                 mapEntryYangInstanceIdentifier.node(mapEntryNode.getIdentifier()));
289                     }
290                 }
291             }
292         }
293         // FIXME: check UnkeyedListNode case
294     }
295
296     private static LeafRefContext findReferencingCtxUnderChoice(
297             final LeafRefContext referencingCtx, final QName qname) {
298
299         for (final LeafRefContext child : referencingCtx.getReferencingChilds().values()) {
300             final LeafRefContext referencingChildByName = child.getReferencingChildByName(qname);
301             if (referencingChildByName != null) {
302                 return referencingChildByName;
303             }
304         }
305
306         return null;
307     }
308
309     private static LeafRefContext findReferencedByCtxUnderChoice(
310             final LeafRefContext referencedByCtx, final QName qname) {
311
312         for (final LeafRefContext child : referencedByCtx.getReferencedByChilds().values()) {
313             final LeafRefContext referencedByChildByName = child.getReferencedChildByName(qname);
314             if (referencedByChildByName != null) {
315                 return referencedByChildByName;
316             }
317         }
318
319         return null;
320     }
321
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());
327             return;
328         }
329
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()) {
335             return;
336         }
337
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);
343                     return;
344                 }
345
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(),
354                         FAILED));
355             });
356         });
357     }
358
359     private Set<Object> extractRootValues(final LeafRefContext context) {
360         return computeValues(root, context.getLeafRefNodePath().getPathFromRoot(), null);
361     }
362
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(),
366             current);
367         if (values.contains(leaf.getValue())) {
368             LOG.debug("Operation [{}] validate data of LEAFREF node: name[{}] = value[{}] {}", modificationType,
369                 referencingCtx.getNodeName(), leaf.getValue(), SUCCESS);
370             return;
371         }
372
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()));
380     }
381
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);
386         return values;
387     }
388
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());
394             return;
395         }
396         if (node instanceof LeafSetNode<?>) {
397             for (final NormalizedNode<?, ?> entry : ((LeafSetNode<?>) node).getValue()) {
398                 values.add(entry.getValue());
399             }
400             return;
401         }
402
403         final Iterator<QNameWithPredicate> iterator = path.iterator();
404         if (!iterator.hasNext()) {
405             return;
406         }
407
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);
415
416             if (child.isPresent()) {
417                 addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
418             } else {
419                 for (final ChoiceNode choiceNode : getChoiceNodes(dataContainerNode)) {
420                     addValues(values, choiceNode, next.getQNamePredicates(), path, current);
421                 }
422             }
423
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);
431
432                     if (child.isPresent()) {
433                         addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
434                     } else {
435                         for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
436                             addValues(values, choiceNode, next.getQNamePredicates(), path, current);
437                         }
438                     }
439                 }
440             } else {
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);
446
447                     keyValues.put(identifier, pathKeyExprValues);
448                 }
449
450                 for (final MapEntryNode mapEntryNode : map.getValue()) {
451                     if (isMatchingPredicate(mapEntryNode, keyValues)) {
452                         final Optional<DataContainerChild<? extends PathArgument, ?>> child = mapEntryNode
453                                 .getChild(pathArgument);
454
455                         if (child.isPresent()) {
456                             addValues(values, child.get(), next.getQNamePredicates(), nextLevel(path), current);
457                         } else {
458                             for (final ChoiceNode choiceNode : getChoiceNodes(mapEntryNode)) {
459                                 addValues(values, choiceNode,  next.getQNamePredicates(), path, current);
460                             }
461                         }
462                     }
463                 }
464             }
465         }
466     }
467
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);
473             }
474         }
475         return choiceNodes;
476     }
477
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())) {
483                 return false;
484             }
485         }
486
487         return true;
488     }
489
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());
495     }
496
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);
505             } else {
506                 return currentNode;
507             }
508         }
509         return Optional.empty();
510     }
511
512     private static Iterable<QNameWithPredicate> nextLevel(final Iterable<QNameWithPredicate> path) {
513         return Iterables.skip(path, 1);
514     }
515 }