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 var operation = metadata.get(OPERATION_ATTRIBUTE);
83 if (operation instanceof String str) {
84 currentAction = ModifyAction.ofXmlValue(str);
85 } else if (operation != null) {
86 throw new IllegalStateException("Unexpected operation attribute value " + operation);
88 writer.metadata(filterMeta(metadata));
91 private static ImmutableMap<QName, Object> filterMeta(final ImmutableMap<QName, Object> metadata) {
92 // FIXME: also remove prefixed attributes?
93 return ImmutableMap.copyOf(Maps.filterKeys(metadata,
94 key -> !XmlParserStream.LEGACY_ATTRIBUTE_NAMESPACE.equals(key.getModule())));
98 public void startLeafNode(final NodeIdentifier name) throws IOException {
99 writer.startLeafNode(name);
104 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
105 writer.startLeafSet(name, childSizeHint);
110 public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
111 writer.startOrderedLeafSet(name, childSizeHint);
116 public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
117 writer.startLeafSetEntryNode(name);
122 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
123 writer.startContainerNode(name, childSizeHint);
128 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
129 writer.startUnkeyedList(name, childSizeHint);
134 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
135 writer.startUnkeyedListItem(name, childSizeHint);
140 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
141 writer.startMapNode(name, childSizeHint);
146 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
148 writer.startMapEntryNode(identifier, childSizeHint);
149 pushPath(identifier);
153 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
154 writer.startOrderedMapNode(name, childSizeHint);
159 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
160 writer.startChoiceNode(name, childSizeHint);
165 public void startAugmentationNode(final AugmentationIdentifier identifier) throws IOException {
166 writer.startAugmentationNode(identifier);
167 pushPath(identifier);
172 public boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
173 // FIXME: add anydata support
178 public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
179 final boolean ret = writer.startAnyxmlNode(name, objectModel);
187 public void endNode() throws IOException {
188 final var prevAction = actions.peek();
189 if (prevAction != null) {
190 // We only split out a builder if we a changing action relative to parent and we are not inside
191 // a remove/delete operation
192 if (prevAction != currentAction && deleteDepth == 0) {
193 dataTreeChanges.add(new DataTreeChange(writer.build(), currentAction, currentPath));
199 // All done, special-cased
200 LOG.debug("All done ... writer {}", writer);
202 dataTreeChanges.add(new DataTreeChange(result.getResult(), currentAction, currentPath));
207 public void domSourceValue(final DOMSource value) throws IOException {
208 writer.domSourceValue(value);
212 public void scalarValue(@NonNull final Object value) throws IOException {
213 writer.scalarValue(value);
217 public void close() throws IOException {
218 checkState(currentPath.isEmpty(), "Cannot close with %s", currentPath);
223 public void flush() throws IOException {
227 private boolean atRemoval() {
228 return currentAction == ModifyAction.DELETE || currentAction == ModifyAction.REMOVE;
231 private void popPath() {
233 currentAction = actions.pop();
235 checkState(deleteDepth > 0);
240 private void pushPath(final PathArgument pathArgument) {
241 if (currentAction != null) {
242 // Nested element: inherit previous action and track number of REMOVE/DELETE operations in the stack
246 actions.push(currentAction);
248 // Top-level element: set the default action
249 currentAction = defaultAction;
251 currentPath.push(pathArgument);