InstanceIdentifierContext does not take generics
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / databind / jaxrs / QueryParams.java
index fef0b8757226d96b39abf6ccbde0753dd019cc36..fe00d8474c723c1b73c78e0d73d5a7bddba6834d 100644 (file)
@@ -8,19 +8,13 @@
 package org.opendaylight.restconf.nb.rfc8040.databind.jaxrs;
 
 import static java.util.Objects.requireNonNull;
-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.Beta;
 import com.google.common.annotations.VisibleForTesting;
-import java.text.ParseException;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map.Entry;
 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;
@@ -32,27 +26,33 @@ import org.opendaylight.restconf.nb.rfc8040.DepthParam;
 import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
 import org.opendaylight.restconf.nb.rfc8040.FilterParam;
 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
+import org.opendaylight.restconf.nb.rfc8040.LeafNodesOnlyParam;
 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
 import org.opendaylight.restconf.nb.rfc8040.PointParam;
+import org.opendaylight.restconf.nb.rfc8040.PrettyPrintParam;
 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
+import org.opendaylight.restconf.nb.rfc8040.SkipNotificationDataParam;
 import org.opendaylight.restconf.nb.rfc8040.StartTimeParam;
 import org.opendaylight.restconf.nb.rfc8040.StopTimeParam;
 import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParam;
 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
 import org.opendaylight.yangtools.yang.common.ErrorTag;
 import org.opendaylight.yangtools.yang.common.ErrorType;
 
 @Beta
 public final class QueryParams {
-    private static final Set<String> ALLOWED_PARAMETERS = Set.of(ContentParam.uriName(), DepthParam.uriName(),
-        FieldsParam.uriName(), WithDefaultsParam.uriName());
-    private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParam.values())
-        .map(ContentParam::paramValue)
-        .collect(Collectors.toUnmodifiableList());
-    private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParam.values())
-        .map(WithDefaultsParam::paramValue)
-        .collect(Collectors.toUnmodifiableList());
+    private static final Set<String> KNOWN_PARAMS = Set.of(
+        // Read data
+        ContentParam.uriName, DepthParam.uriName, FieldsParam.uriName, WithDefaultsParam.uriName,
+        PrettyPrintParam.uriName,
+        // Modify data
+        InsertParam.uriName, PointParam.uriName,
+        // Notifications
+        FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName,
+        LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName);
 
     private QueryParams() {
         // Utility class
@@ -62,49 +62,57 @@ public final class QueryParams {
         StartTimeParam startTime = null;
         StopTimeParam stopTime = null;
         FilterParam filter = null;
-        boolean skipNotificationData = false;
+        LeafNodesOnlyParam leafNodesOnly = null;
+        SkipNotificationDataParam skipNotificationData = null;
 
-        for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+        for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
             final String paramName = entry.getKey();
             final List<String> paramValues = entry.getValue();
 
             try {
-                if (paramName.equals(StartTimeParam.uriName())) {
-                    startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
-                    break;
-                } else if (paramName.equals(StopTimeParam.uriName())) {
-                    stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
-                    break;
-                } else if (paramName.equals(FilterParam.uriName())) {
-                    filter = optionalParam(FilterParam::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);
+                switch (paramName) {
+                    case FilterParam.uriName:
+                        filter = optionalParam(FilterParam::forUriValue, paramName, paramValues);
+                        break;
+                    case StartTimeParam.uriName:
+                        startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
+                        break;
+                    case StopTimeParam.uriName:
+                        stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
+                        break;
+                    case LeafNodesOnlyParam.uriName:
+                        leafNodesOnly = optionalParam(LeafNodesOnlyParam::forUriValue, paramName, paramValues);
+                        break;
+                    case SkipNotificationDataParam.uriName:
+                        skipNotificationData = optionalParam(SkipNotificationDataParam::forUriValue, paramName,
+                            paramValues);
+                        break;
+                    default:
+                        throw unhandledParam("notification", paramName);
                 }
             } catch (IllegalArgumentException e) {
-                throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(), e);
+                throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
+                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
             }
         }
 
         try {
-            return NotificationQueryParams.of(startTime, stopTime, filter, skipNotificationData);
+            return NotificationQueryParams.of(startTime, stopTime, filter, leafNodesOnly, skipNotificationData);
         } catch (IllegalArgumentException e) {
             throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
         }
     }
 
     public static QueryParameters newQueryParameters(final ReadDataParams params,
-            final InstanceIdentifierContext<?> identifier) {
+            final InstanceIdentifierContext identifier) {
         final var fields = params.fields();
         if (fields == null) {
             return QueryParameters.of(params);
         }
 
         return identifier.getMountPoint() != null
-            ? QueryParameters.ofFieldPaths(params, parseFieldsPaths(identifier, fields.paramValue()))
-                : QueryParameters.ofFields(params, parseFieldsParameter(identifier, fields.paramValue()));
+            ? QueryParameters.ofFieldPaths(params, NetconfFieldsTranslator.translate(identifier, fields))
+                : QueryParameters.ofFields(params, WriterFieldsTranslator.translate(identifier, fields));
     }
 
     /**
@@ -114,79 +122,66 @@ public final class QueryParams {
      * @return {@link ReadDataParams}
      */
     public static @NonNull ReadDataParams newReadDataParams(final UriInfo uriInfo) {
-        // check only allowed parameters
-        final MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
-        checkParametersTypes(queryParams.keySet(), ALLOWED_PARAMETERS);
-
-        // check and set content
-        final String contentStr = getSingleParameter(queryParams, ContentParam.uriName());
-        final ContentParam content = contentStr == null ? ContentParam.ALL
-            : RestconfDocumentedException.throwIfNull(ContentParam.forUriValue(contentStr),
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
-                "Invalid content parameter: %s, allowed values are %s", contentStr, POSSIBLE_CONTENT);
+        ContentParam content = ContentParam.ALL;
+        DepthParam depth = null;
+        FieldsParam fields = null;
+        WithDefaultsParam withDefaults = null;
+        PrettyPrintParam prettyPrint = null;
+        boolean tagged = false;
+
+        for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
+            final String paramName = entry.getKey();
+            final List<String> paramValues = entry.getValue();
 
-        // check and set depth
-        final DepthParam depth;
-        final String depthStr = getSingleParameter(queryParams, DepthParam.uriName());
-        if (depthStr != null) {
             try {
-                depth = DepthParam.forUriValue(depthStr);
+                switch (paramName) {
+                    case ContentParam.uriName:
+                        content = optionalParam(ContentParam::forUriValue, paramName, paramValues);
+                        break;
+                    case DepthParam.uriName:
+                        final String depthStr = optionalParam(paramName, paramValues);
+                        try {
+                            depth = DepthParam.forUriValue(depthStr);
+                        } catch (IllegalArgumentException e) {
+                            throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
+                                ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depthStr, null,
+                                "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
+                        }
+                        break;
+                    case FieldsParam.uriName:
+                        fields = optionalParam(FieldsParam::forUriValue, paramName, paramValues);
+                        break;
+                    case WithDefaultsParam.uriName:
+                        final var defaultsVal = optionalParam(WithDefaultsParam::forUriValue, paramName, paramValues);
+                        if (defaultsVal != null) {
+                            switch (defaultsVal) {
+                                case REPORT_ALL:
+                                    withDefaults = null;
+                                    tagged = false;
+                                    break;
+                                case REPORT_ALL_TAGGED:
+                                    withDefaults = null;
+                                    tagged = true;
+                                    break;
+                                default:
+                                    withDefaults = defaultsVal;
+                                    tagged = false;
+                            }
+                        }
+                        break;
+                    case PrettyPrintParam.uriName:
+                        prettyPrint = optionalParam(PrettyPrintParam::forUriValue, paramName, paramValues);
+                        break;
+                    default:
+                        throw unhandledParam("read", paramName);
+                }
             } catch (IllegalArgumentException e) {
-                throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
-                    "Invalid depth parameter: " + depthStr, null,
-                    "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
-            }
-        } else {
-            depth = null;
-        }
-
-        // check and set fields
-        final FieldsParam fields;
-        final String fieldsStr = getSingleParameter(queryParams, FieldsParam.uriName());
-        if (fieldsStr != null) {
-            try {
-                fields = FieldsParam.parse(fieldsStr);
-            } catch (ParseException e) {
-                throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
-                    "Invalid filds parameter: " + fieldsStr));
+                throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
+                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
             }
