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.util.List;
15 import java.util.Map.Entry;
17 import java.util.function.Function;
18 import javax.ws.rs.core.UriInfo;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
22 import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
23 import org.opendaylight.restconf.api.query.ContentParam;
24 import org.opendaylight.restconf.api.query.DepthParam;
25 import org.opendaylight.restconf.api.query.FieldsParam;
26 import org.opendaylight.restconf.api.query.FilterParam;
27 import org.opendaylight.restconf.api.query.InsertParam;
28 import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
29 import org.opendaylight.restconf.api.query.PointParam;
30 import org.opendaylight.restconf.api.query.PrettyPrintParam;
31 import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
32 import org.opendaylight.restconf.api.query.StartTimeParam;
33 import org.opendaylight.restconf.api.query.StopTimeParam;
34 import org.opendaylight.restconf.api.query.WithDefaultsParam;
35 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
36 import org.opendaylight.restconf.common.errors.RestconfError;
37 import org.opendaylight.restconf.nb.rfc8040.Insert;
38 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
39 import org.opendaylight.restconf.nb.rfc8040.ReadDataParams;
40 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
41 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
42 import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
43 import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
44 import org.opendaylight.yangtools.yang.common.ErrorTag;
45 import org.opendaylight.yangtools.yang.common.ErrorType;
48 public final class QueryParams {
49 private static final Set<String> KNOWN_PARAMS = Set.of(
51 ContentParam.uriName, DepthParam.uriName, FieldsParam.uriName, WithDefaultsParam.uriName,
52 PrettyPrintParam.uriName,
54 InsertParam.uriName, PointParam.uriName,
56 FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName,
57 LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName, ChangedLeafNodesOnlyParam.uriName,
58 ChildNodesOnlyParam.uriName);
60 private QueryParams() {
64 public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
65 StartTimeParam startTime = null;
66 StopTimeParam stopTime = null;
67 FilterParam filter = null;
68 LeafNodesOnlyParam leafNodesOnly = null;
69 SkipNotificationDataParam skipNotificationData = null;
70 ChangedLeafNodesOnlyParam changedLeafNodesOnly = null;
71 ChildNodesOnlyParam childNodesOnly = null;
73 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
74 final String paramName = entry.getKey();
75 final List<String> paramValues = entry.getValue();
79 case FilterParam.uriName:
80 filter = optionalParam(FilterParam::forUriValue, paramName, paramValues);
82 case StartTimeParam.uriName:
83 startTime = optionalParam(StartTimeParam::forUriValue, paramName, paramValues);
85 case StopTimeParam.uriName:
86 stopTime = optionalParam(StopTimeParam::forUriValue, paramName, paramValues);
88 case LeafNodesOnlyParam.uriName:
89 leafNodesOnly = optionalParam(LeafNodesOnlyParam::forUriValue, paramName, paramValues);
91 case SkipNotificationDataParam.uriName:
92 skipNotificationData = optionalParam(SkipNotificationDataParam::forUriValue, paramName,
95 case ChangedLeafNodesOnlyParam.uriName:
96 changedLeafNodesOnly = optionalParam(ChangedLeafNodesOnlyParam::forUriValue, paramName,
99 case ChildNodesOnlyParam.uriName:
100 childNodesOnly = optionalParam(ChildNodesOnlyParam::forUriValue, paramName, paramValues);
103 throw unhandledParam("notification", paramName);
105 } catch (IllegalArgumentException e) {
106 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
107 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
112 return NotificationQueryParams.of(startTime, stopTime, filter, leafNodesOnly, skipNotificationData,
113 changedLeafNodesOnly, childNodesOnly);
114 } catch (IllegalArgumentException e) {
115 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
119 public static QueryParameters newQueryParameters(final ReadDataParams params,
120 final InstanceIdentifierContext identifier) {
121 final var fields = params.fields();
122 if (fields == null) {
123 return QueryParameters.of(params);
126 return identifier.getMountPoint() != null
127 ? QueryParameters.ofFieldPaths(params, NetconfFieldsTranslator.translate(identifier, fields))
128 : QueryParameters.ofFields(params, WriterFieldsTranslator.translate(identifier, fields));
132 * Parse parameters from URI request and check their types and values.
134 * @param uriInfo URI info
135 * @return {@link ReadDataParams}
137 public static @NonNull ReadDataParams newReadDataParams(final UriInfo uriInfo) {
138 ContentParam content = ContentParam.ALL;
139 DepthParam depth = null;
140 FieldsParam fields = null;
141 WithDefaultsParam withDefaults = null;
142 PrettyPrintParam prettyPrint = null;
144 for (Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
145 final String paramName = entry.getKey();
146 final List<String> paramValues = entry.getValue();
150 case ContentParam.uriName:
151 content = optionalParam(ContentParam::forUriValue, paramName, paramValues);
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 fields = optionalParam(FieldsParam::forUriValue, paramName, paramValues);
166 case WithDefaultsParam.uriName:
167 withDefaults = optionalParam(WithDefaultsParam::forUriValue, paramName, paramValues);
169 case PrettyPrintParam.uriName:
170 prettyPrint = optionalParam(PrettyPrintParam::forUriValue, paramName, paramValues);
173 throw unhandledParam("read", paramName);
175 } catch (IllegalArgumentException e) {
176 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
177 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
181 return new ReadDataParams(content, depth, fields, withDefaults, prettyPrint);
184 public static @Nullable Insert parseInsert(final UriInfo uriInfo) {
185 InsertParam insert = null;
186 PointParam point = null;
188 for (var entry : uriInfo.getQueryParameters().entrySet()) {
189 final var paramName = entry.getKey();
190 final var paramValues = entry.getValue();
194 case InsertParam.uriName:
195 insert = optionalParam(InsertParam::forUriValue, paramName, paramValues);
197 case PointParam.uriName:
198 point = optionalParam(PointParam::forUriValue, paramName, paramValues);
201 throw unhandledParam("write", paramName);
203 } catch (IllegalArgumentException e) {
204 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
205 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
210 return Insert.forParams(insert, point);
211 } catch (IllegalArgumentException e) {
212 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
216 private static RestconfDocumentedException unhandledParam(final String operation, final String name) {
217 return KNOWN_PARAMS.contains(name)
218 ? new RestconfDocumentedException("Invalid parameter in " + operation + ": " + name,
219 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE)
220 : new RestconfDocumentedException("Unknown parameter in " + operation + ": " + name,
221 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
225 static @Nullable String optionalParam(final String name, final List<String> values) {
226 return switch (values.size()) {
228 case 1 -> requireNonNull(values.get(0));
229 default -> throw new RestconfDocumentedException(
230 "Parameter " + name + " can appear at most once in request URI",
231 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
235 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
236 final List<String> values) {
237 final String str = optionalParam(name, values);
238 return str == null ? null : factory.apply(str);