From 4ea9c90ab7eb1382fb049e1a40718a1d003a040b Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Sun, 24 Oct 2021 17:40:39 +0200 Subject: [PATCH] Promote QueryParams as WriteDataParams 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 --- .../nb/rfc8040/NotificationQueryParams.java | 2 +- .../restconf/nb/rfc8040/WriteDataParams.java | 82 +++++++++++++++++++ .../{UriInfoSupport.java => QueryParams.java} | 43 +++++++++- .../impl/RestconfDataServiceImpl.java | 76 ++--------------- ...estconfStreamsSubscriptionServiceImpl.java | 8 +- .../rests/utils/ReadDataTransactionUtil.java | 2 +- ...oSupportTest.java => QueryParamsTest.java} | 6 +- 7 files changed, 137 insertions(+), 82 deletions(-) create mode 100644 restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/WriteDataParams.java rename restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/{UriInfoSupport.java => QueryParams.java} (68%) rename restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/{UriInfoSupportTest.java => QueryParamsTest.java} (89%) diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java index 728a1df621..0ce477cea2 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/NotificationQueryParams.java @@ -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 index 0000000000..5d907d7999 --- /dev/null +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/WriteDataParams.java @@ -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 diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupport.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java similarity index 68% rename from restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupport.java rename to restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java index b2d892c517..e5d4e668b9 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupport.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParams.java @@ -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> entry : uriInfo.getQueryParameters().entrySet()) { + final String uriName = entry.getKey(); + final List 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 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 values) { + private static @Nullable String optionalParam(final String name, final List values) { switch (values.size()) { case 0: return null; diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java index e9b6139122..76b3d23ceb 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfDataServiceImpl.java @@ -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 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> entry : uriInfo.getQueryParameters().entrySet()) { - final String uriName = entry.getKey(); - final List 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 diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfStreamsSubscriptionServiceImpl.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfStreamsSubscriptionServiceImpl.java index 81f23c0417..f141f6503a 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfStreamsSubscriptionServiceImpl.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/services/impl/RestconfStreamsSubscriptionServiceImpl.java @@ -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); diff --git a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java index f74edc1edc..4741999296 100644 --- a/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java +++ b/restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/rests/utils/ReadDataTransactionUtil.java @@ -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; diff --git a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupportTest.java b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParamsTest.java similarity index 89% rename from restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupportTest.java rename to restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParamsTest.java index 5037e24d23..788bf0c814 100644 --- a/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/UriInfoSupportTest.java +++ b/restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/databind/jaxrs/QueryParamsTest.java @@ -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 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 errors = ex.getErrors(); assertEquals(1, errors.size()); -- 2.36.6