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