All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.restconf.server.spi.Insert Maven / Gradle / Ivy

There is a newer version: 8.0.3
Show newest version
/*
 * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.restconf.server.spi;

import static java.util.Objects.requireNonNull;
import static org.opendaylight.restconf.server.api.EventStreamGetParams.mandatoryParam;

import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import java.text.ParseException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.QueryParameters;
import org.opendaylight.restconf.api.query.InsertParam;
import org.opendaylight.restconf.api.query.PointParam;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.ServerException;
import org.opendaylight.yangtools.concepts.Immutable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;

/**
 * Parser and holder of query parameters from uriInfo for data and datastore modification operations.
 */
// FIXME: Java 21: model this as a sealed interface and two records BeforeOrAfter and FirstOrLast, which further expose
//                 a boolean to differentiate which of the cases we are dealing with. This will allow users to use
//                 switch expression with record decomposition to safely dispatch execution. Only BeforeOrAfter will
//                 have a @NonNull PointParam then and there will not be an insert field. We can also ditch toString(),
//                 as the records will do the right thing.
public final class Insert implements Immutable {
    @Beta
    @NonNullByDefault
    @FunctionalInterface
    public interface PointNormalizer {

        PathArgument normalizePoint(ApiPath value) throws ServerException;
    }

    private final @NonNull InsertParam insert;
    private final @Nullable PathArgument pointArg;

    private Insert(final InsertParam insert, final PathArgument pointArg) {
        this.insert = requireNonNull(insert);
        this.pointArg = pointArg;
    }

    /**
     * Return an {@link Insert} parameter for specified query parameters.
     *
     * @param params Parameters and their values
     * @return An {@link Insert}, or {@code null} if no insert information is present
     * @throws NullPointerException if any argument is {@code null}
     * @throws IllegalArgumentException if the parameters are invalid
     */
    public static @Nullable Insert of(final DatabindContext databind, final QueryParameters params) {
        InsertParam insert = null;
        PointParam point = null;

        for (var param : params.asCollection()) {
            final var paramName = param.getKey();
            final var paramValue = param.getValue();

            switch (paramName) {
                case InsertParam.uriName:
                    insert = mandatoryParam(InsertParam::forUriValue, paramName, paramValue);
                    break;
                case PointParam.uriName:
                    point = mandatoryParam(PointParam::forUriValue, paramName, paramValue);
                    break;
                default:
                    throw new IllegalArgumentException("Invalid parameter: " + paramName);
            }
        }

        return Insert.forParams(insert, point, new ApiPathNormalizer(databind));
    }

    public static @Nullable Insert forParams(final @Nullable InsertParam insert, final @Nullable PointParam point,
            final PointNormalizer pointParser) {
        if (insert == null) {
            if (point != null) {
                throw invalidPointIAE();
            }
            return null;
        }

        return switch (insert) {
            case BEFORE, AFTER -> {
                // https://www.rfc-editor.org/rfc/rfc8040#section-4.8.5:
                //        If the values "before" or "after" are used, then a "point" query
                //        parameter for the "insert" query parameter MUST also be present, or a
                //        "400 Bad Request" status-line is returned.
                if (point == null) {
                    throw new IllegalArgumentException(
                        "Insert parameter " + insert.paramValue() + " cannot be used without a Point parameter.");
                }
                yield new Insert(insert, parsePoint(pointParser, point.value()));
            }
            case FIRST, LAST -> {
                // https://www.rfc-editor.org/rfc/rfc8040#section-4.8.6:
                // [when "point" parameter is present and]
                //        If the "insert" query parameter is not present or has a value other
                //        than "before" or "after", then a "400 Bad Request" status-line is
                //        returned.
                if (point != null) {
                    throw invalidPointIAE();
                }
                yield new Insert(insert, null);
            }
        };
    }

    private static PathArgument parsePoint(final PointNormalizer pointParser, final String value) {
        try {
            return pointParser.normalizePoint(ApiPath.parse(value));
        } catch (ParseException | ServerException e) {
            throw new IllegalArgumentException("Malformed point parameter '" + value + "': " + e.getMessage(), e);
        }
    }

    private static IllegalArgumentException invalidPointIAE() {
        return new IllegalArgumentException(
            "Point parameter can be used only with 'after' or 'before' values of Insert parameter.");
    }

    public @NonNull InsertParam insert() {
        return insert;
    }

    public @Nullable PathArgument pointArg() {
        return pointArg;
    }

    @Override
    public String toString() {
        final var helper = MoreObjects.toStringHelper(this).add("insert", insert.paramValue());
        final var local = pointArg;
        if (local != null) {
            helper.add("point", pointArg);
        }
        return helper.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy