2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.databind.jaxrs;
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsParameter;
12 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserFieldsParameter.parseFieldsPaths;
14 import com.google.common.annotations.Beta;
15 import com.google.common.annotations.VisibleForTesting;
16 import java.text.ParseException;
17 import java.util.Arrays;
18 import java.util.List;
19 import java.util.Map.Entry;
21 import java.util.function.Function;
22 import java.util.stream.Collectors;
23 import javax.ws.rs.core.UriInfo;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
27 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
28 import org.opendaylight.restconf.common.errors.RestconfError;
29 import org.opendaylight.restconf.nb.rfc8040.ContentParam;
30 import org.opendaylight.restconf.nb.rfc8040.DepthParam;
31 import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
32 import org.opendaylight.restconf.nb.rfc8040.FilterParam;
33 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
34 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
35 import org.opendaylight.restconf.nb.rfc8040.PointParam;
36 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
37 import org.opendaylight.restconf.nb.rfc8040.StartTimeParam;
38 import org.opendaylight.restconf.nb.rfc8040.StopTimeParam;
39 import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParam;
40 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
41 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
42 import org.opendaylight.yangtools.yang.common.ErrorTag;
43 import org.opendaylight.yangtools.yang.common.ErrorType;
46 public final class QueryParams {
47 private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParam.values())
48 .map(ContentParam::paramValue)
49 .collect(Collectors.toUnmodifiableList());
50 private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParam.values())
51 .map(WithDefaultsParam::paramValue)
52 .collect(Collectors.toUnmodifiableList());
53 private static final Set<String> KNOWN_PARAMS = Set.of(
55 ContentParam.uriName, DepthParam.uriName, FieldsParam.uriName, WithDefaultsParam.uriName,
57 InsertParam.uriName, PointParam.uriName,
59 FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName ,"odl-skip-notification-data");
62 private QueryParams() {
66 public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
67 StartTimeParam startTime = null;
68 StopTimeParam stopTime = null;
69 FilterParam filter = null;
70 boolean skipNotificationData = false;
72 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
73 final String paramName = entry.getKey();
74 final List<String> paramValues = entry.getValue();
78 case FilterParam.uriName:
79 filter = optionalParam(FilterParam::forUriValue, paramName, paramValues);
81 case StartTimeParam.uriName:
82 startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
84 case StopTimeParam.uriName:
85 stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
87 case "odl-skip-notification-data":
88 // FIXME: this should be properly encapsulated in SkipNotificatioDataParameter
89 skipNotificationData = Boolean.parseBoolean(optionalParam(paramName, paramValues));
92 throw unhandledParam("notification", paramName);
94 } catch (IllegalArgumentException e) {
95 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
96 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
101 return NotificationQueryParams.of(startTime, stopTime, filter, skipNotificationData);
102 } catch (IllegalArgumentException e) {
103 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
107 public static QueryParameters newQueryParameters(final ReadDataParams params,
108 final InstanceIdentifierContext<?> identifier) {
109 final var fields = params.fields();
110 if (fields == null) {
111 return QueryParameters.of(params);
114 return identifier.getMountPoint() != null
115 ? QueryParameters.ofFieldPaths(params, parseFieldsPaths(identifier, fields.paramValue()))
116 : QueryParameters.ofFields(params, parseFieldsParameter(identifier, fields.paramValue()));
120 * Parse parameters from URI request and check their types and values.
122 * @param uriInfo URI info
123 * @return {@link ReadDataParams}
125 public static @NonNull ReadDataParams newReadDataParams(final UriInfo uriInfo) {
126 ContentParam content = ContentParam.ALL;
127 DepthParam depth = null;
128 FieldsParam fields = null;
129 WithDefaultsParam withDefaults = null;
130 boolean tagged = false;
132 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
133 final String paramName = entry.getKey();
134 final List<String> paramValues = entry.getValue();
137 case ContentParam.uriName:
138 final String contentStr = optionalParam(paramName, paramValues);
139 if (contentStr != null) {
140 content = RestconfDocumentedException.throwIfNull(ContentParam.forUriValue(contentStr),
141 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
142 "Invalid content parameter: %s, allowed values are %s", contentStr, POSSIBLE_CONTENT);
145 case DepthParam.uriName:
146 final String depthStr = optionalParam(paramName, paramValues);
148 depth = DepthParam.forUriValue(depthStr);
149 } catch (IllegalArgumentException e) {
150 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
151 ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depthStr, null,
152 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
155 case FieldsParam.uriName:
156 final String fieldsStr = optionalParam(paramName, paramValues);
157 if (fieldsStr != null) {
159 fields = FieldsParam.parse(fieldsStr);
160 } catch (ParseException e) {
161 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
162 ErrorTag.INVALID_VALUE, "Invalid filds parameter: " + fieldsStr));
166 case WithDefaultsParam.uriName:
167 final String withDefaultsStr = optionalParam(paramName, paramValues);
168 if (withDefaultsStr != null) {
169 final WithDefaultsParam val = WithDefaultsParam.forUriValue(withDefaultsStr);
171 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL,
172 ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + withDefaultsStr, null,
173 "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
181 case REPORT_ALL_TAGGED:
192 // FIXME: recognize pretty-print here
193 throw unhandledParam("read", paramName);
197 return ReadDataParams.of(content, depth, fields, withDefaults, tagged, false);
200 public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
201 InsertParam insert = null;
202 PointParam point = null;
204 for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
205 final String uriName = entry.getKey();
206 final List<String> paramValues = entry.getValue();
208 case InsertParam.uriName:
209 final String instartStr = optionalParam(uriName, paramValues);
210 if (instartStr != null) {
211 insert = InsertParam.forUriValue(instartStr);
212 if (insert == null) {
213 throw new RestconfDocumentedException(
214 "Unrecognized insert parameter value '" + instartStr + "'", ErrorType.PROTOCOL,
215 ErrorTag.BAD_ELEMENT);
219 case PointParam.uriName:
220 final String pointStr = optionalParam(uriName, paramValues);
221 if (pointStr != null) {
222 point = PointParam.forUriValue(pointStr);
226 throw unhandledParam("write", uriName);
231 return WriteDataParams.of(insert, point);
232 } catch (IllegalArgumentException e) {
233 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
237 private static RestconfDocumentedException unhandledParam(final String operation, final String name) {
238 return KNOWN_PARAMS.contains(name)
239 ? new RestconfDocumentedException("Invalid parameter in " + operation + ": " + name,
240 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE)
241 : new RestconfDocumentedException("Unknown parameter in " + operation + ": " + name,
242 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
246 static @Nullable String optionalParam(final String name, final List<String> values) {
247 switch (values.size()) {
251 return requireNonNull(values.get(0));
253 throw new RestconfDocumentedException("Parameter " + name + " can appear at most once in request URI",
254 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
258 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
259 final List<String> values) {
260 final String str = optionalParam(name, values);
261 return str == null ? null : factory.apply(str);