Clean up ModifyAction 69/103669/1
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 14 Dec 2022 01:55:59 +0000 (02:55 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 14 Dec 2022 02:37:37 +0000 (03:37 +0100)
Document ModifyAction and modernize it. This allows a number of users to
rely on new interfaces, eliminating interactions with Locale.

Change-Id: If471c823e38c363e06a69cfd05923cdcb2f68a1a
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/Datastore.java
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/EditConfig.java
netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/SplittingNormalizedNodeMetadataStreamWriter.java
netconf/netconf-api/src/main/java/org/opendaylight/netconf/api/ModifyAction.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/SchemalessRpcStructureTransformer.java

index 4a8878cd5e9778716eb3acc966b52eb265da2812..671fadfc3bfd07bfb79acf4cf20c4928e8bdd646 100644 (file)
@@ -5,9 +5,9 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.netconf.mdsal.connector.ops;
 
+// FIXME: document, rename to LegacyDatastore (as per NMDA), add XML interaction values
 public enum Datastore {
     candidate, running
 }
index 1156c99ff223182687d28aba2c9efdc41de92bf8..8d43838c567871f9505837e3792208138412ff21 100644 (file)
@@ -7,13 +7,13 @@
  */
 package org.opendaylight.netconf.mdsal.connector.ops;
 
-import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
 
 import java.util.List;
-import java.util.ListIterator;
 import java.util.concurrent.ExecutionException;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
 import org.opendaylight.netconf.api.DocumentedException;
 import org.opendaylight.netconf.api.ModifyAction;
 import org.opendaylight.netconf.api.xml.XmlElement;
@@ -24,51 +24,47 @@ import org.opendaylight.yangtools.yang.common.ErrorSeverity;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
 
 public final class EditConfig extends AbstractEdit {
-
     private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
-
     private static final String OPERATION_NAME = "edit-config";
-    private static final String DEFAULT_OPERATION_KEY = "default-operation";
+    private static final String DEFAULT_OPERATION = "default-operation";
+
     private final TransactionProvider transactionProvider;
 
     public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
             final TransactionProvider transactionProvider) {
         super(netconfSessionIdForReporting, schemaContext);
-        this.transactionProvider = transactionProvider;
+        this.transactionProvider = requireNonNull(transactionProvider);
+    }
+
+    @Override
+    protected String getOperationName() {
+        return OPERATION_NAME;
     }
 
     @Override
     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
             throws DocumentedException {
-        final XmlElement targetElement = extractTargetElement(operationElement, OPERATION_NAME);
-        final Datastore targetDatastore = Datastore.valueOf(targetElement.getName());
-        if (targetDatastore == Datastore.running) {
+        if (Datastore.valueOf(extractTargetElement(operationElement, OPERATION_NAME).getName()) == Datastore.running) {
             throw new DocumentedException("edit-config on running datastore is not supported",
                     ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
         }
 
-        final ModifyAction defaultAction = getDefaultOperation(operationElement);
-
-        final XmlElement configElement = getConfigElement(operationElement);
-
-        for (final XmlElement element : configElement.getChildElements()) {
-            final SplittingNormalizedNodeMetadataStreamWriter writer = new SplittingNormalizedNodeMetadataStreamWriter(
-                defaultAction);
+        final var defaultAction = getDefaultOperation(operationElement);
+        for (var element : getConfigElement(operationElement).getChildElements()) {
+            final var writer = new SplittingNormalizedNodeMetadataStreamWriter(defaultAction);
             parseIntoNormalizedNode(getSchemaNodeFromNamespace(element.getNamespace(), element), element, writer);
             executeOperations(writer.getDataTreeChanges());
         }
@@ -77,19 +73,18 @@ public final class EditConfig extends AbstractEdit {
     }
 
     private void executeOperations(final List<DataTreeChange> changes) throws DocumentedException {
-        final DOMDataTreeReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
-        final ListIterator<DataTreeChange> iterator = changes.listIterator(changes.size());
-
+        final var rwTx = transactionProvider.getOrCreateTransaction();
+        final var iterator = changes.listIterator(changes.size());
         while (iterator.hasPrevious()) {
-            final DataTreeChange dtc = iterator.previous();
-            executeChange(rwTx, dtc);
+            executeChange(rwTx, iterator.previous());
         }
     }
 
+    // FIXME: we should have proper ReadWriteOperations
     private void executeChange(final DOMDataTreeReadWriteTransaction rwtx, final DataTreeChange change)
             throws DocumentedException {
-        final YangInstanceIdentifier path = change.getPath();
-        final NormalizedNode changeData = change.getChangeRoot();
+        final var path = change.getPath();
+        final var changeData = change.getChangeRoot();
         switch (change.getAction()) {
             case NONE:
                 return;
@@ -99,6 +94,7 @@ public final class EditConfig extends AbstractEdit {
                 break;
             case CREATE:
                 try {
+                    // FIXME: synchronous operation: can we get a rwTx.create() with a per-operation result instead?
                     if (rwtx.exists(LogicalDatastoreType.CONFIGURATION, path).get()) {
                         throw new DocumentedException("Data already exists, cannot execute CREATE operation",
                             ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, ErrorSeverity.ERROR);
@@ -115,6 +111,8 @@ public final class EditConfig extends AbstractEdit {
                 break;
             case DELETE:
                 try {
+                    // FIXME: synchronous operation: can we get a rwTx.delete() semantics with a per-operation result
+                    //        instead?
                     if (!rwtx.exists(LogicalDatastoreType.CONFIGURATION, path).get()) {
                         throw new DocumentedException("Data is missing, cannot execute DELETE operation",
                             ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR);
@@ -132,60 +130,40 @@ public final class EditConfig extends AbstractEdit {
         }
     }
 
-    private void mergeParentMixin(final DOMDataTreeReadWriteTransaction rwtx, final YangInstanceIdentifier path,
+    private void mergeParentMixin(final DOMDataTreeWriteOperations rwtx, final YangInstanceIdentifier path,
                                   final NormalizedNode change) {
-        final YangInstanceIdentifier parentNodeYid = path.getParent();
+        final var parentNodeYid = path.getParent();
         if (change instanceof MapEntryNode) {
-            final DataSchemaNode schemaNode = DataSchemaContextTree.from(schemaContext.getCurrentContext())
+            final var dataSchemaNode = DataSchemaContextTree.from(schemaContext.getCurrentContext())
                 .findChild(parentNodeYid)
                 .orElseThrow(() -> new IllegalStateException("Cannot find schema for " + parentNodeYid))
                 .getDataSchemaNode();
 
             // we should have the schema node that points to the parent list now, enforce it
-            checkState(schemaNode instanceof ListSchemaNode, "Schema node is not pointing to a list.");
-
-            //merge empty ordered or unordered map
-            if (((ListSchemaNode) schemaNode).isUserOrdered()) {
-                final MapNode mixinNode = Builders.orderedMapBuilder()
-                        .withNodeIdentifier(
-                                new YangInstanceIdentifier.NodeIdentifier(
-                                        parentNodeYid.getLastPathArgument().getNodeType()))
-                        .build();
-                rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
-                return;
+            if (!(dataSchemaNode instanceof ListSchemaNode listSchemaNode)) {
+                throw new IllegalStateException("Schema node is not pointing to a list");
             }
 
-            final MapNode mixinNode = Builders.mapBuilder()
-                    .withNodeIdentifier(
-                            new YangInstanceIdentifier.NodeIdentifier(
-                                        parentNodeYid.getLastPathArgument().getNodeType()))
-                    .build();
-            rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
-        } else if (parentNodeYid.getLastPathArgument() instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+            // merge empty ordered or unordered map
+            rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid,
+                (listSchemaNode.isUserOrdered() ? Builders.orderedMapBuilder() : Builders.mapBuilder())
+                    .withNodeIdentifier(new NodeIdentifier(parentNodeYid.getLastPathArgument().getNodeType()))
+                    .build());
+        } else if (parentNodeYid.getLastPathArgument() instanceof AugmentationIdentifier augId) {
             // merge empty augmentation node
-            final YangInstanceIdentifier.AugmentationIdentifier augmentationYid =
-                (YangInstanceIdentifier.AugmentationIdentifier) parentNodeYid.getLastPathArgument();
-            final AugmentationNode augmentationNode = Builders.augmentationBuilder()
-                .withNodeIdentifier(augmentationYid).build();
-            rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, augmentationNode);
+            rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, Builders.augmentationBuilder()
+                .withNodeIdentifier(augId)
+                .build());
         }
     }
 
     private static ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
-        final NodeList elementsByTagName = getElementsByTagName(operationElement, DEFAULT_OPERATION_KEY);
-        if (elementsByTagName.getLength() == 0) {
-            return ModifyAction.MERGE;
-        } else if (elementsByTagName.getLength() > 1) {
-            throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements", ErrorType.RPC,
+        final var elementsByTagName = getElementsByTagName(operationElement, DEFAULT_OPERATION);
+        return switch (elementsByTagName.getLength()) {
+            case 0 -> ModifyAction.MERGE;
+            case 1 ->  ModifyAction.ofXmlValue(elementsByTagName.item(0).getTextContent());
+            default -> throw new DocumentedException("Multiple " + DEFAULT_OPERATION + " elements", ErrorType.RPC,
                 ErrorTag.UNKNOWN_ATTRIBUTE, ErrorSeverity.ERROR);
-        } else {
-            return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
-        }
-
-    }
-
-    @Override
-    protected String getOperationName() {
-        return OPERATION_NAME;
+        };
     }
 }
index 39c7fe7ec8a41010664647d11936aaa43ed772bf..61bbbda8df96f558ad489c1a497ae004f387ae4c 100644 (file)
@@ -79,13 +79,12 @@ final class SplittingNormalizedNodeMetadataStreamWriter implements NormalizedNod
 
     @Override
     public void metadata(final ImmutableMap<QName, Object> metadata) throws IOException {
-        final Object operation = metadata.get(OPERATION_ATTRIBUTE);
-        if (operation != null) {
-            checkState(operation instanceof String, "Unexpected operation attribute value %s", operation);
-            final ModifyAction newAction = ModifyAction.fromXmlValue((String) operation);
-            currentAction = newAction;
+        final var operation = metadata.get(OPERATION_ATTRIBUTE);
+        if (operation instanceof String str) {
+            currentAction = ModifyAction.ofXmlValue(str);
+        } else if (operation != null) {
+            throw new IllegalStateException("Unexpected operation attribute value " + operation);
         }
-
         writer.metadata(filterMeta(metadata));
     }
 
@@ -186,7 +185,7 @@ final class SplittingNormalizedNodeMetadataStreamWriter implements NormalizedNod
 
     @Override
     public void endNode() throws IOException {
-        final ModifyAction prevAction = actions.peek();
+        final var prevAction = actions.peek();
         if (prevAction != null) {
             // We only split out a builder if we a changing action relative to parent and we are not inside
             // a remove/delete operation
index 0ee9956b6023c6cc7a50b2ed26c29caedbf33b09..ca8bc1d3403aac0f7e17b4332e62e396859f2acf 100644 (file)
  */
 package org.opendaylight.netconf.api;
 
-import java.util.Arrays;
+import static java.util.Objects.requireNonNull;
 
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * NETCONF modification actions, as allowed for in {@code operation} and {@code default-operation} attributes of
+ * {@code <edit-config>} operation, as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc6241#section-7.2">RFC6241 section 7.2</a>.
+ *
+ * <p>
+ * This concept is uncharacteristically bound to two separate semantics, but for a good reason: at the end of the day we
+ * want to know what the effective operation is.
+ */
 public enum ModifyAction {
-    MERGE(true), REPLACE(true), CREATE(false), DELETE(false), REMOVE(false), NONE(true, false);
-
-    public static ModifyAction fromXmlValue(final String xmlNameOfAction) {
-        switch (xmlNameOfAction) {
-            case "merge":
-                return MERGE;
-            case "replace":
-                return REPLACE;
-            case "remove":
-                return REMOVE;
-            case "delete":
-                return DELETE;
-            case "create":
-                return CREATE;
-            case "none":
-                return NONE;
-            default:
-                throw new IllegalArgumentException("Unknown operation " + xmlNameOfAction + " available operations "
-                        + Arrays.toString(ModifyAction.values()));
-        }
-    }
+    // operation and default-operation
+    MERGE("merge",     true,  true),
+    REPLACE("replace", true,  true),
+    // operation only
+    CREATE("create",   true,  false),
+    DELETE("delete",   true,  false),
+    REMOVE("remove",   true,  false),
+
+    // default-operation-only
+    NONE("none",       false, true);
+
+    private final @NonNull String xmlValue;
+    private final boolean isDefaultOperation;
+    private final boolean isOperation;
 
-    private final boolean asDefaultPermitted;
-    private final boolean onElementPermitted;
+    ModifyAction(final String xmlValue, final boolean isOperation, final boolean isDefaultOperation) {
+        this.xmlValue = requireNonNull(xmlValue);
+        this.isDefaultOperation = isDefaultOperation;
+        this.isOperation = isOperation;
+    }
 
-    ModifyAction(final boolean asDefaultPermitted, final boolean onElementPermitted) {
-        this.asDefaultPermitted = asDefaultPermitted;
-        this.onElementPermitted = onElementPermitted;
+    /**
+     * Return the {@link ModifyAction} corresponding to a {@link #xmlValue}.
+     *
+     * @param xmlValue XML attribute or element value
+     * @return A {@link ModifyAction}
+     * @throws NullPointerException if {@code xmlValue} is {@code null}
+     * @throws IllegalArgumentException if {@code xmlValue} is not recognized
+     */
+    public static @NonNull ModifyAction ofXmlValue(final String xmlValue) {
+        return switch (xmlValue) {
+            case "merge" -> MERGE;
+            case "replace" -> REPLACE;
+            case "remove" -> REMOVE;
+            case "delete" -> DELETE;
+            case "create" -> CREATE;
+            case "none" -> NONE;
+            default -> throw new IllegalArgumentException("Unknown operation " + xmlValue);
+        };
     }
 
-    ModifyAction(final boolean asDefaultPermitted) {
-        this(asDefaultPermitted, true);
+    /**
+     * Return an XML string literal corresponding to this {@link ModifyAction}.
+     *
+     * @return An XML string literal
+     */
+    public @NonNull String xmlValue() {
+        return xmlValue;
     }
 
     /**
      * Check if this operation is a candidate for {@code default-operation} argument.
      *
-     * @return True if this operation can be used as {@code default-operation}.
+     * @return {@code true} if this operation can be used as {@code default-operation}, {@code false} otherwise.
+     * @deprecated Use {@link #isDefaultOperation()} instead
      */
+    @Deprecated(since = "5.0.0", forRemoval = true)
     public boolean isAsDefaultPermitted() {
-        return asDefaultPermitted;
+        return isDefaultOperation;
+    }
+
+    /**
+     * Check if this operation is a candidate for {@code default-operation} argument.
+     *
+     * @return {@code true} if this operation can be used as {@code default-operation}, {@code false} otherwise.
+     */
+    public boolean isDefaultOperation() {
+        return isDefaultOperation;
     }
 
+    @Deprecated(since = "5.0.0", forRemoval = true)
     public boolean isOnElementPermitted() {
-        return onElementPermitted;
+        return isOperation;
+    }
+
+    /**
+     * Check if this operation is a candidate for {@code operation} attribute.
+     *
+     * @return {@code true} if this operation can be used as {@code operation}, {@code false} otherwise.
+     */
+    public boolean isOperation() {
+        return isOperation;
     }
 }
index 45058eb17cd2945f2145c744ea8f82598b9aa99a..7f4fa6b87794b9f3d72e92490ec48e61baa9e7ce 100644 (file)
@@ -17,18 +17,13 @@ import static org.opendaylight.netconf.util.NetconfUtil.NETCONF_DATA_QNAME;
 import static org.opendaylight.netconf.util.NetconfUtil.appendListKeyNodes;
 import static org.opendaylight.netconf.util.NetconfUtil.writeSchemalessFilter;
 
-import com.google.common.base.Preconditions;
-import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
-import java.util.Map.Entry;
 import java.util.Optional;
 import javax.xml.transform.dom.DOMSource;
 import org.opendaylight.netconf.api.DocumentedException;
 import org.opendaylight.netconf.api.ModifyAction;
 import org.opendaylight.netconf.api.xml.XmlElement;
 import org.opendaylight.netconf.api.xml.XmlUtil;
-import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -37,7 +32,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -57,21 +51,21 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
     @Override
     public Optional<NormalizedNode> selectFromDataStructure(final DataContainerChild data,
             final YangInstanceIdentifier path) {
-        Preconditions.checkArgument(data instanceof DOMSourceAnyxmlNode);
-        final List<XmlElement> xmlElements = selectMatchingNodes(
-            getSourceElement(((DOMSourceAnyxmlNode)data).body()), path);
-        final Document result = XmlUtil.newDocument();
-        final Element dataElement =
-                result.createElementNS(NETCONF_DATA_QNAME.getNamespace().toString(), NETCONF_DATA_QNAME.getLocalName());
+        if (!(data instanceof DOMSourceAnyxmlNode anyxml)) {
+            throw new IllegalArgumentException("Unexpected data " + data.prettyTree());
+        }
+
+        final var result = XmlUtil.newDocument();
+        final var dataElement = result.createElementNS(NETCONF_DATA_QNAME.getNamespace().toString(),
+            NETCONF_DATA_QNAME.getLocalName());
         result.appendChild(dataElement);
-        for (XmlElement xmlElement : xmlElements) {
+        for (var xmlElement : selectMatchingNodes(getSourceElement(anyxml.body()), path)) {
             dataElement.appendChild(result.importNode(xmlElement.getDomElement(), true));
         }
-        final DOMSourceAnyxmlNode resultAnyxml = Builders.anyXmlBuilder()
-                .withNodeIdentifier(NETCONF_DATA_NODEID)
-                .withValue(new DOMSource(result))
-                .build();
-        return Optional.of(resultAnyxml);
+        return Optional.of(Builders.anyXmlBuilder()
+            .withNodeIdentifier(NETCONF_DATA_NODEID)
+            .withValue(new DOMSource(result))
+            .build());
     }
 
     /**
@@ -85,15 +79,16 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
     @Override
     public DOMSourceAnyxmlNode createEditConfigStructure(final Optional<NormalizedNode> data,
             final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation) {
-        Preconditions.checkArgument(data.isPresent());
-        Preconditions.checkArgument(data.get() instanceof DOMSourceAnyxmlNode);
+        final var dataValue = data.orElseThrow();
+        if (!(dataValue instanceof DOMSourceAnyxmlNode anxmlData)) {
+            throw new IllegalArgumentException("Unexpected data " + dataValue.prettyTree());
+        }
 
-        final DOMSourceAnyxmlNode anxmlData = (DOMSourceAnyxmlNode) data.get();
-        final Document document = XmlUtil.newDocument();
-        final Element dataNode = (Element) document.importNode(getSourceElement(anxmlData.body()), true);
+        final var document = XmlUtil.newDocument();
+        final var dataNode = (Element) document.importNode(getSourceElement(anxmlData.body()), true);
         checkDataValidForPath(dataPath, dataNode);
 
-        final Element configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
+        final var configElement = document.createElementNS(NETCONF_CONFIG_QNAME.getNamespace().toString(),
                 NETCONF_CONFIG_QNAME.getLocalName());
         document.appendChild(configElement);
 
@@ -102,17 +97,18 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
             parentXmlStructure = dataNode;
             configElement.appendChild(parentXmlStructure);
         } else {
-            final List<PathArgument> pathArguments = dataPath.getPathArguments();
-            //last will be appended later
-            final List<PathArgument> pathWithoutLast = pathArguments.subList(0, pathArguments.size() - 1);
-            parentXmlStructure = instanceIdToXmlStructure(pathWithoutLast, configElement);
+            final var pathArguments = dataPath.getPathArguments();
+            // last will be appended later
+            parentXmlStructure = instanceIdToXmlStructure(pathArguments.subList(0, pathArguments.size() - 1),
+                configElement);
         }
         operation.ifPresent(modifyAction -> setOperationAttribute(modifyAction, document, dataNode));
         //append data
         parentXmlStructure.appendChild(document.importNode(dataNode, true));
-        return Builders.anyXmlBuilder().withNodeIdentifier(NETCONF_CONFIG_NODEID)
-                .withValue(new DOMSource(document.getDocumentElement()))
-                .build();
+        return Builders.anyXmlBuilder()
+            .withNodeIdentifier(NETCONF_CONFIG_NODEID)
+            .withValue(new DOMSource(document.getDocumentElement()))
+            .build();
     }
 
     /**
@@ -123,37 +119,37 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
      */
     @Override
     public AnyxmlNode<?> toFilterStructure(final YangInstanceIdentifier path) {
-        final Document document = XmlUtil.newDocument();
-        final Element filterElement = prepareFilterElement(document);
-        instanceIdToXmlStructure(path.getPathArguments(), filterElement);
+        final var document = XmlUtil.newDocument();
+        instanceIdToXmlStructure(path.getPathArguments(), prepareFilterElement(document));
         return buildFilterXmlNode(document);
     }
 
     @Override
     public AnyxmlNode<?> toFilterStructure(final List<FieldsFilter> fieldsFilters) {
-        final Document document = XmlUtil.newDocument();
-        final Element filterElement = prepareFilterElement(document);
-        for (final FieldsFilter filter : fieldsFilters) {
+        final var document = XmlUtil.newDocument();
+        final var filterElement = prepareFilterElement(document);
+        for (var filter : fieldsFilters) {
             writeSchemalessFilter(filter.path(), filter.fields(), filterElement);
         }
         return buildFilterXmlNode(document);
     }
 
     private static Element prepareFilterElement(final Document document) {
-        final String filterNs = NETCONF_FILTER_QNAME.getNamespace().toString();
-        final Element filter = document.createElementNS(filterNs, NETCONF_FILTER_QNAME.getLocalName());
-        final Attr a = document.createAttributeNS(filterNs, "type");
-        a.setTextContent("subtree");
-        filter.setAttributeNode(a);
+        // FIXME: use a constant
+        final var filterNs = NETCONF_FILTER_QNAME.getNamespace().toString();
+        final var filter = document.createElementNS(filterNs, NETCONF_FILTER_QNAME.getLocalName());
+        final var attr = document.createAttributeNS(filterNs, "type");
+        attr.setTextContent("subtree");
+        filter.setAttributeNode(attr);
         document.appendChild(filter);
         return filter;
     }
 
     private static AnyxmlNode<?> buildFilterXmlNode(final Document document) {
         return Builders.anyXmlBuilder()
-                .withNodeIdentifier(NETCONF_FILTER_NODEID)
-                .withValue(new DOMSource(document.getDocumentElement()))
-                .build();
+            .withNodeIdentifier(NETCONF_FILTER_NODEID)
+            .withValue(new DOMSource(document.getDocumentElement()))
+            .build();
     }
 
     private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
@@ -161,9 +157,9 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
         if (dataPath.isEmpty()) {
             return;
         }
-        final XmlElement dataElement = XmlElement.fromDomElement(dataNode);
-        final PathArgument lastPathArgument = dataPath.getLastPathArgument();
-        final QName nodeType = lastPathArgument.getNodeType();
+        final var dataElement = XmlElement.fromDomElement(dataNode);
+        final var  lastPathArgument = dataPath.getLastPathArgument();
+        final var nodeType = lastPathArgument.getNodeType();
         if (!nodeType.getNamespace().toString().equals(dataNode.getNamespaceURI())
                 || !nodeType.getLocalName().equals(dataElement.getName())) {
             throw new IllegalStateException(
@@ -175,12 +171,10 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
     }
 
     private static void checkKeyValuesValidForPath(final XmlElement dataElement, final PathArgument lastPathArgument) {
-        final NodeIdentifierWithPredicates keyedId = (NodeIdentifierWithPredicates) lastPathArgument;
-        for (Entry<QName, Object> entry : keyedId.entrySet()) {
-            QName qualifiedName = entry.getKey();
-            final List<XmlElement> key =
-                    dataElement.getChildElementsWithinNamespace(qualifiedName.getLocalName(),
-                            qualifiedName.getNamespace().toString());
+        for (var entry : ((NodeIdentifierWithPredicates) lastPathArgument).entrySet()) {
+            final var qname = entry.getKey();
+            final var key = dataElement.getChildElementsWithinNamespace(qname.getLocalName(),
+                qname.getNamespace().toString());
             if (key.isEmpty()) {
                 throw new IllegalStateException("No key present in xml");
             }
@@ -200,23 +194,23 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
     }
 
     private static void setOperationAttribute(final ModifyAction operation, final Document document,
-                                              final Element dataNode) {
-        final Attr operationAttribute = document.createAttributeNS(NETCONF_OPERATION_QNAME.getNamespace().toString(),
+            final Element dataNode) {
+        final var operationAttribute = document.createAttributeNS(NETCONF_OPERATION_QNAME.getNamespace().toString(),
             NETCONF_OPERATION_QNAME.getLocalName());
-        operationAttribute.setTextContent(toOperationString(operation));
+        operationAttribute.setTextContent(operation.xmlValue());
         dataNode.setAttributeNode(operationAttribute);
     }
 
     private static Element instanceIdToXmlStructure(final List<PathArgument> pathArguments, final Element data) {
-        final Document doc = data.getOwnerDocument();
-        Element parent = data;
-        for (PathArgument pathArgument : pathArguments) {
-            final QName nodeType = pathArgument.getNodeType();
-            final Element element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
+        final var doc = data.getOwnerDocument();
+        var parent = data;
+        for (var pathArgument : pathArguments) {
+            final var nodeType = pathArgument.getNodeType();
+            final var element = doc.createElementNS(nodeType.getNamespace().toString(), nodeType.getLocalName());
             parent.appendChild(element);
             //if path argument is list id, add also keys to resulting xml
-            if (pathArgument instanceof NodeIdentifierWithPredicates) {
-                appendListKeyNodes(element, (NodeIdentifierWithPredicates) pathArgument);
+            if (pathArgument instanceof NodeIdentifierWithPredicates nip) {
+                appendListKeyNodes(element, nip);
             }
             parent = element;
         }
@@ -224,32 +218,24 @@ class SchemalessRpcStructureTransformer implements RpcStructureTransformer {
     }
 
     private static List<XmlElement> selectMatchingNodes(final Element domElement, final YangInstanceIdentifier path) {
-        XmlElement element = XmlElement.fromDomElement(domElement);
-        for (PathArgument pathArgument : path.getPathArguments()) {
-            List<XmlElement> childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
+        var element = XmlElement.fromDomElement(domElement);
+        for (var pathArgument : path.getPathArguments()) {
+            var childElements = element.getChildElements(pathArgument.getNodeType().getLocalName());
             if (childElements.size() == 1) {
                 element = childElements.get(0);
             } else {
                 return childElements;
             }
         }
-        return Collections.singletonList(element);
-    }
-
-    private static String toOperationString(final ModifyAction operation) {
-        return operation.name().toLowerCase(Locale.ROOT);
+        return List.of(element);
     }
 
     private static Element getSourceElement(final DOMSource source) {
-        final Node node = source.getNode();
-        switch (node.getNodeType()) {
-            case Node.DOCUMENT_NODE:
-                return ((Document)node).getDocumentElement();
-            case Node.ELEMENT_NODE:
-                return (Element) node;
-            default:
-                throw new IllegalStateException("DOMSource node must be document or element.");
-        }
+        final var node = source.getNode();
+        return switch (node.getNodeType()) {
+            case Node.DOCUMENT_NODE -> ((Document) node).getDocumentElement();
+            case Node.ELEMENT_NODE -> (Element) node;
+            default -> throw new IllegalStateException("DOMSource node must be document or element.");
+        };
     }
-
 }