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 com.google.common.collect.ImmutableMap;
15 import java.util.List;
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.server.api.DataGetParams;
38 import org.opendaylight.yangtools.yang.common.ErrorTag;
39 import org.opendaylight.yangtools.yang.common.ErrorType;
42 public final class QueryParams {
43 private static final Set<String> KNOWN_PARAMS = Set.of(
45 ContentParam.uriName, DepthParam.uriName, FieldsParam.uriName, WithDefaultsParam.uriName,
47 InsertParam.uriName, PointParam.uriName,
49 FilterParam.uriName, StartTimeParam.uriName, StopTimeParam.uriName,
51 ChangedLeafNodesOnlyParam.uriName, ChildNodesOnlyParam.uriName, PrettyPrintParam.uriName,
52 LeafNodesOnlyParam.uriName, SkipNotificationDataParam.uriName);
54 private QueryParams() {
59 * Normalize query parameters from an {@link UriInfo}.
61 * @param uriInfo An {@link UriInfo}
62 * @return Normalized query parameters
63 * @throws NullPointerException if {@code uriInfo} is {@code null}
64 * @throws IllegalArgumentException if there are multiple values for a parameter
66 public static @NonNull ImmutableMap<String, String> normalize(final UriInfo uriInfo) {
67 final var builder = ImmutableMap.<String, String>builder();
68 for (var entry : uriInfo.getQueryParameters().entrySet()) {
69 final var values = entry.getValue();
70 switch (values.size()) {
75 builder.put(entry.getKey(), values.get(0));
78 throw new IllegalArgumentException(
79 "Parameter " + entry.getKey() + " can appear at most once in request URI");
82 return builder.build();
86 * Parse parameters from URI request and check their types and values.
88 * @param uriInfo URI info
89 * @return {@link DataGetParams}
91 public static @NonNull DataGetParams newDataGetParams(final UriInfo uriInfo) {
92 ContentParam content = ContentParam.ALL;
93 DepthParam depth = null;
94 FieldsParam fields = null;
95 WithDefaultsParam withDefaults = null;
96 PrettyPrintParam prettyPrint = PrettyPrintParam.FALSE;
98 for (var entry : uriInfo.getQueryParameters().entrySet()) {
99 final var paramName = entry.getKey();
100 final var paramValues = entry.getValue();
104 case ContentParam.uriName:
105 content = optionalParam(ContentParam::forUriValue, paramName, paramValues);
107 case DepthParam.uriName:
108 final String depthStr = optionalParam(paramName, paramValues);
110 depth = DepthParam.forUriValue(depthStr);
111 } catch (IllegalArgumentException e) {
112 throw new RestconfDocumentedException(e, new RestconfError(ErrorType.PROTOCOL,
113 ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depthStr, null,
114 "The depth parameter must be an integer between 1 and 65535 or \"unbounded\""));
117 case FieldsParam.uriName:
118 fields = optionalParam(FieldsParam::forUriValue, paramName, paramValues);
120 case WithDefaultsParam.uriName:
121 withDefaults = optionalParam(WithDefaultsParam::forUriValue, paramName, paramValues);
123 case PrettyPrintParam.uriName:
124 prettyPrint = optionalParam(PrettyPrintParam::forUriValue, paramName, paramValues);
127 throw unhandledParam("read", paramName);
129 } catch (IllegalArgumentException e) {
130 throw new RestconfDocumentedException("Invalid " + paramName + " value: " + e.getMessage(),
131 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
135 return new DataGetParams(content, depth, fields, withDefaults, prettyPrint);
138 private static RestconfDocumentedException unhandledParam(final String operation, final String name) {
139 return KNOWN_PARAMS.contains(name)
140 ? new RestconfDocumentedException("Invalid parameter in " + operation + ": " + name,
141 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE)
142 : new RestconfDocumentedException("Unknown parameter in " + operation + ": " + name,
143 ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE);
147 static @Nullable String optionalParam(final String name, final List<String> values) {
148 return switch (values.size()) {
150 case 1 -> requireNonNull(values.get(0));
151 default -> throw new RestconfDocumentedException(
152 "Parameter " + name + " can appear at most once in request URI",
153 ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
157 private static <T> @Nullable T optionalParam(final Function<String, @NonNull T> factory, final String name,
158 final List<String> values) {
159 final String str = optionalParam(name, values);
160 return str == null ? null : factory.apply(str);