Bump upstreams
[netconf.git] / plugins / netconf-server-mdsal / src / main / java / org / opendaylight / netconf / server / mdsal / operations / SplittingNormalizedNodeMetadataStreamWriter.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, 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.netconf.server.mdsal.operations;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.Maps;
15 import java.io.IOException;
16 import java.util.ArrayDeque;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Deque;
20 import java.util.List;
21 import javax.xml.transform.dom.DOMSource;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.netconf.api.EffectiveOperation;
24 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfigInput;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
32 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.MetadataExtension;
33 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
34 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 final class SplittingNormalizedNodeMetadataStreamWriter implements NormalizedNodeStreamWriter, MetadataExtension {
39     private static final Logger LOG = LoggerFactory.getLogger(SplittingNormalizedNodeMetadataStreamWriter.class);
40     private static final QName OPERATION_ATTRIBUTE = QName.create(EditConfigInput.QNAME.getNamespace(),
41         XmlNetconfConstants.OPERATION_ATTR_KEY);
42
43     // Top-level result node
44     private final NormalizationResultHolder result = new NormalizationResultHolder();
45     // Split-out changes
46     private final List<DataTreeChange> dataTreeChanges = new ArrayList<>();
47     // Path of the node we are currently in
48     private final Deque<PathArgument> currentPath = new ArrayDeque<>();
49     // Stack of parent changes.
50     private final Deque<EffectiveOperation> actions = new ArrayDeque<>();
51     // Stack of stashed writers which have been split out
52     private final EffectiveOperation defaultAction;
53     // Backing writer
54     private final ComponentNormalizedNodeStreamWriter writer;
55
56     // Current action, populated to default action on entry
57     private EffectiveOperation currentAction;
58
59     // Tracks the number of delete operations in actions
60     private int deleteDepth;
61
62     SplittingNormalizedNodeMetadataStreamWriter(final EffectiveOperation defaultAction) {
63         this.defaultAction = requireNonNull(defaultAction);
64         writer = new ComponentNormalizedNodeStreamWriter(result);
65     }
66
67     List<DataTreeChange> getDataTreeChanges() {
68         return dataTreeChanges;
69     }
70
71     @Override
72     public Collection<? extends Extension> supportedExtensions() {
73         return List.of(this);
74     }
75
76     @Override
77     public void metadata(final ImmutableMap<QName, Object> metadata) throws IOException {
78         final var operation = metadata.get(OPERATION_ATTRIBUTE);
79         if (operation instanceof String str) {
80             currentAction = EffectiveOperation.ofXmlValue(str);
81         } else if (operation != null) {
82             throw new IllegalStateException("Unexpected operation attribute value " + operation);
83         }
84         writer.metadata(filterMeta(metadata));
85     }
86
87     private static ImmutableMap<QName, Object> filterMeta(final ImmutableMap<QName, Object> metadata) {
88         // FIXME: also remove prefixed attributes?
89         return ImmutableMap.copyOf(Maps.filterKeys(metadata,
90             key -> !XmlParserStream.LEGACY_ATTRIBUTE_NAMESPACE.equals(key.getModule())));
91     }
92
93     @Override
94     public void startLeafNode(final NodeIdentifier name) throws IOException {
95         writer.startLeafNode(name);
96         pushPath(name);
97     }
98
99     @Override
100     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
101         writer.startLeafSet(name, childSizeHint);
102         pushPath(name);
103     }
104
105     @Override
106     public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
107         writer.startOrderedLeafSet(name, childSizeHint);
108         pushPath(name);
109     }
110
111     @Override
112     public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
113         writer.startLeafSetEntryNode(name);
114         pushPath(name);
115     }
116
117     @Override
118     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
119         writer.startContainerNode(name, childSizeHint);
120         pushPath(name);
121     }
122
123     @Override
124     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
125         writer.startUnkeyedList(name, childSizeHint);
126         pushPath(name);
127     }
128
129     @Override
130     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
131         writer.startUnkeyedListItem(name, childSizeHint);
132         pushPath(name);
133     }
134
135     @Override
136     public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
137         writer.startMapNode(name, childSizeHint);
138         pushPath(name);
139     }
140
141     @Override
142     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
143             throws IOException {
144         writer.startMapEntryNode(identifier, childSizeHint);
145         pushPath(identifier);
146     }
147
148     @Override
149     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
150         writer.startOrderedMapNode(name, childSizeHint);
151         pushPath(name);
152     }
153
154     @Override
155     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
156         writer.startChoiceNode(name, childSizeHint);
157         pushPath(name);
158     }
159
160     @Override
161     public boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
162         // FIXME: add anydata support
163         return false;
164     }
165
166     @Override
167     public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
168         final boolean ret = writer.startAnyxmlNode(name, objectModel);
169         if (ret) {
170             pushPath(name);
171         }
172         return ret;
173     }
174
175     @Override
176     public void endNode() throws IOException {
177         final var prevAction = actions.peek();
178         if (prevAction != null) {
179             // We only split out a builder if we a changing action relative to parent and we are not inside
180             // a remove/delete operation
181             if (prevAction != currentAction && deleteDepth == 0) {
182                 dataTreeChanges.add(new DataTreeChange(writer.build(), currentAction, currentPath));
183             } else {
184                 writer.endNode();
185             }
186             popPath();
187         } else {
188             // All done, special-cased
189             LOG.debug("All done ... writer {}", writer);
190             writer.endNode();
191             dataTreeChanges.add(new DataTreeChange(result.getResult().data(), currentAction, currentPath));
192         }
193     }
194
195     @Override
196     public void domSourceValue(final DOMSource value) throws IOException {
197         writer.domSourceValue(value);
198     }
199
200     @Override
201     public void scalarValue(@NonNull final Object value) throws IOException {
202         writer.scalarValue(value);
203     }
204
205     @Override
206     public void close() throws IOException {
207         checkState(currentPath.isEmpty(), "Cannot close with %s", currentPath);
208         writer.close();
209     }
210
211     @Override
212     public void flush() throws IOException {
213         writer.flush();
214     }
215
216     private boolean atRemoval() {
217         return currentAction == EffectiveOperation.DELETE || currentAction == EffectiveOperation.REMOVE;
218     }
219
220     private void popPath() {
221         currentPath.pop();
222         currentAction = actions.pop();
223         if (atRemoval()) {
224             checkState(deleteDepth > 0);
225             deleteDepth--;
226         }
227     }
228
229     private void pushPath(final PathArgument pathArgument) {
230         if (currentAction != null) {
231             // Nested element: inherit previous action and track number of REMOVE/DELETE operations in the stack
232             if (atRemoval()) {
233                 deleteDepth++;
234             }
235             actions.push(currentAction);
236         } else {
237             // Top-level element: set the default action
238             currentAction = defaultAction;
239         }
240         currentPath.push(pathArgument);
241     }
242 }