2 * Copyright (c) 2023 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.server.api;
10 import static java.util.Objects.requireNonNull;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.text.ParseException;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.NonNullByDefault;
17 import org.opendaylight.restconf.api.ApiPath;
18 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
19 import org.opendaylight.restconf.common.patch.PatchContext;
20 import org.opendaylight.restconf.server.api.DatabindPath.Data;
21 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
22 import org.opendaylight.yangtools.concepts.Immutable;
23 import org.opendaylight.yangtools.yang.common.ErrorTag;
24 import org.opendaylight.yangtools.yang.common.ErrorType;
29 public abstract sealed class PatchBody extends RequestBody permits JsonPatchBody, XmlPatchBody {
31 * Resource context needed to completely resolve a {@link PatchBody}.
34 public abstract static class ResourceContext implements Immutable {
35 protected final Data path;
37 protected ResourceContext(final Data path) {
38 this.path = requireNonNull(path);
42 * Return a {@link ResourceContext} for a sub-resource identified by an {@link ApiPath}.
44 * @param apiPath sub-resource
45 * @return A {@link ResourceContext}
46 * @throws RestconfDocumentedException if the sub-resource cannot be resolved
48 protected abstract ResourceContext resolveRelative(ApiPath apiPath);
51 PatchBody(final InputStream inputStream) {
55 public final @NonNull PatchContext toPatchContext(final @NonNull ResourceContext resource) throws IOException {
56 try (var is = consume()) {
57 return toPatchContext(resource, is);
61 abstract @NonNull PatchContext toPatchContext(@NonNull ResourceContext resource, @NonNull InputStream inputStream)
64 static final Data parsePatchTarget(final @NonNull ResourceContext resource, final String target) {
65 // As per: https://www.rfc-editor.org/rfc/rfc8072#page-18:
67 // "Identifies the target data node for the edit
68 // operation. If the target has the value '/', then
69 // the target data node is the target resource.
70 // The target node MUST identify a data resource,
71 // not the datastore resource.";
73 final ApiPath targetPath;
75 targetPath = ApiPath.parse(target.startsWith("/") ? target.substring(1) : target);
76 } catch (ParseException e) {
77 throw new RestconfDocumentedException("Failed to parse edit target '" + target + "'",
78 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
83 result = resource.resolveRelative(targetPath).path;
84 } catch (RestconfDocumentedException e) {
85 throw new RestconfDocumentedException("Invalid edit target '" + targetPath + "'",
86 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
88 if (result.instance().isEmpty()) {
89 throw new RestconfDocumentedException("Target node resource must not be a datastore resource",
90 ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE);
96 * Not all patch operations support value node. Check if operation requires value or not.
98 * @param operation Patch edit operation
99 * @return true if operation requires value, false otherwise
101 static final boolean requiresValue(final Operation operation) {
102 return switch (operation) {
103 case Create, Insert, Merge, Replace -> true;
104 case Delete, Move, Remove -> false;