import static java.util.Objects.requireNonNull;
import java.net.URI;
-import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.concepts.Immutable;
/**
* This class represents a {@code filter} parameter as defined in
* <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.4">RFC8040 section 4.8.4</a>.
*/
-@NonNullByDefault
+
public final class FilterParameter implements Immutable {
- private static final URI CAPABILITY = URI.create("urn:ietf:params:restconf:capability:filter:1.0");
+ private static final @NonNull URI CAPABILITY = URI.create("urn:ietf:params:restconf:capability:filter:1.0");
// FIXME: can we have a parsed, but not bound version of an XPath, please?
- private final String value;
+ private final @NonNull String value;
private FilterParameter(final String value) {
this.value = requireNonNull(value);
}
- public static FilterParameter forUriValue(final String uriValue) {
+ public static @NonNull FilterParameter forUriValue(final String uriValue) {
return new FilterParameter(uriValue);
}
- public static String uriName() {
+ public static @NonNull String uriName() {
return "filter";
}
- public String uriValue() {
+ public @NonNull String uriValue() {
return value;
}
- public static URI capabilityUri() {
+ public static @NonNull URI capabilityUri() {
return CAPABILITY;
}
}
*/
package org.opendaylight.restconf.nb.rfc8040;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.common.base.MoreObjects;
-import java.util.List;
-import java.util.Map.Entry;
-import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.yangtools.concepts.Immutable;
/**
this.skipNotificationData = skipNotificationData;
}
- // FIXME: this is JAX-RS specific
- public static @NonNull NotificationQueryParams fromUriInfo(final UriInfo uriInfo) {
- StartTimeParameter startTime = null;
- StopTimeParameter stopTime = null;
- FilterParameter filter = null;
- boolean skipNotificationData = false;
-
- for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
- final String paramName = entry.getKey();
- final List<String> paramValues = entry.getValue();
- if (paramName.equals(StartTimeParameter.uriName())) {
- switch (paramValues.size()) {
- case 0:
- break;
- case 1:
- final String str = paramValues.get(0);
- try {
- startTime = StartTimeParameter.forUriValue(str);
- } catch (IllegalArgumentException e) {
- throw new RestconfDocumentedException("Invalid start-time date: " + str, e);
- }
- break;
- default:
- throw new RestconfDocumentedException("Start-time parameter can be used only once.");
- }
- } else if (paramName.equals(StopTimeParameter.uriName())) {
- switch (paramValues.size()) {
- case 0:
- break;
- case 1:
- final String str = paramValues.get(0);
- try {
- stopTime = StopTimeParameter.forUriValue(str);
- } catch (IllegalArgumentException e) {
- throw new RestconfDocumentedException("Invalid stop-time date: " + str, e);
- }
- break;
- default:
- throw new RestconfDocumentedException("Stop-time parameter can be used only once.");
- }
- } else if (paramName.equals(FilterParameter.uriName())) {
- if (!paramValues.isEmpty()) {
- filter = FilterParameter.forUriValue(paramValues.get(0));
- }
- } else if (paramName.equals("odl-skip-notification-data")) {
- switch (paramValues.size()) {
- case 0:
- break;
- case 1:
- skipNotificationData = Boolean.parseBoolean(paramValues.get(0));
- break;
- default:
- throw new RestconfDocumentedException(
- "Odl-skip-notification-data parameter can be used only once.");
- }
- } else {
- throw new RestconfDocumentedException("Bad parameter used with notifications: " + paramName);
- }
- }
- if (startTime == null && stopTime != null) {
- throw new RestconfDocumentedException("Stop-time parameter has to be used with start-time parameter.");
- }
-
+ public static @NonNull NotificationQueryParams of(final StartTimeParameter startTime,
+ final StopTimeParameter stopTime, final FilterParameter filter, final boolean skipNotificationData) {
+ checkArgument(stopTime == null || startTime != null,
+ "Stop-time parameter has to be used with start-time parameter.");
return new NotificationQueryParams(startTime, stopTime, filter, skipNotificationData);
}
*/
package org.opendaylight.restconf.nb.rfc8040;
-import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
/**
* This class represents a {@code start-time} parameter as defined in
* <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.7">RFC8040 section 4.8.7</a>.
*/
-@NonNullByDefault
public final class StartTimeParameter extends AbstractReplayParameter {
private StartTimeParameter(final DateAndTime value) {
super(value);
}
- public static StartTimeParameter of(final DateAndTime value) {
+ public static @NonNull StartTimeParameter of(final DateAndTime value) {
return new StartTimeParameter(value);
}
- public static String uriName() {
+ public static @NonNull String uriName() {
return "start-time";
}
- public static StartTimeParameter forUriValue(final String uriValue) {
+ public static @NonNull StartTimeParameter forUriValue(final String uriValue) {
return of(new DateAndTime(uriValue));
}
}
*/
package org.opendaylight.restconf.nb.rfc8040;
-import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
/**
* This class represents a {@code stop-time} parameter as defined in
* <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.8">RFC8040 section 4.8.8</a>.
*/
-@NonNullByDefault
public final class StopTimeParameter extends AbstractReplayParameter {
private StopTimeParameter(final DateAndTime value) {
super(value);
}
- public static StopTimeParameter of(final DateAndTime value) {
+ public static @NonNull StopTimeParameter of(final DateAndTime value) {
return new StopTimeParameter(value);
}
- public static String uriName() {
+ public static @NonNull String uriName() {
return "stop-time";
}
- public static StopTimeParameter forUriValue(final String uriValue) {
+ public static @NonNull StopTimeParameter forUriValue(final String uriValue) {
return of(new DateAndTime(uriValue));
}
}
--- /dev/null
+/*
+ * 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.databind.jaxrs;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.function.Function;
+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.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.nb.rfc8040.FilterParameter;
+import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
+import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
+import org.opendaylight.restconf.nb.rfc8040.StopTimeParameter;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+
+@Beta
+public final class UriInfoSupport {
+ private UriInfoSupport() {
+ // Utility class
+ }
+
+ public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
+ StartTimeParameter startTime = null;
+ StopTimeParameter stopTime = null;
+ FilterParameter filter = null;
+ boolean skipNotificationData = false;
+
+ for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+ final String paramName = entry.getKey();
+ final List<String> paramValues = entry.getValue();
+
+ try {
+ if (paramName.equals(StartTimeParameter.uriName())) {
+ startTime = optionalParam(StartTimeParameter::forUriValue, paramName, paramValues);
+ break;
+ } else if (paramName.equals(StopTimeParameter.uriName())) {
+ stopTime = optionalParam(StopTimeParameter::forUriValue, paramName, paramValues);
+ break;
+ } else if (paramName.equals(FilterParameter.uriName())) {
+ filter = optionalParam(FilterParameter::forUriValue, paramName, paramValues);
+ } else if (paramName.equals("odl-skip-notification-data")) {
+ // FIXME: this should be properly encapsulated in SkipNotificatioDataParameter
+ skipNotificationData = Boolean.parseBoolean(optionalParam(paramName, paramValues));
+ } else {
+ throw new RestconfDocumentedException("Bad parameter used with notifications: " + paramName);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(), e);
+ }
+ }
+
+ try {
+ return NotificationQueryParams.of(startTime, stopTime, filter, skipNotificationData);
+ } 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) {
+ switch (values.size()) {
+ case 0:
+ return null;
+ case 1:
+ return requireNonNull(values.get(0));
+ default:
+ throw new RestconfDocumentedException("Parameter " + name + " can appear at most once in request URI",
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
+ }
+ }
+
+ private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
+ final List<String> values) {
+ final String str = optionalParam(name, values);
+ return str == null ? null : factory.apply(str);
+ }
+}
--- /dev/null
+/*
+ * 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 holding JAX-RS data binding components.
+ */
+package org.opendaylight.restconf.nb.rfc8040.databind.jaxrs;
\ No newline at end of file
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.handlers.SchemaContextHandler;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
}
private static QueryParams checkQueryParameters(final UriInfo uriInfo) {
- boolean insertUsed = false;
- boolean pointUsed = false;
InsertParameter insert = null;
PointParameter point = null;
for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
final String uriName = entry.getKey();
- if (InsertParameter.uriName().equals(uriName)) {
- if (insertUsed) {
- throw new RestconfDocumentedException("Insert parameter can be used only once.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
-
- insertUsed = true;
- final String str = entry.getValue().get(0);
- insert = InsertParameter.forUriValue(str);
- if (insert == null) {
- throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+ 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)) {
- if (pointUsed) {
- throw new RestconfDocumentedException("Point parameter can be used only once.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+ final String str = UriInfoSupport.optionalParam(uriName, paramValues);
+ if (str != null) {
+ point = PointParameter.forUriValue(str);
}
-
- pointUsed = true;
- point = PointParameter.forUriValue(entry.getValue().get(0));
} else {
throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
}
}
- checkQueryParams(insertUsed, pointUsed, insert);
- return new QueryParams(insert, point);
- }
-
- private static void checkQueryParams(final boolean insertUsed, final boolean pointUsed,
- final InsertParameter insert) {
- if (pointUsed) {
- if (!insertUsed) {
- throw new RestconfDocumentedException("Point parameter can't be used without Insert parameter.",
- ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
- }
- if (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);
- }
+ // 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);
}
@Override
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.handlers.SchemaContextHandler;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
import org.opendaylight.restconf.nb.rfc8040.rests.services.api.RestconfStreamsSubscriptionService;
@Override
public NormalizedNodePayload subscribeToStream(final String identifier, final UriInfo uriInfo) {
- final NotificationQueryParams notificationQueryParams = NotificationQueryParams.fromUriInfo(uriInfo);
+ final NotificationQueryParams notificationQueryParams = UriInfoSupport.newNotificationQueryParams(uriInfo);
final URI response;
if (identifier.contains(RestconfStreamsConstants.DATA_SUBSCRIPTION)) {
*/
package org.opendaylight.restconf.nb.rfc8040.rests.utils;
-import static com.google.common.base.Verify.verifyNotNull;
+import static org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.UriInfoSupport.getSingleParameter;
import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
}
}
- @VisibleForTesting
- static @Nullable String getSingleParameter(final MultivaluedMap<String, String> params, final String name) {
- final var values = params.get(name);
- if (values == null) {
- return null;
- }
-
- switch (values.size()) {
- case 0:
- return null;
- case 1:
- return verifyNotNull(values.get(0));
- default:
- throw new RestconfDocumentedException("Parameter " + name + " 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.
*
--- /dev/null
+/*
+ * 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.databind.jaxrs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import java.util.List;
+import javax.ws.rs.core.MultivaluedHashMap;
+import org.junit.Test;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfError;
+import org.opendaylight.restconf.nb.rfc8040.ContentParameter;
+import org.opendaylight.yangtools.yang.common.ErrorTag;
+import org.opendaylight.yangtools.yang.common.ErrorType;
+
+public class UriInfoSupportTest {
+ /**
+ * Test when parameter is present at most once.
+ */
+ @Test
+ public void getSingleParameterTest() {
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+ parameters.putSingle(ContentParameter.uriName(), "all");
+ assertEquals("all", UriInfoSupport.getSingleParameter(parameters, ContentParameter.uriName()));
+ }
+
+ /**
+ * Test when parameter is present more than once.
+ */
+ @Test
+ public void getSingleParameterNegativeTest() {
+ final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
+ parameters.put(ContentParameter.uriName(), List.of("config", "nonconfig", "all"));
+
+ final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
+ () -> UriInfoSupport.getSingleParameter(parameters, ContentParameter.uriName()));
+ final List<RestconfError> errors = ex.getErrors();
+ assertEquals(1, errors.size());
+
+ final RestconfError error = errors.get(0);
+ assertEquals("Error type is not correct", ErrorType.PROTOCOL, error.getErrorType());
+ assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, error.getErrorTag());
+ }
+}
assertFalse(writerParameters.isTagged());
}
- /**
- * Test when parameter is present at most once.
- */
- @Test
- public void getSingleParameterTest() {
- final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
- parameters.putSingle(ContentParameter.uriName(), "all");
- assertEquals("all", ReadDataTransactionUtil.getSingleParameter(parameters, ContentParameter.uriName()));
- }
-
- /**
- * Test when parameter is present more than once.
- */
- @Test
- public void getSingleParameterNegativeTest() {
- final MultivaluedHashMap<String, String> parameters = new MultivaluedHashMap<>();
- parameters.put(ContentParameter.uriName(), List.of("config", "nonconfig", "all"));
-
- final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
- () -> ReadDataTransactionUtil.getSingleParameter(parameters, ContentParameter.uriName()));
- final List<RestconfError> errors = ex.getErrors();
- assertEquals(1, errors.size());
-
- final RestconfError error = errors.get(0);
- assertEquals("Error type is not correct", ErrorType.PROTOCOL, error.getErrorType());
- assertEquals("Error tag is not correct", ErrorTag.INVALID_VALUE, error.getErrorTag());
- }
-
/**
* Test when all parameters are allowed.
*/