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.server.mdsal.operations;
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
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;
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);
43 // Top-level result node
44 private final NormalizationResultHolder result = new NormalizationResultHolder();
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;
54 private final ComponentNormalizedNodeStreamWriter writer;
56 // Current action, populated to default action on entry
57 private EffectiveOperation currentAction;
59 // Tracks the number of delete operations in actions
60 private int deleteDepth;
62 SplittingNormalizedNodeMetadataStreamWriter(final EffectiveOperation defaultAction) {
63 this.defaultAction = requireNonNull(defaultAction);
64 writer = new ComponentNormalizedNodeStreamWriter(result);
67 List<DataTreeChange> getDataTreeChanges() {
68 return dataTreeChanges;
72 public Collection<? extends Extension> supportedExtensions() {
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);
84 writer.metadata(filterMeta(metadata));
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())));
94 public void startLeafNode(final NodeIdentifier name) throws IOException {
95 writer.startLeafNode(name);
100 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
101 writer.startLeafSet(name, childSizeHint);
106 public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
107 writer.startOrderedLeafSet(name, childSizeHint);
112 public void startLeafSetEntryNode(final NodeWithValue<?> name) throws IOException {
113 writer.startLeafSetEntryNode(name);
118 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
119 writer.startContainerNode(name, childSizeHint);
124 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
125 writer.startUnkeyedList(name, childSizeHint);
130 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
131 writer.startUnkeyedListItem(name, childSizeHint);
136 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
137 writer.startMapNode(name, childSizeHint);
142 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
144 writer.startMapEntryNode(identifier, childSizeHint);
145 pushPath(identifier);
149 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
150 writer.startOrderedMapNode(name, childSizeHint);
155 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
156 writer.startChoiceNode(name, childSizeHint);
161 public boolean startAnydataNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
162 // FIXME: add anydata support
167 public boolean startAnyxmlNode(final NodeIdentifier name, final Class<?> objectModel) throws IOException {
168 final boolean ret = writer.startAnyxmlNode(name, objectModel);
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));
188 // All done, special-cased
189 LOG.debug("All done ... writer {}", writer);
191 dataTreeChanges.add(new DataTreeChange(result.getResult().data(), currentAction, currentPath));
196 public void domSourceValue(final DOMSource value) throws IOException {
197 writer.domSourceValue(value);
201 public void scalarValue(@NonNull final Object value) throws IOException {
202 writer.scalarValue(value);
206 public void close() throws IOException {
207 checkState(currentPath.isEmpty(), "Cannot close with %s", currentPath);
212 public void flush() throws IOException {
216 private boolean atRemoval() {
217 return currentAction == EffectiveOperation.DELETE || currentAction == EffectiveOperation.REMOVE;
220 private void popPath() {
222 currentAction = actions.pop();
224 checkState(deleteDepth > 0);
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
235 actions.push(currentAction);
237 // Top-level element: set the default action
238 currentAction = defaultAction;
240 currentPath.push(pathArgument);