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.util.Arrays;
17 import java.util.List;
18 import java.util.Map.Entry;
20 import java.util.function.Function;
21 import java.util.stream.Collectors;
22 import javax.ws.rs.core.MultivaluedMap;
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.ContentParameter;
30 import org.opendaylight.restconf.nb.rfc8040.DepthParameter;
31 import org.opendaylight.restconf.nb.rfc8040.FieldsParameter;
32 import org.opendaylight.restconf.nb.rfc8040.FilterParameter;
33 import org.opendaylight.restconf.nb.rfc8040.InsertParameter;
34 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
35 import org.opendaylight.restconf.nb.rfc8040.PointParameter;
36 import org.opendaylight.restconf.nb.rfc8040.StartTimeParameter;
37 import org.opendaylight.restconf.nb.rfc8040.StopTimeParameter;
38 import org.opendaylight.restconf.nb.rfc8040.WithDefaultsParameter;
39 import org.opendaylight.restconf.nb.rfc8040.WriteDataParams;
40 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
41 import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters.Builder;
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(ContentParameter.uriName(), DepthParameter.uriName(),
48 FieldsParameter.uriName(), WithDefaultsParameter.uriName());
49 private static final List<String> POSSIBLE_CONTENT = Arrays.stream(ContentParameter.values())
50 .map(ContentParameter::uriValue)
51 .collect(Collectors.toUnmodifiableList());
52 private static final List<String> POSSIBLE_WITH_DEFAULTS = Arrays.stream(WithDefaultsParameter.values())
53 .map(WithDefaultsParameter::uriValue)
54 .collect(Collectors.toUnmodifiableList());
56 private QueryParams() {
60 public static @NonNull NotificationQueryParams newNotificationQueryParams(final UriInfo uriInfo) {
61 StartTimeParameter startTime = null;
62 StopTimeParameter stopTime = null;
63 FilterParameter filter = null;
64 boolean skipNotificationData = false;
66 for (final 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(StartTimeParameter.uriName())) {
72 startTime = optionalParam(StartTimeParameter::forUriValue, paramName, paramValues);
74 } else if (paramName.equals(StopTimeParameter.uriName())) {
75 stopTime = optionalParam(StopTimeParameter::forUriValue, paramName, paramValues);
77 } else if (paramName.equals(FilterParameter.uriName())) {
78 filter = optionalParam(FilterParameter::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);
85 } catch (IllegalArgumentException e) {
86 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(), e);
91 return NotificationQueryParams.of(startTime, stopTime, filter, skipNotificationData);
92 } catch (IllegalArgumentException e) {
93 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
98 * Parse parameters from URI request and check their types and values.
100 * @param identifier {@link InstanceIdentifierContext}
101 * @param uriInfo URI info
102 * @return {@link QueryParameters}
104 public static QueryParameters newReadDataParams(final InstanceIdentifierContext<?> identifier,
105 final UriInfo uriInfo) {
106 if (uriInfo == null) {
107 return QueryParameters.empty();
110 // check only allowed parameters
111 final MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
112 checkParametersTypes(queryParams.keySet(), ALLOWED_PARAMETERS);
114 final Builder builder = QueryParameters.builder();
115 // check and set content
116 final String contentStr = getSingleParameter(queryParams, ContentParameter.uriName());
117 if (contentStr != null) {
118 builder.setContent(RestconfDocumentedException.throwIfNull(
119 ContentParameter.forUriValue(contentStr), ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
120 "Invalid content parameter: %s, allowed values are %s", contentStr, POSSIBLE_CONTENT));
123 // check and set depth
124 final String depthStr = getSingleParameter(queryParams, DepthParameter.uriName());
125 if (depthStr != null) {
127 builder.setDepth(DepthParameter.forUriValue(depthStr));
128 } catch (IllegalArgumentException e) {
129 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
130 "Invalid depth parameter: " + depthStr, null,
131 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
135 // check and set fields
136 final String fieldsStr = getSingleParameter(queryParams, FieldsParameter.uriName());
137 if (fieldsStr != null) {
138 // FIXME: parse a FieldsParameter instead
139 if (identifier.getMountPoint() != null) {
140 builder.setFieldPaths(parseFieldsPaths(identifier, fieldsStr));
142 builder.setFields(parseFieldsParameter(identifier, fieldsStr));
146 // check and set withDefaults parameter
147 final String withDefaultsStr = getSingleParameter(queryParams, WithDefaultsParameter.uriName());
148 if (withDefaultsStr != null) {
149 final WithDefaultsParameter val = WithDefaultsParameter.forUriValue(withDefaultsStr);
151 throw new RestconfDocumentedException(new RestconfError(ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
152 "Invalid with-defaults parameter: " + withDefaultsStr, null,
153 "The with-defaults parameter must be a string in " + POSSIBLE_WITH_DEFAULTS));
159 case REPORT_ALL_TAGGED:
160 builder.setTagged(true);
163 builder.setWithDefault(val);
167 return builder.build();
170 public static @NonNull WriteDataParams newWriteDataParams(final UriInfo uriInfo) {
171 InsertParameter insert = null;
172 PointParameter point = null;
174 for (final Entry<String, List<String>> entry : uriInfo.getQueryParameters().entrySet()) {
175 final String uriName = entry.getKey();
176 final List<String> paramValues = entry.getValue();
177 if (uriName.equals(InsertParameter.uriName())) {
178 final String str = optionalParam(uriName, paramValues);
180 insert = InsertParameter.forUriValue(str);
181 if (insert == null) {
182 throw new RestconfDocumentedException("Unrecognized insert parameter value '" + str + "'",
183 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
186 } else if (PointParameter.uriName().equals(uriName)) {
187 final String str = optionalParam(uriName, paramValues);
189 point = PointParameter.forUriValue(str);
192 throw new RestconfDocumentedException("Bad parameter for post: " + uriName,
193 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT);
198 return WriteDataParams.of(insert, point);
199 } catch (IllegalArgumentException e) {
200 throw new RestconfDocumentedException("Invalid query parameters: " + e.getMessage(), e);
205 * Check if URI does not contain not allowed parameters for specified operation.
207 * @param usedParameters parameters used in URI request
208 * @param allowedParameters allowed parameters for operation
211 static void checkParametersTypes(final Set<String> usedParameters, final Set<String> allowedParameters) {
212 if (!allowedParameters.containsAll(usedParameters)) {
213 final Set<String> notAllowedParameters = usedParameters.stream()
214 .filter(param -> !allowedParameters.contains(param))
215 .collect(Collectors.toSet());
216 throw new RestconfDocumentedException("Not allowed parameters for read operation: " + notAllowedParameters,
217 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
222 static @Nullable String getSingleParameter(final MultivaluedMap<String, String> params, final String name) {
223 final var values = params.get(name);
224 return values == null ? null : optionalParam(name, values);
227 private static @Nullable String optionalParam(final String name, final List<String> values) {
228 switch (values.size()) {
232 return requireNonNull(values.get(0));
234 throw new RestconfDocumentedException("Parameter " + name + " can appear at most once in request URI",
235 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
239 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
240 final List<String> values) {
241 final String str = optionalParam(name, values);
242 return str == null ? null : factory.apply(str);