2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. 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.netconf.mdsal.connector.ops;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
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;
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);
47 // Top-level result node
48 private final NormalizedNodeMetadataResult result = new NormalizedNodeMetadataResult();
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;
58 private final ComponentNormalizedNodeStreamWriter writer;
60 // Current action, populated to default action on entry
61 private ModifyAction currentAction;
63 // Tracks the number of delete operations in actions
64 private int deleteDepth;
66 SplittingNormalizedNodeMetadataStreamWriter(final ModifyAction defaultAction) {
67 this.defaultAction = requireNonNull(defaultAction);
68 writer = new ComponentNormalizedNodeStreamWriter(result);
71 List<DataTreeChange> getDataTreeChanges() {
72 return dataTreeChanges;
76 public ClassToInstanceMap<NormalizedNodeStreamWriterExtension> getExtensions() {
77 return ImmutableClassToInstanceMap.of(StreamWriterMetadataExtension.class, this);
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;
89 writer.metadata(filterMeta(metadata));
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())));
99 public void startLeafNode(final NodeIdentifier name) throws IOException {
100 writer.startLeafNode(name);
105 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
106 writer.startLeafSet(name, childSizeHint);
111 public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
112 writer.startOrderedLeafSet(name, childSizeHint);
117 public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
118 writer.startLeafSetEntryNode(name);
123 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
124 writer.startContainerNode(name, childSizeHint);
129 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
130 writer.startUnkeyedList(name, childSizeHint);
135 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
136 writer.startUnkeyedListItem(name, childSizeHint);
141 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
142 writer.startMapNode(name, childSizeHint);
147 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
149 writer.startMapEntryNode(identifier, childSizeHint);
150 pushPath(identifier);
154 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
155 writer.startOrderedMapNode(name, childSizeHint);
160 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
161 writer.startChoiceNode(name, childSizeHint);
166 public void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
167 writer.startAugmentationNode(identifier);
168 pushPath(identifier);
173 public boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
174 // FIXME: add anydata support
179 public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
180 final boolean ret = writer.startAnyxmlNode(name, objectModel);
188 public void startYangModeledAnyXmlNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
189 writer.startYangModeledAnyXmlNode(name, childSizeHint);
194 public void endNode() throws IOException {
195 final ModifyAction prevAction = actions.peek();
196 if (prevAction != null) {
197 // We only split out a builder if we a changing action relative to parent and we are not inside
198 // a remove/delete operation
199 if (prevAction != currentAction && deleteDepth == 0) {
200 dataTreeChanges.add(new DataTreeChange(writer.build(), currentAction, currentPath));
206 // All done, special-cased
207 LOG.debug("All done ... writer {}", writer);
209 dataTreeChanges.add(new DataTreeChange(result.getResult(), currentAction, currentPath));
214 public void domSourceValue(final DOMSource value) throws IOException {
215 writer.domSourceValue(value);
219 public void scalarValue(@NonNull final Object value) throws IOException {
220 writer.scalarValue(value);
224 public void close() throws IOException {
225 checkState(currentPath.isEmpty(), "Cannot close with %s", currentPath);
230 public void flush() throws IOException {
234 private boolean atRemoval() {
235 return currentAction == ModifyAction.DELETE || currentAction == ModifyAction.REMOVE;
238 private void popPath() {
240 currentAction = actions.pop();
242 checkState(deleteDepth > 0);
247 private void pushPath(final PathArgument pathArgument) {
248 if (currentAction != null) {
249 // Nested element: inherit previous action and track number of REMOVE/DELETE operations in the stack
253 actions.push(currentAction);
255 // Top-level element: set the default action
256 currentAction = defaultAction;
258 currentPath.push(pathArgument);