Simplify trimming
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / utils / ReadDataTransactionUtil.java
index d6bad685dbfac071c85869a071bbbd3fddccf05f..76f0d6f1a27f72bdc0767b512a68045977cd0667 100644 (file)
@@ -7,34 +7,24 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
-import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.UriInfo;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
-import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
-import org.opendaylight.restconf.common.context.WriterParameters;
-import org.opendaylight.restconf.common.context.WriterParameters.WriterParametersBuilder;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorTag;
-import org.opendaylight.restconf.common.errors.RestconfError.ErrorType;
+import org.opendaylight.restconf.nb.rfc8040.ContentParam;
+import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParam;
 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.utils.RestconfDataServiceConstant.ReadData.WithDefaults;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -83,148 +73,39 @@ import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
  * </ul>
  */
 public final class ReadDataTransactionUtil {
-    private static final Set<String> ALLOWED_PARAMETERS = Set.of(
-        RestconfDataServiceConstant.ReadData.CONTENT,
-        RestconfDataServiceConstant.ReadData.DEPTH,
-        RestconfDataServiceConstant.ReadData.FIELDS,
-        RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
-    private static final List<String> DEFAULT_CONTENT = List.of(RestconfDataServiceConstant.ReadData.ALL);
-    private static final List<String> DEFAULT_DEPTH = List.of(RestconfDataServiceConstant.ReadData.UNBOUNDED);
-    private static final String READ_TYPE_TX = "READ";
-
     private ReadDataTransactionUtil() {
         // Hidden on purpose
     }
 
-    /**
-     * Parse parameters from URI request and check their types and values.
-     *
-     * @param identifier {@link InstanceIdentifierContext}
-     * @param uriInfo    URI info
-     * @return {@link WriterParameters}
-     */
-    public static WriterParameters parseUriParameters(final InstanceIdentifierContext<?> identifier,
-                                                      final UriInfo uriInfo) {
-        final WriterParametersBuilder builder = new WriterParametersBuilder();
-        if (uriInfo == null) {
-            return builder.build();
-        }
-
-        // check only allowed parameters
-        final MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
-        checkParametersTypes(queryParams.keySet(), ALLOWED_PARAMETERS);
-
-        // read parameters from URI or set default values
-        final List<String> content = queryParams.getOrDefault(
-                RestconfDataServiceConstant.ReadData.CONTENT, DEFAULT_CONTENT);
-        final List<String> depth = queryParams.getOrDefault(
-                RestconfDataServiceConstant.ReadData.DEPTH, DEFAULT_DEPTH);
-        final List<String> withDefaults = queryParams.getOrDefault(
-                RestconfDataServiceConstant.ReadData.WITH_DEFAULTS, List.of());
-        // fields
-        final List<String> fields = queryParams.getOrDefault(RestconfDataServiceConstant.ReadData.FIELDS, List.of());
-
-        // parameter can be in URI at most once
-        checkParameterCount(content, RestconfDataServiceConstant.ReadData.CONTENT);
-        checkParameterCount(depth, RestconfDataServiceConstant.ReadData.DEPTH);
-        checkParameterCount(fields, RestconfDataServiceConstant.ReadData.FIELDS);
-        checkParameterCount(withDefaults, RestconfDataServiceConstant.ReadData.WITH_DEFAULTS);
-
-        // check and set content
-        final String contentValue = content.get(0);
-        switch (contentValue) {
-            case RestconfDataServiceConstant.ReadData.ALL:
-            case RestconfDataServiceConstant.ReadData.CONFIG:
-            case RestconfDataServiceConstant.ReadData.NONCONFIG:
-                // FIXME: we really want to have a proper enumeration for this field
-                builder.setContent(contentValue);
-                break;
-            default:
-                throw new RestconfDocumentedException(
-                    new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
-                        "Invalid content parameter: " + contentValue, null,
-                        "The content parameter value must be either config, nonconfig or all (default)"));
-        }
-
-        // check and set depth
-        if (!depth.get(0).equals(RestconfDataServiceConstant.ReadData.UNBOUNDED)) {
-            final Integer value = Ints.tryParse(depth.get(0));
-
-            if (value == null || value < RestconfDataServiceConstant.ReadData.MIN_DEPTH
-                    || value > RestconfDataServiceConstant.ReadData.MAX_DEPTH) {
-                throw new RestconfDocumentedException(
-                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
-                                "Invalid depth parameter: " + depth, null,
-                                "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
-            } else {
-                builder.setDepth(value);
-            }
-        }
-
-        // check and set fields
-        if (!fields.isEmpty()) {
-            if (identifier.getMountPoint() != null) {
-                builder.setFieldPaths(parseFieldsPaths(identifier, fields.get(0)));
-            } else {
-                builder.setFields(parseFieldsParameter(identifier, fields.get(0)));
-            }
-        }
-
-        // check and set withDefaults parameter
-        if (!withDefaults.isEmpty()) {
-            final String str = withDefaults.get(0);
-            final WithDefaults val = WithDefaults.forValue(str);
-            if (val == null) {
-                throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
-                    RestconfError.ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + str, null,
-                    "The with-defaults parameter must be a string in " + WithDefaults.possibleValues()));
-            }
-
-            switch (val) {
-                case REPORT_ALL:
-                    break;
-                case REPORT_ALL_TAGGED:
-                    builder.setTagged(true);
-                    break;
-                default:
-                    builder.setWithDefault(val.value());
-            }
-        }
-        return builder.build();
-    }
-
     /**
      * Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
      * inside of object {@link RestconfStrategy} provided as a parameter.
      *
-     * @param valueOfContent type of data to read (config, state, all)
+     * @param content        type of data to read (config, state, all)
      * @param path           the path to read
      * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
      * @param withDefa       value of with-defaults parameter
      * @param ctx            schema context
      * @return {@link NormalizedNode}
      */
