Fix automatic lifecycle delete stacking
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / schema / tree / AutomaticLifecycleMixin.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. 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.schema.tree;
9
10 import java.util.Optional;
11 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
12 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
13 import org.opendaylight.yangtools.yang.data.api.schema.OrderedNodeContainer;
14 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
15 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
16 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
17 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNodeFactory;
18 import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.Version;
19
20 /**
21  * Mixin-type support class for subclasses of {@link ModificationApplyOperation} which need to provide automatic
22  * lifecycle management.
23  */
24 final class AutomaticLifecycleMixin {
25     /**
26      * This is a capture of {@link ModificationApplyOperation#apply(ModifiedNode, Optional, Version)}.
27      */
28     @FunctionalInterface
29     interface Apply {
30         Optional<TreeNode> apply(ModifiedNode modification, Optional<TreeNode> storeMeta, Version version);
31     }
32
33     /**
34      * This is a capture of
35      * {@link SchemaAwareApplyOperation#applyWrite(ModifiedNode, NormalizedNode, Optional, Version)}.
36      */
37     @FunctionalInterface
38     interface ApplyWrite {
39         TreeNode applyWrite(ModifiedNode modification, NormalizedNode<?, ?> newValue, Optional<TreeNode> storeMeta,
40                 Version version);
41     }
42
43     /**
44      * This is a capture of
45      * {@link ModificationApplyOperation#checkApplicable(ModificationPath, NodeModification, Optional, Version)}.
46      */
47     @FunctionalInterface
48     interface CheckApplicable {
49         void checkApplicable(ModificationPath path, NodeModification modification, Optional<TreeNode> current,
50                 Version version) throws DataValidationFailedException;
51     }
52
53     /**
54      * Fake TreeNode version used in
55      * {@link #checkApplicable(ModificationPath, NodeModification, Optional, Version)}.
56      * It is okay to use a global constant, as the delegate will ignore it anyway. For
57      * {@link #apply(ModifiedNode, Optional, Version)} we will use the appropriate version as provided to us.
58      */
59     private static final Version FAKE_VERSION = Version.initial();
60
61     private AutomaticLifecycleMixin() {
62
63     }
64
65     static Optional<TreeNode> apply(final Apply delegate, final ApplyWrite writeDelegate,
66             final NormalizedNode<?, ?> emptyNode, final ModifiedNode modification, final Optional<TreeNode> storeMeta,
67             final Version version) {
68         // The only way a tree node can disappear is through delete (which we handle here explicitly) or through
69         // actions of disappearResult(). It is therefore safe to perform Optional.get() on the results of
70         // delegate.apply()
71         final TreeNode ret;
72         if (modification.getOperation() == LogicalOperation.DELETE) {
73             if (modification.getChildren().isEmpty()) {
74                 return delegate.apply(modification, storeMeta, version);
75             }
76             // Delete with children, implies it really is an empty write
77             ret = writeDelegate.applyWrite(modification, emptyNode, storeMeta, version);
78         } else if (modification.getOperation() == LogicalOperation.TOUCH && !storeMeta.isPresent()) {
79             ret = applyTouch(delegate, emptyNode, modification, storeMeta, version);
80         } else {
81             // No special handling required here, run normal apply operation
82             ret = delegate.apply(modification, storeMeta, version).get();
83         }
84
85         return disappearResult(modification, ret, storeMeta);
86     }
87
88     static void checkApplicable(final CheckApplicable delegate, final NormalizedNode<?, ?> emptyNode,
89             final ModificationPath path, final NodeModification modification, final Optional<TreeNode> current,
90             final Version version) throws DataValidationFailedException {
91         if (modification.getOperation() == LogicalOperation.TOUCH && !current.isPresent()) {
92             // Structural containers are created as needed, so we pretend this container is here
93             delegate.checkApplicable(path, modification, fakeMeta(emptyNode, FAKE_VERSION), version);
94         } else {
95             delegate.checkApplicable(path, modification, current, version);
96         }
97     }
98
99     private static TreeNode applyTouch(final Apply delegate, final NormalizedNode<?, ?> emptyNode,
100             final ModifiedNode modification, final Optional<TreeNode> storeMeta, final Version version) {
101         // Container is not present, let's take care of the 'magically appear' part of our job
102         final Optional<TreeNode> ret = delegate.apply(modification, fakeMeta(emptyNode, version), version);
103
104         // If the delegate indicated SUBTREE_MODIFIED, account for the fake and report APPEARED
105         if (modification.getModificationType() == ModificationType.SUBTREE_MODIFIED) {
106             modification.resolveModificationType(ModificationType.APPEARED);
107         }
108         return ret.get();
109     }
110
111     private static Optional<TreeNode> disappearResult(final ModifiedNode modification, final TreeNode result,
112             final Optional<TreeNode> storeMeta) {
113         // Check if the result is in fact empty before pulling any tricks
114         if (!isEmpty(result)) {
115             return Optional.of(result);
116         }
117
118         // We are pulling the 'disappear' trick, but what we report can be three different things
119         final ModificationType finalType;
120         if (!storeMeta.isPresent()) {
121             // ... there was nothing in the datastore, no change
122             finalType = ModificationType.UNMODIFIED;
123         } else if (modification.getModificationType() == ModificationType.WRITE) {
124             // ... this was an empty write, possibly originally a delete
125             finalType = ModificationType.DELETE;
126         } else {
127             // ... it really disappeared
128             finalType = ModificationType.DISAPPEARED;
129         }
130         modification.resolveModificationType(finalType);
131         return Optional.empty();
132     }
133
134     private static Optional<TreeNode> fakeMeta(final NormalizedNode<?, ?> emptyNode, final Version version) {
135         return Optional.of(TreeNodeFactory.createTreeNode(emptyNode, version));
136     }
137
138     private static boolean isEmpty(final TreeNode treeNode) {
139         final NormalizedNode<?, ?> data = treeNode.getData();
140         if (data instanceof NormalizedNodeContainer) {
141             return ((NormalizedNodeContainer<?, ?, ?>) data).getValue().isEmpty();
142         }
143         if (data instanceof OrderedNodeContainer) {
144             return ((OrderedNodeContainer<?>) data).getSize() == 0;
145         }
146         throw new IllegalStateException("Unhandled data " + data);
147     }
148 }