-        } else {
-            fields = null;
         }
 
-        // check and set withDefaults parameter
-        final WithDefaultsParam withDefaults;
-        final boolean tagged;
-
-        final String withDefaultsStr = getSingleParameter(queryParams, WithDefaultsParam.uriName());
-        if (withDefaultsStr != null) {
-            final WithDefaultsParam val = WithDefaultsParam.forUriValue(withDefaultsStr);
-            if (val == null) {
-                throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
-                    "Invalid with-defaults parameter: " + withDefaultsStr, null,
-                    "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
-            }
-
-            switch (val) {
-                case REPORT_ALL:
-                    withDefaults = null;
-                    tagged = false;
-                    break;
-                case REPORT_ALL_TAGGED:
-                    withDefaults = null;
-                    tagged = true;
-                    break;
-                default:
-                    withDefaults = val;
-                    tagged = false;
-            }
-        } else {
-            withDefaults = null;
-            tagged = false;
-        }
-
-        // FIXME: recognize pretty-print here
-        return ReadDataParams.of(content, depth, fields, withDefaults, tagged, false);
+        return ReadDataParams.of(content, depth, fields, withDefaults, tagged, prettyPrint);
     }
 
     public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
@@ -194,25 +189,23 @@ public final class QueryParams {
         PointParam point = null;
 
         for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
-            final String uriName = entry.getKey();
+            final String paramName = entry.getKey();
             final List<String> paramValues = entry.getValue();
-            if (uriName.equals(InsertParam.uriName())) {
-                final String str = optionalParam(uriName, paramValues);
-                if (str != null) {
-                    insert = InsertParam.forUriValue(str);
-                    if (insert == null) {
-                        throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
-                            ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
-                    }
-                }
-            } else if (PointParam.uriName().equals(uriName)) {
-                final String str = optionalParam(uriName, paramValues);
-                if (str != null) {
-                    point = PointParam.forUriValue(str);
+
+            try {
+                switch (paramName) {
+                    case InsertParam.uriName:
+                        insert = optionalParam(InsertParam::forUriValue, paramName, paramValues);
+                        break;
+                    case PointParam.uriName:
+                        point = optionalParam(PointParam::forUriValue, paramName, paramValues);
+                        break;
+                    default:
+                        throw unhandledParam("write", paramName);
                 }
-            } else {
-                throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
-                    ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
+            } catch (IllegalArgumentException e) {
+                throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
+                    ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
             }
         }
 
@@ -223,30 +216,16 @@ public final class QueryParams {
         }
     }
 
-    /**
-     * 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 Set<String> usedParameters, final 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 operation: " + notAllowedParameters,
-                ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
-        }
+    private static RestconfDocumentedException unhandledParam(final String operation, final String name) {
+        return KNOWN_PARAMS.contains(name)
+            ? new RestconfDocumentedException("Invalid parameter in " + operation + ": " + name,
+                ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE)
+            : new RestconfDocumentedException("Unknown parameter in " + operation + ": " + name,
+                ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
     }
 
     @VisibleForTesting
-    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);
-    }
-
-    private static @Nullable String optionalParam(final String name, final List<String> values) {
+    static @Nullable String optionalParam(final String name, final List<String> values) {
         switch (values.size()) {
             case 0:
                 return null;