-    public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent,
+    public static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
                                                     final @NonNull YangInstanceIdentifier path,
                                                     final @NonNull RestconfStrategy strategy,
-                                                    final String withDefa, final EffectiveModelContext ctx) {
-        switch (valueOfContent) {
-            case RestconfDataServiceConstant.ReadData.CONFIG:
-                if (withDefa == null) {
-                    return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true);
-                } else {
-                    return prepareDataByParamWithDef(
-                            readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
-                            path, withDefa, ctx);
-                }
-            case RestconfDataServiceConstant.ReadData.NONCONFIG:
-                return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true);
-            case RestconfDataServiceConstant.ReadData.ALL:
+                                                    final WithDefaultsParam withDefa,
+                                                    final EffectiveModelContext ctx) {
+        // FIXME: use a switch expression when they are available, removing source of RestconfDocumentedException
+        switch (content) {
+            case ALL:
                 return readAllData(strategy, path, withDefa, ctx);
+            case CONFIG:
+                final NormalizedNode read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path);
+                return withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa, ctx);
+            case NONCONFIG:
+                return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
             default:
                 throw new RestconfDocumentedException(
-                        new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE,
-                                "Invalid content parameter: " + valueOfContent, null,
+                        new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+                                "Invalid content parameter: " + content.paramValue(), null,
                                 "The content parameter value must be either config, nonconfig or all (default)"));
         }
     }
@@ -233,7 +114,7 @@ public final class ReadDataTransactionUtil {
      * Read specific type of data from data store via transaction with specified subtrees that should only be read.
      * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
      *
-     * @param valueOfContent type of data to read (config, state, all)
+     * @param content        type of data to read (config, state, all)
      * @param path           the parent path to read
      * @param strategy       {@link RestconfStrategy} - object that perform the actual DS operations
      * @param withDefa       value of with-defaults parameter
@@ -241,76 +122,39 @@ public final class ReadDataTransactionUtil {
      * @param fields         paths to selected subtrees which should be read, relative to to the parent path
      * @return {@link NormalizedNode}
      */
-    public static @Nullable NormalizedNode readData(final @NonNull String valueOfContent,
+    public static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
             final @NonNull YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy,
-            final @Nullable String withDefa, @NonNull final EffectiveModelContext ctx,
+            final @Nullable WithDefaultsParam withDefa, @NonNull final EffectiveModelContext ctx,
             final @NonNull List<YangInstanceIdentifier> fields) {
-        switch (valueOfContent) {
-            case RestconfDataServiceConstant.ReadData.CONFIG:
-                if (withDefa == null) {
-                    return readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
-                } else {
-                    return prepareDataByParamWithDef(
-                            readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
-                            path, withDefa, ctx);
-                }
-            case RestconfDataServiceConstant.ReadData.NONCONFIG:
-                return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, true, fields);
-            case RestconfDataServiceConstant.ReadData.ALL:
+        // FIXME: use a switch expression when they are available, removing source of RestconfDocumentedException
+        switch (content) {
+            case ALL:
                 return readAllData(strategy, path, withDefa, ctx, fields);
+            case CONFIG:
+                final NormalizedNode read = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path,
+                    fields);
+                return withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa, ctx);
+            case NONCONFIG:
+                return readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path, fields);
             default:
-                throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL,
-                        RestconfError.ErrorTag.INVALID_VALUE, "Invalid content parameter: " + valueOfContent, null,
+                throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
+                        "Invalid content parameter: " + content.paramValue(), null,
                         "The content parameter value must be either config, nonconfig or all (default)"));
         }
     }
 
