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 Set<String> ALLOWED_PARAMETERS = Set.of(ContentParam.uriName(), DepthParam.uriName(),
48 FieldsParam.uriName(), WithDefaultsParam.uriName());
49 private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParam.values())
50 .map(ContentParam::paramValue)
51 .collect(Collectors.toUnmodifiableList());
52 private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParam.values())
53 .map(WithDefaultsParam::paramValue)
54 .collect(Collectors.toUnmodifiableList());
56 private QueryParams() {
60 public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
61 StartTimeParam startTime = null;
62 StopTimeParam stopTime = null;
63 FilterParam filter = null;
64 boolean skipNotificationData = false;
66 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
67 final String paramName = entry.getKey();
68 final List<String> paramValues = entry.getValue();
71 if (paramName.equals(StartTimeParam.uriName())) {
72 startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
74 } else if (paramName.equals(StopTimeParam.uriName())) {
75 stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
77 } else if (paramName.equals(FilterParam.uriName())) {
78 filter = optionalParam(FilterParam::forUriValue, paramName, paramValues);
79 } else if (paramName.equals("odl-skip-notification-data")) {
80 // FIXME: this should be properly encapsulated in SkipNotificatioDataParameter
81 skipNotificationData = Boolean.parseBoolean(optionalParam(paramName, paramValues));
83 throw new RestconfDocumentedException("Bad parameter used with notifications: " + paramName,
84 ErrorType.PROTOCOL, ErrorTag. UNKNOWN_ATTRIBUTE);
86 } catch (IllegalArgumentException e) {
87 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(), e);
92 return NotificationQueryParams.of(startTime, stopTime, filter, skipNotificationData);
93 } catch (IllegalArgumentException e) {
94 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
98 public static QueryParameters newQueryParameters(final ReadDataParams params,
99 final InstanceIdentifierContext<?> identifier) {
100 final var fields = params.fields();
101 if (fields == null) {
102 return QueryParameters.of(params);
105 return identifier.getMountPoint() != null
106 ? QueryParameters.ofFieldPaths(params, parseFieldsPaths(identifier, fields.paramValue()))
107 : QueryParameters.ofFields(params, parseFieldsParameter(identifier, fields.paramValue()));
111 * Parse parameters from URI request and check their types and values.
113 * @param uriInfo URI info
114 * @return {@link ReadDataParams}
116 public static @NonNull ReadDataParams newReadDataParams(final UriInfo uriInfo) {
117 ContentParam content = ContentParam.ALL;
118 DepthParam depth = null;
119 FieldsParam fields = null;
120 WithDefaultsParam withDefaults = null;
121 boolean tagged = false;
123 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
124 final String paramName = entry.getKey();
125 final List<String> paramValues = entry.getValue();
127 if (paramName.equals(ContentParam.uriName())) {
128 final String str = optionalParam(paramName, paramValues);
130 content = RestconfDocumentedException.throwIfNull(ContentParam.forUriValue(str),
131 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
132 "Invalid content parameter: %s, allowed values are %s", str, POSSIBLE_CONTENT);
134 } else if (paramName.equals(DepthParam.uriName())) {
135 final String str = optionalParam(paramName, paramValues);
137 depth = DepthParam.forUriValue(str);
138 } catch (IllegalArgumentException e) {
139 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
140 ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + str, null,
141 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
143 } else if (paramName.equals(FieldsParam.uriName())) {
144 final String str = optionalParam(paramName, paramValues);
147 fields = FieldsParam.parse(str);
148 } catch (ParseException e) {
149 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
150 ErrorTag.INVALID_VALUE, "Invalid filds parameter: " + str));
153 } else if (paramName.equals(WithDefaultsParam.uriName())) {
154 final String str = optionalParam(paramName, paramValues);
156 final WithDefaultsParam val = WithDefaultsParam.forUriValue(str);
158 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL,
159 ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + str, null,
160 "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
168 case REPORT_ALL_TAGGED:
178 // FIXME: recognize pretty-print here
179 throw new RestconfDocumentedException("Not allowed parameter for read operation: " + paramName,
180 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
184 return ReadDataParams.of(content, depth, fields, withDefaults, tagged, false);
187 public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
188 InsertParam insert = null;
189 PointParam point = null;
191 for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
192 final String uriName = entry.getKey();
193 final List<String> paramValues = entry.getValue();
194 if (uriName.equals(InsertParam.uriName())) {
195 final String str = optionalParam(uriName, paramValues);
197 insert = InsertParam.forUriValue(str);
198 if (insert == null) {
199 throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
200 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
203 } else if (PointParam.uriName().equals(uriName)) {
204 final String str = optionalParam(uriName, paramValues);
206 point = PointParam.forUriValue(str);
209 throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
210 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
215 return WriteDataParams.of(insert, point);
216 } catch (IllegalArgumentException e) {
217 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
222 static @Nullable String optionalParam(final String name, final List<String> values) {
223 switch (values.size()) {
227 return requireNonNull(values.get(0));
229 throw new RestconfDocumentedException("Parameter " + name + " can appear at most once in request URI",
230 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
234 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
235 final List<String> values) {
236 final String str = optionalParam(name, values);
237 return str == null ? null : factory.apply(str);