Promote QueryParams as WriteDataParams 05/98105/3
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 24 Oct 2021 15:40:39 +0000 (17:40 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 24 Oct 2021 16:18:04 +0000 (18:18 +0200)
Internal structure in RestconfDataServiceImpl is suitable definition
of what we expect data resource modifications to accept. Promote it to
an API contract. Also rename UriInfoSupport to QueryParams to use the
newly-vacant name.

JIRA: NETCONF-773
Change-Id: I4ca2d01338ecc3a828978a78a23566c27ed4db84
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/WriteDataParams.java [new file with mode: 0644]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java [moved from restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupport.java with 68% similarity]
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfStreamsSubscriptionServiceImpl.java
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java
restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParamsTest.java [moved from restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupportTest.java with 89% similarity]

index 728a1df621254d4fce70ef8568a109f4f5b40144..0ce477cea21313e24e4811e0dc19858f4b39f34b 100644 (file)
@@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.Immutable;
 
 /**
- * Parser and holder of query paramteres from uriInfo for notifications.
+ * Parser and holder of query parameters from uriInfo for notifications.
  */
 public final class NotificationQueryParams implements Immutable {
     private final StartTimeParameter startTime;
diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/WriteDataParams.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/WriteDataParams.java
new file mode 100644 (file)
index 0000000..5d907d7
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * 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.restconf.nb.rfc8040;
+
+import com.google.common.base.MoreObjects;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+/**
+ * Parser and holder of query parameters from uriInfo for data and datastore modification operations.
+ */
+// FIXME: this should be a record with JDK17+
+public final class WriteDataParams implements Immutable {
+    private static final @NonNull WriteDataParams EMPTY = new WriteDataParams(null, null);
+
+    private final PointParameter point;
+    private final InsertParameter insert;
+
+    private WriteDataParams(final InsertParameter insert, final PointParameter point) {
+        this.insert = insert;
+        this.point = point;
+    }
+
+    public static @NonNull WriteDataParams empty() {
+        return EMPTY;
+    }
+
+    public static @NonNull WriteDataParams of(final InsertParameter insert, final PointParameter point) {
+        if (point == null) {
+            if (insert == null) {
+                return empty();
+            }
+
+            // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.5:
+            //        If the values "before" or "after" are used, then a "point" query
+            //        parameter for the "insert" query parameter MUST also be present, or a
+            //        "400 Bad Request" status-line is returned.
+            if (insert == InsertParameter.BEFORE || insert == InsertParameter.AFTER) {
+                throw new IllegalArgumentException(
+                    "Insert parameter " + insert.uriValue() + " cannot be used without a Point parameter.");
+            }
+        } else {
+            // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.6:
+            // [when "point" parameter is present and]
+            //        If the "insert" query parameter is not present or has a value other
+            //        than "before" or "after", then a "400 Bad Request" status-line is
+            //        returned.
+            if (insert != InsertParameter.BEFORE && insert != InsertParameter.AFTER) {
+                throw new IllegalArgumentException(
+                    "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
+            }
+        }
+
+        return new WriteDataParams(insert, point);
+    }
+
+    public @Nullable InsertParameter insert() {
+        return insert;
+    }
+
+    public @Nullable PointParameter point() {
+        return point;
+    }
+
+    @Override
+    public String toString() {
+        final var helper = MoreObjects.toStringHelper(this).omitNullValues();
+        if (insert != null) {
+            helper.add("insert", insert.uriValue());
+        }
+        if (point != null) {
+            helper.add("point", point.value());
+        }
+        return helper.toString();
+    }
+}
\ No newline at end of file
@@ -19,15 +19,18 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.FilterParameter;
+import org.opendaylight.restconf.nb.rfc8040.InsertParameter;
 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
+import org.opendaylight.restconf.nb.rfc8040.PointParameter;
 import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
 import org.opendaylight.restconf.nb.rfc8040.StopTimeParameter;
+import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
 @Beta
-public final class UriInfoSupport {
-    private UriInfoSupport() {
+public final class QueryParams {
+    private QueryParams() {
         // Utility class
     }
 
@@ -68,12 +71,46 @@ public final class UriInfoSupport {
         }
     }
 
+    public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
+        InsertParameter insert = null;
+        PointParameter point = null;
+
+        for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+            final String uriName = entry.getKey();
+            final List<String> paramValues = entry.getValue();
+            if (uriName.equals(InsertParameter.uriName())) {
+                final String str = optionalParam(uriName, paramValues);
+                if (str != null) {
+                    insert = InsertParameter.forUriValue(str);
+                    if (insert == null) {
+                        throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
+                            ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+                    }
+                }
+            } else if (PointParameter.uriName().equals(uriName)) {
+                final String str = optionalParam(uriName, paramValues);
+                if (str != null) {
+                    point = PointParameter.forUriValue(str);
+                }
+            } else {
+                throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
+                    ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+            }
+        }
+
+        try {
+            return WriteDataParams.of(insert, point);
+        } catch (IllegalArgumentException e) {
+            throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
+        }
+    }
+
     public static @Nullable String getSingleParameter(final MultivaluedMap<String, String> params, final String name) {
         final var values = params.get(name);
         return values == null ? null : optionalParam(name, values);
     }
 
-    public static @Nullable String optionalParam(final String name, final List<String> values) {
+    private static @Nullable String optionalParam(final String name, final List<String> values) {
         switch (values.size()) {
             case 0:
                 return null;
index e9b6139122ccd2d8acf57962a46b2b9a67d719c3..76b3d23cebba2407610d21f6209f70d891f9bb77 100644 (file)
@@ -26,7 +26,6 @@ import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
@@ -34,7 +33,6 @@ import javax.ws.rs.Path;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
-import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMActionException;
 import org.opendaylight.mdsal.dom.api.DOMActionResult;
@@ -51,10 +49,9 @@ import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.patch.PatchContext;
 import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.nb.rfc8040.InsertParameter;
-import org.opendaylight.restconf.nb.rfc8040.PointParameter;
 import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
-import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.UriInfoSupport;
+import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
+import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
@@ -73,7 +70,6 @@ import org.opendaylight.restconf.nb.rfc8040.streams.listeners.NotificationListen
 import org.opendaylight.restconf.nb.rfc8040.utils.mapping.RestconfMappingNodeUtil;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
-import org.opendaylight.yangtools.concepts.Immutable;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -101,17 +97,6 @@ import org.slf4j.LoggerFactory;
  */
 @Path("/")
 public class RestconfDataServiceImpl implements RestconfDataService {
-    // FIXME: we should be able to interpret 'point' and refactor this class into a behavior
-    private static final class QueryParams implements Immutable {
-        final @Nullable PointParameter point;
-        final @Nullable InsertParameter insert;
-
-        QueryParams(final @Nullable InsertParameter insert, final @Nullable PointParameter point) {
-            this.insert = insert;
-            this.point = point;
-        }
-    }
-
     private static final Logger LOG = LoggerFactory.getLogger(RestconfDataServiceImpl.class);
     private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
     private static final QName NETCONF_BASE_QNAME = SchemaContext.NAME;
@@ -231,7 +216,7 @@ public class RestconfDataServiceImpl implements RestconfDataService {
     public Response putData(final String identifier, final NormalizedNodePayload payload, final UriInfo uriInfo) {
         requireNonNull(payload);
 
-        final QueryParams checkedParms = checkQueryParameters(uriInfo);
+        final WriteDataParams checkedParms = QueryParams.newWriteDataParams(uriInfo);
 
         final InstanceIdentifierContext<? extends SchemaNode> iid = payload.getInstanceIdentifierContext();
 
@@ -244,56 +229,7 @@ public class RestconfDataServiceImpl implements RestconfDataService {
                 ? schemaContextHandler.get() : modelContext(mountPoint);
 
         final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
-        return PutDataTransactionUtil.putData(payload, ref, strategy, checkedParms.insert, checkedParms.point);
-    }
-
-    private static QueryParams checkQueryParameters(final UriInfo uriInfo) {
-        InsertParameter insert = null;
-        PointParameter point = null;
-
-        for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
-            final String uriName = entry.getKey();
-            final List<String> paramValues = entry.getValue();
-            if (uriName.equals(InsertParameter.uriName())) {
-                final String str = UriInfoSupport.optionalParam(uriName, paramValues);
-                if (str != null) {
-                    insert = InsertParameter.forUriValue(str);
-                    if (insert == null) {
-                        throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
-                            ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
-                    }
-                }
-            } else if (PointParameter.uriName().equals(uriName)) {
-                final String str = UriInfoSupport.optionalParam(uriName, paramValues);
-                if (str != null) {
-                    point = PointParameter.forUriValue(str);
-                }
-            } else {
-                throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
-                    ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
-            }
-        }
-
-        // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.5:
-        //        If the values "before" or "after" are used, then a "point" query
-        //        parameter for the "insert" query parameter MUST also be present, or a
-        //        "400 Bad Request" status-line is returned.
-        if ((insert == InsertParameter.BEFORE || insert == InsertParameter.AFTER) && point == null) {
-            throw new RestconfDocumentedException(
-                "Insert parameter " + insert.uriValue() + " cannot be used without a Point parameter.",
-                ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
-        }
-        // https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.6:
-        //        If the "insert" query parameter is not present or has a value other
-        //        than "before" or "after", then a "400 Bad Request" status-line is
-        //        returned.
-        if (point != null && insert != InsertParameter.BEFORE && insert != InsertParameter.AFTER) {
-            throw new RestconfDocumentedException(
-                "Point parameter can be used only with 'after' or 'before' values of Insert parameter.",
-                ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
-        }
-
-        return new QueryParams(insert, point);
+        return PutDataTransactionUtil.putData(payload, ref, strategy, checkedParms.insert(), checkedParms.point());
     }
 
     @Override
@@ -308,11 +244,11 @@ public class RestconfDataServiceImpl implements RestconfDataService {
             return invokeAction(payload);
         }
 
-        final QueryParams checkedParms = checkQueryParameters(uriInfo);
+        final WriteDataParams checkedParms = QueryParams.newWriteDataParams(uriInfo);
         final DOMMountPoint mountPoint = payload.getInstanceIdentifierContext().getMountPoint();
         final RestconfStrategy strategy = getRestconfStrategy(mountPoint);
         return PostDataTransactionUtil.postData(uriInfo, payload, strategy,
-                getSchemaContext(mountPoint), checkedParms.insert, checkedParms.point);
+                getSchemaContext(mountPoint), checkedParms.insert(), checkedParms.point());
     }
 
     @Override
index 81f23c0417230c6bd258a8a3ec77b3e8d30c05d7..f141f6503a254e4d26680ef82dc5a09bc8a4c204 100644 (file)
@@ -18,7 +18,7 @@ import org.opendaylight.mdsal.dom.api.DOMNotificationService;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
-import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.UriInfoSupport;
+import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
@@ -71,13 +71,13 @@ public class RestconfStreamsSubscriptionServiceImpl implements RestconfStreamsSu
 
     @Override
     public NormalizedNodePayload subscribeToStream(final String identifier, final UriInfo uriInfo) {
-        final NotificationQueryParams notificationQueryParams = UriInfoSupport.newNotificationQueryParams(uriInfo);
+        final NotificationQueryParams params = QueryParams.newNotificationQueryParams(uriInfo);
 
         final URI response;
         if (identifier.contains(RestconfStreamsConstants.DATA_SUBSCRIPTION)) {
-            response = streamUtils.subscribeToDataStream(identifier, uriInfo, notificationQueryParams, handlersHolder);
+            response = streamUtils.subscribeToDataStream(identifier, uriInfo, params, handlersHolder);
         } else if (identifier.contains(RestconfStreamsConstants.NOTIFICATION_STREAM)) {
-            response = streamUtils.subscribeToYangStream(identifier, uriInfo, notificationQueryParams, handlersHolder);
+            response = streamUtils.subscribeToYangStream(identifier, uriInfo, params, handlersHolder);
         } else {
             final String msg = "Bad type of notification of sal-remote";
             LOG.warn(msg);
index f74edc1edcb11dfb11e8cf11ae2fb71b6cd94995..47419992963cbaa9c40ee159d731ad83ed23330b 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.restconf.nb.rfc8040.rests.utils;
 
-import static org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.UriInfoSupport.getSingleParameter;
+import static org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams.getSingleParameter;
 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
 
@@ -19,7 +19,7 @@ import org.opendaylight.restconf.nb.rfc8040.ContentParameter;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
-public class UriInfoSupportTest {
+public class QueryParamsTest {
     /**
      * Test when parameter is present at most once.
      */
@@ -27,7 +27,7 @@ public class UriInfoSupportTest {
     public void getSingleParameterTest() {
         final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
         parameters.putSingle(ContentParameter.uriName(), "all");
-        assertEquals("all", UriInfoSupport.getSingleParameter(parameters, ContentParameter.uriName()));
+        assertEquals("all", QueryParams.getSingleParameter(parameters, ContentParameter.uriName()));
     }
 
     /**
@@ -39,7 +39,7 @@ public class UriInfoSupportTest {
         parameters.put(ContentParameter.uriName(), List.of("config", "nonconfig", "all"));
 
         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
-            () -> UriInfoSupport.getSingleParameter(parameters, ContentParameter.uriName()));
+            () -> QueryParams.getSingleParameter(parameters, ContentParameter.uriName()));
         final List<RestconfError> errors = ex.getErrors();
         assertEquals(1, errors.size());