-    /**
-     * Check if URI does not contain value for the same parameter more than once.
-     *
-     * @param parameterValues URI parameter values
-     * @param parameterName URI parameter name
-     */
-    @VisibleForTesting
-    static void checkParameterCount(final @NonNull List<String> parameterValues, final @NonNull String parameterName) {
-        if (parameterValues.size() > 1) {
-            throw new RestconfDocumentedException(
-                    "Parameter " + parameterName + " can appear at most once in request URI",
-                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-        }
-    }
-
-    /**
-     * Check if URI does not contain not allowed parameters for specified operation.
-     *
-     * @param usedParameters parameters used in URI request
-     * @param allowedParameters allowed parameters for operation
-     */
-    @VisibleForTesting
-    static void checkParametersTypes(final @NonNull Set<String> usedParameters,
-                                     final @NonNull Set<String> allowedParameters) {
-        if (!allowedParameters.containsAll(usedParameters)) {
-            final Set<String> notAllowedParameters = usedParameters.stream()
-                .filter(param -> !allowedParameters.contains(param))
-                .collect(Collectors.toSet());
-            throw new RestconfDocumentedException(
-                "Not allowed parameters for " + READ_TYPE_TX + " operation: " + notAllowedParameters,
-                RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE);
-        }
-    }
-
     private static NormalizedNode prepareDataByParamWithDef(final NormalizedNode result,
-            final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
+            final YangInstanceIdentifier path, final WithDefaultsParam withDefa, final EffectiveModelContext ctx) {
         boolean trim;
         switch (withDefa) {
-            case "trim":
+            case TRIM:
                 trim = true;
                 break;
-            case "explicit":
+            case EXPLICIT:
                 trim = false;
                 break;
             default:
-                throw new RestconfDocumentedException("");
+                throw new RestconfDocumentedException("Unsupported with-defaults value " + withDefa.paramValue());
         }
 
         final DataSchemaContextTree baseSchemaCtxTree = DataSchemaContextTree.from(ctx);
@@ -355,18 +199,14 @@ public final class ReadDataTransactionUtil {
                 if (keys.contains(child.getIdentifier().getNodeType())) {
                     leafBuilder.withValue(((LeafNode<?>) child).body());
                     builder.withChild(leafBuilder.build());
-                } else {
-                    if (trim) {
-                        if (defaultVal == null || !defaultVal.equals(nodeVal)) {
-                            leafBuilder.withValue(((LeafNode<?>) child).body());
-                            builder.withChild(leafBuilder.build());
-                        }
-                    } else {
-                        if (defaultVal != null && defaultVal.equals(nodeVal)) {
-                            leafBuilder.withValue(((LeafNode<?>) child).body());
-                            builder.withChild(leafBuilder.build());
-                        }
+                } else if (trim) {
+                    if (defaultVal == null || !defaultVal.equals(nodeVal)) {
+                        leafBuilder.withValue(((LeafNode<?>) child).body());
+                        builder.withChild(leafBuilder.build());
                     }
+                } else if (defaultVal != null && defaultVal.equals(nodeVal)) {
+                    leafBuilder.withValue(((LeafNode<?>) child).body());
+                    builder.withChild(leafBuilder.build());
                 }
             }
         }
@@ -413,11 +253,9 @@ public final class ReadDataTransactionUtil {
                         leafBuilder.withValue(((LeafNode<?>) child).body());
                         builder.withChild(leafBuilder.build());
                     }
-                } else {
-                    if (defaultVal != null && defaultVal.equals(nodeVal)) {
-                        leafBuilder.withValue(((LeafNode<?>) child).body());
-                        builder.withChild(leafBuilder.build());
-                    }
+                } else if (defaultVal != null && defaultVal.equals(nodeVal)) {
+                    leafBuilder.withValue(((LeafNode<?>) child).body());
+                    builder.withChild(leafBuilder.build());
                 }
             }
         }
