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;
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
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));
}
/**
* @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) {
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);
}
}
}
}
- /**
- * 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;