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;
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.restconf.server.api.EventStreamGetParams.mandatoryParam;
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.MoreObjects;
15 import java.text.ParseException;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.opendaylight.restconf.api.ApiPath;
21 import org.opendaylight.restconf.api.query.InsertParam;
22 import org.opendaylight.restconf.api.query.PointParam;
23 import org.opendaylight.restconf.server.api.DatabindContext;
24 import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
25 import org.opendaylight.yangtools.concepts.Immutable;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
29 * Parser and holder of query parameters from uriInfo for data and datastore modification operations.
31 // FIXME: Java 21: model this as a sealed interface and two records BeforeOrAfter and FirstOrLast, which further expose
32 // a boolean to differentiate which of the cases we are dealing with. This will allow users to use
33 // switch expression with record decomposition to safely dispatch execution. Only BeforeOrAfter will
34 // have a @NonNull PointParam then and there will not be an insert field. We can also ditch toString(),
35 // as the records will do the right thing.
36 public final class Insert implements Immutable {
40 public interface PointNormalizer {
42 PathArgument normalizePoint(ApiPath value);
45 private final @NonNull InsertParam insert;
46 private final @Nullable PathArgument pointArg;
48 private Insert(final InsertParam insert, final PathArgument pointArg) {
49 this.insert = requireNonNull(insert);
50 this.pointArg = pointArg;
54 * Return an {@link Insert} parameter for specified query parameters.
56 * @param queryParameters Parameters and their values
57 * @return An {@link Insert}, or {@code null} if no insert information is present
58 * @throws NullPointerException if any argument is {@code null}
59 * @throws IllegalArgumentException if the parameters are invalid
61 public static @Nullable Insert ofQueryParameters(final DatabindContext databind,
62 final Map<String, String> queryParameters) {
63 InsertParam insert = null;
64 PointParam point = null;
66 for (var entry : queryParameters.entrySet()) {
67 final var paramName = entry.getKey();
68 final var paramValue = entry.getValue();
71 case InsertParam.uriName:
72 insert = mandatoryParam(InsertParam::forUriValue, paramName, paramValue);
74 case PointParam.uriName:
75 point = mandatoryParam(PointParam::forUriValue, paramName, paramValue);
78 throw new IllegalArgumentException("Invalid parameter: " + paramName);
82 return Insert.forParams(insert, point, new ApiPathNormalizer(databind));
85 public static @Nullable Insert forParams(final @Nullable InsertParam insert, final @Nullable PointParam point,
86 final PointNormalizer pointParser) {
89 throw invalidPointIAE();
94 return switch (insert) {
95 case BEFORE, AFTER -> {
96 // https://www.rfc-editor.org/rfc/rfc8040#section-4.8.5:
97 // If the values "before" or "after" are used, then a "point" query
98 // parameter for the "insert" query parameter MUST also be present, or a
99 // "400 Bad Request" status-line is returned.
101 throw new IllegalArgumentException(
102 "Insert parameter " + insert.paramValue() + " cannot be used without a Point parameter.");
104 yield new Insert(insert, parsePoint(pointParser, point.value()));
106 case FIRST, LAST -> {
107 // https://www.rfc-editor.org/rfc/rfc8040#section-4.8.6:
108 // [when "point" parameter is present and]
109 // If the "insert" query parameter is not present or has a value other
110 // than "before" or "after", then a "400 Bad Request" status-line is
113 throw invalidPointIAE();
115 yield new Insert(insert, null);
120 private static PathArgument parsePoint(final PointNormalizer pointParser, final String value) {
121 final ApiPath pointPath;
123 pointPath = ApiPath.parse(value);
124 } catch (ParseException e) {
125 throw new IllegalArgumentException("Malformed point parameter '" + value + "': " + e.getMessage(), e);
127 return pointParser.normalizePoint(pointPath);
130 private static IllegalArgumentException invalidPointIAE() {
131 return new IllegalArgumentException(
132 "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
135 public @NonNull InsertParam insert() {
139 public @Nullable PathArgument pointArg() {
144 public String toString() {
145 final var helper = MoreObjects.toStringHelper(this).add("insert", insert.paramValue());
146 final var local = pointArg;
148 helper.add("point", pointArg);
150 return helper.toString();