@@ -433,10 +271,8 @@ public final class ReadDataTransactionUtil {
      * @return {@link NormalizedNode}
      */
     static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
-            final LogicalDatastoreType store, final YangInstanceIdentifier path,
-            final boolean closeTransactionChain) {
-        final ListenableFuture<Optional<NormalizedNode>> listenableFuture = strategy.read(store, path);
-        return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
+            final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+        return extractReadData(strategy, path, strategy.read(store, path));
     }
 
     /**
@@ -453,21 +289,14 @@ public final class ReadDataTransactionUtil {
      */
     private static @Nullable NormalizedNode readDataViaTransaction(final @NonNull RestconfStrategy strategy,
             final @NonNull LogicalDatastoreType store, final @NonNull YangInstanceIdentifier path,
-            final boolean closeTransactionChain, final @NonNull List<YangInstanceIdentifier> fields) {
-        final ListenableFuture<Optional<NormalizedNode>> listenableFuture = strategy.read(store, path, fields);
-        return extractReadData(strategy, path, closeTransactionChain, listenableFuture);
+            final @NonNull List<YangInstanceIdentifier> fields) {
+        return extractReadData(strategy, path, strategy.read(store, path, fields));
     }
 
     private static NormalizedNode extractReadData(final RestconfStrategy strategy,
-            final YangInstanceIdentifier path, final boolean closeTransactionChain,
-            final ListenableFuture<Optional<NormalizedNode>> dataFuture) {
+            final YangInstanceIdentifier path, final ListenableFuture<Optional<NormalizedNode>> dataFuture) {
         final NormalizedNodeFactory dataFactory = new NormalizedNodeFactory();
-        if (closeTransactionChain) {
-            //Method close transactionChain if any
-            FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory, strategy, path);
-        } else {
-            FutureCallbackTx.addCallback(dataFuture, READ_TYPE_TX, dataFactory);
-        }
+        FutureCallbackTx.addCallback(dataFuture, "READ", dataFactory, path);
         return dataFactory.build();
     }
 
@@ -481,24 +310,15 @@ public final class ReadDataTransactionUtil {
      * @return {@link NormalizedNode}
      */
     private static @Nullable NormalizedNode readAllData(final @NonNull RestconfStrategy strategy,
-            final YangInstanceIdentifier path, final String withDefa, final EffectiveModelContext ctx) {
+            final YangInstanceIdentifier path, final WithDefaultsParam withDefa, final EffectiveModelContext ctx) {
         // PREPARE STATE DATA NODE
-        final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path,
-            false);
-
+        final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path);
         // PREPARE CONFIG DATA NODE
-        final NormalizedNode configDataNode;
-        //Here will be closed transactionChain if any
-        if (withDefa == null) {
-            configDataNode = readDataViaTransaction(
-                    strategy, LogicalDatastoreType.CONFIGURATION, path, true);
-        } else {
-            configDataNode = prepareDataByParamWithDef(
-                    readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true),
-                    path, withDefa, ctx);
-        }
+        final NormalizedNode configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION,
+            path);
 
-        return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
+        return mergeConfigAndSTateDataIfNeeded(stateDataNode,
+            withDefa == null ? configDataNode : prepareDataByParamWithDef(configDataNode, path, withDefa, ctx));
     }
 
     /**
@@ -513,24 +333,17 @@ public final class ReadDataTransactionUtil {
      * @return {@link NormalizedNode}
      */
     private static @Nullable NormalizedNode readAllData(final @NonNull RestconfStrategy strategy,
-            final @NonNull YangInstanceIdentifier path, final @Nullable String withDefa,
+            final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
             final @NonNull EffectiveModelContext ctx, final @NonNull List<YangInstanceIdentifier> fields) {
         // PREPARE STATE DATA NODE
         final NormalizedNode stateDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.OPERATIONAL, path,
-            false, fields);
+            fields);
 
         // PREPARE CONFIG DATA NODE
-        final NormalizedNode configDataNode;
-        //Here will be closed transactionChain if any
-        if (withDefa == null) {
-            configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields);
-        } else {
-            configDataNode = prepareDataByParamWithDef(
-                    readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path, true, fields),
-                    path, withDefa, ctx);
-        }
-
-        return mergeConfigAndSTateDataIfNeeded(stateDataNode, configDataNode);
+        final NormalizedNode configDataNode = readDataViaTransaction(strategy, LogicalDatastoreType.CONFIGURATION, path,
+            fields);
+        return mergeConfigAndSTateDataIfNeeded(stateDataNode,
+            withDefa == null ? configDataNode : prepareDataByParamWithDef(configDataNode, path, withDefa, ctx));
     }
 
     private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,