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;
12 import com.google.common.annotations.Beta;
13 import com.google.common.annotations.VisibleForTesting;
14 import java.text.ParseException;
15 import java.util.Arrays;
16 import java.util.List;
17 import java.util.Map.Entry;
19 import java.util.function.Function;
20 import java.util.stream.Collectors;
21 import javax.ws.rs.core.UriInfo;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
25 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
26 import org.opendaylight.restconf.common.errors.RestconfError;
27 import org.opendaylight.restconf.nb.rfc8040.ContentParam;
28 import org.opendaylight.restconf.nb.rfc8040.DepthParam;
29 import org.opendaylight.restconf.nb.rfc8040.FieldsParam;
30 import org.opendaylight.restconf.nb.rfc8040.FilterParam;
31 import org.opendaylight.restconf.nb.rfc8040.InsertParam;
32 import org.opendaylight.restconf.nb.rfc8040.LeafNodesOnlyParam;
33 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
34 import org.opendaylight.restconf.nb.rfc8040.PointParam;
35 import org.opendaylight.restconf.nb.rfc8040.PrettyPrintParam;
36 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
37 import org.opendaylight.restconf.nb.rfc8040.SkipNotificationDataParam;
38 import org.opendaylight.restconf.nb.rfc8040.StartTimeParam;
39 import org.opendaylight.restconf.nb.rfc8040.StopTimeParam;
40 import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParam;
41 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
42 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
43 import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
44 import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
45 import org.opendaylight.yangtools.yang.common.ErrorTag;
46 import org.opendaylight.yangtools.yang.common.ErrorType;
49 public final class QueryParams {
50 private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParam.values())
51 .map(ContentParam::paramValue)
52 .collect(Collectors.toUnmodifiableList());
53 private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParam.values())
54 .map(WithDefaultsParam::paramValue)
55 .collect(Collectors.toUnmodifiableList());
56 private static final Set<String> KNOWN_PARAMS = Set.of(
58 ContentParam.uriName, DepthParam.uriName, FieldsParam.uriName, WithDefaultsParam.uriName,
60 InsertParam.uriName, PointParam.uriName,
62 FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName ,"odl-skip-notification-data");
65 private QueryParams() {
69 public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
70 StartTimeParam startTime = null;
71 StopTimeParam stopTime = null;
72 FilterParam filter = null;
73 LeafNodesOnlyParam leafNodesOnly = null;
74 SkipNotificationDataParam skipNotificationData = null;
76 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
77 final String paramName = entry.getKey();
78 final List<String> paramValues = entry.getValue();
82 case FilterParam.uriName:
83 filter = optionalParam(FilterParam::forUriValue, paramName, paramValues);
85 case StartTimeParam.uriName:
86 startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
88 case StopTimeParam.uriName:
89 stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
91 case LeafNodesOnlyParam.uriName:
92 leafNodesOnly = optionalParam(LeafNodesOnlyParam::forUriValue, paramName, paramValues);
94 case SkipNotificationDataParam.uriName:
95 skipNotificationData = optionalParam(SkipNotificationDataParam::forUriValue, paramName,
99 throw unhandledParam("notification", paramName);
101 } catch (IllegalArgumentException e) {
102 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
103 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
108 return NotificationQueryParams.of(startTime, stopTime, filter, leafNodesOnly, skipNotificationData);
109 } catch (IllegalArgumentException e) {
110 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
114 public static QueryParameters newQueryParameters(final ReadDataParams params,
115 final InstanceIdentifierContext<?> identifier) {
116 final var fields = params.fields();
117 if (fields == null) {
118 return QueryParameters.of(params);
121 return identifier.getMountPoint() != null
122 ? QueryParameters.ofFieldPaths(params, NetconfFieldsTranslator.translate(identifier, fields))
123 : QueryParameters.ofFields(params, WriterFieldsTranslator.translate(identifier, fields));
127 * Parse parameters from URI request and check their types and values.
129 * @param uriInfo URI info
130 * @return {@link ReadDataParams}
132 public static @NonNull ReadDataParams newReadDataParams(final UriInfo uriInfo) {
133 ContentParam content = ContentParam.ALL;
134 DepthParam depth = null;
135 FieldsParam fields = null;
136 WithDefaultsParam withDefaults = null;
137 PrettyPrintParam prettyPrint = null;
138 boolean tagged = false;
140 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
141 final String paramName = entry.getKey();
142 final List<String> paramValues = entry.getValue();
145 case ContentParam.uriName:
146 final String contentStr = optionalParam(paramName, paramValues);
147 if (contentStr != null) {
148 content = RestconfDocumentedException.throwIfNull(ContentParam.forUriValue(contentStr),
149 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
150 "Invalid content parameter: %s, allowed values are %s", contentStr, POSSIBLE_CONTENT);
153 case DepthParam.uriName:
154 final String depthStr = optionalParam(paramName, paramValues);
156 depth = DepthParam.forUriValue(depthStr);
157 } catch (IllegalArgumentException e) {
158 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
159 ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depthStr, null,
160 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
163 case FieldsParam.uriName:
164 final String fieldsStr = optionalParam(paramName, paramValues);
165 if (fieldsStr != null) {
167 fields = FieldsParam.parse(fieldsStr);
168 } catch (ParseException e) {
169 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
170 ErrorTag.INVALID_VALUE, "Invalid filds parameter: " + fieldsStr));
174 case WithDefaultsParam.uriName:
175 final String withDefaultsStr = optionalParam(paramName, paramValues);
176 if (withDefaultsStr != null) {
177 final WithDefaultsParam val = WithDefaultsParam.forUriValue(withDefaultsStr);
179 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL,
180 ErrorTag.INVALID_VALUE, "Invalid with-defaults parameter: " + withDefaultsStr, null,
181 "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
189 case REPORT_ALL_TAGGED:
199 case PrettyPrintParam.uriName:
200 prettyPrint = optionalParam(PrettyPrintParam::forUriValue, paramName, paramValues);
203 throw unhandledParam("read", paramName);
207 return ReadDataParams.of(content, depth, fields, withDefaults, tagged, prettyPrint);
210 public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
211 InsertParam insert = null;
212 PointParam point = null;
214 for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
215 final String uriName = entry.getKey();
216 final List<String> paramValues = entry.getValue();
218 case InsertParam.uriName:
219 final String instartStr = optionalParam(uriName, paramValues);
220 if (instartStr != null) {
221 insert = InsertParam.forUriValue(instartStr);
222 if (insert == null) {
223 throw new RestconfDocumentedException(
224 "Unrecognized insert parameter value '" + instartStr + "'", ErrorType.PROTOCOL,
225 ErrorTag.BAD_ELEMENT);
229 case PointParam.uriName:
230 final String pointStr = optionalParam(uriName, paramValues);
231 if (pointStr != null) {
232 point = PointParam.forUriValue(pointStr);
236 throw unhandledParam("write", uriName);
241 return WriteDataParams.of(insert, point);
242 } catch (IllegalArgumentException e) {
243 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
247 private static RestconfDocumentedException unhandledParam(final String operation, final String name) {
248 return KNOWN_PARAMS.contains(name)
249 ? new RestconfDocumentedException("Invalid parameter in " + operation + ": " + name,
250 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE)
251 : new RestconfDocumentedException("Unknown parameter in " + operation + ": " + name,
252 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
256 static @Nullable String optionalParam(final String name, final List<String> values) {
257 switch (values.size()) {
261 return requireNonNull(values.get(0));
263 throw new RestconfDocumentedException("Parameter " + name + " can appear at most once in request URI",
264 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
268 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
269 final List<String> values) {
270 final String str = optionalParam(name, values);
271 return str == null ? null : factory.apply(str);