PATCH methods should communicate ETag/Last-Modified where applicable.
Introduce DataYangPatchResult to communicate these for YANG PATCH.
JIRA: NETCONF-1207
Change-Id: Ie98423c6615dc0ef2d19349d19e937f72ed03165
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationsGetResult;
import org.opendaylight.restconf.server.api.OperationsPostResult;
}
}
- private static void completeDataYangPATCH(final RestconfFuture<PatchStatusContext> future, final AsyncResponse ar) {
+ private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
+ final AsyncResponse ar) {
future.addCallback(new JaxRsRestconfCallback<>(ar) {
@Override
- Response transform(final PatchStatusContext result) {
- return Response.status(statusOf(result)).entity(result).build();
+ Response transform(final DataYangPatchResult result) {
+ final var status = result.status();
+ final var builder = Response.status(statusOf(status)).entity(status);
+ fillConfigurationMetadata(builder, result);
+ return builder.build();
}
private static Status statusOf(final PatchStatusContext result) {
import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
import org.opendaylight.restconf.server.api.DataPutPath;
import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.OperationsGetResult;
import org.opendaylight.restconf.server.api.OperationsPostPath;
return merge(path.instance(), data);
}
- public final @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath apiPath, final PatchBody body) {
+ public final @NonNull RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath apiPath, final PatchBody body) {
final DataPath path;
try {
path = pathNormalizer.normalizeDataPath(apiPath);
* @param patch Patch context to be processed
* @return {@link PatchStatusContext}
*/
- public final @NonNull RestconfFuture<PatchStatusContext> patchData(final PatchContext patch) {
+ public final @NonNull RestconfFuture<DataYangPatchResult> patchData(final PatchContext patch) {
final var editCollection = new ArrayList<PatchStatusEntity>();
final var tx = prepareWriteExecution();
}
}
- final var ret = new SettableRestconfFuture<PatchStatusContext>();
+ final var ret = new SettableRestconfFuture<DataYangPatchResult>();
// We have errors
if (!noError) {
tx.cancel();
- ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null));
+ ret.set(new DataYangPatchResult(
+ new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false, null)));
return ret;
}
Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
@Override
public void onSuccess(final CommitInfo result) {
- ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true,
- null));
+ ret.set(new DataYangPatchResult(
+ new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), true, null)));
}
@Override
public void onFailure(final Throwable cause) {
// if errors occurred during transaction commit then patch failed and global errors are reported
- ret.set(new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
- TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors()));
+ ret.set(new DataYangPatchResult(
+ new PatchStatusContext(modelContext(), patch.patchId(), List.copyOf(editCollection), false,
+ TransactionUtil.decodeException(cause, "PATCH", null, modelContext()).getErrors())));
}
}, MoreExecutors.directExecutor());
--- /dev/null
+/*
+ * Copyright (c) 2023 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.api;
+
+import static java.util.Objects.requireNonNull;
+
+import java.time.Instant;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.restconf.common.patch.PatchStatusContext;
+
+/**
+ * Result of a {@code PATCH} request as defined in
+ * <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
+ *
+ * @param status A {@link PatchStatusContext}
+ * @param entityTag response {@code ETag} header, or {@code null} if not applicable
+ * @param lastModified response {@code Last-Modified} header, or {@code null} if not applicable
+ */
+public record DataYangPatchResult(
+ @NonNull PatchStatusContext status,
+ @Nullable EntityTag entityTag,
+ @Nullable Instant lastModified) implements ConfigurationMetadata {
+ public DataYangPatchResult {
+ requireNonNull(status);
+ }
+
+ public DataYangPatchResult(final @NonNull PatchStatusContext status) {
+ this(status, null, null);
+ }
+}
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
* <a href="https://www.rfc-editor.org/rfc/rfc8072#section-2">RFC8072, section 2</a>.
*
* @param body YANG Patch body
- * @return A {@link RestconfFuture} of the {@link PatchStatusContext} content
+ * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
*/
- // FIXME: NETCONF-1207: result should carry ConfigurationMetadata
- RestconfFuture<PatchStatusContext> dataPATCH(PatchBody body);
+ RestconfFuture<DataYangPatchResult> dataPATCH(PatchBody body);
/**
* Ordered list of edits that are applied to the datastore by the server, as defined in
*
* @param identifier path to target
* @param body YANG Patch body
- * @return A {@link RestconfFuture} of the {@link PatchStatusContext} content
+ * @return A {@link RestconfFuture} of the {@link DataYangPatchResult} content
*/
- // FIXME: NETCONF-1207: result should carry ConfigurationMetadata
- RestconfFuture<PatchStatusContext> dataPATCH(ApiPath identifier, PatchBody body);
+ RestconfFuture<DataYangPatchResult> dataPATCH(ApiPath identifier, PatchBody body);
RestconfFuture<DataPostResult.CreateResource> dataPOST(ChildBody body, Map<String, String> queryParameters);
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
import org.opendaylight.restconf.server.api.DataPutResult;
+import org.opendaylight.restconf.server.api.DataYangPatchResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationsGetResult;
}
@Override
- public RestconfFuture<PatchStatusContext> dataPATCH(final PatchBody body) {
+ public RestconfFuture<DataYangPatchResult> dataPATCH(final PatchBody body) {
return localStrategy().dataPATCH(ApiPath.empty(), body);
}
@Override
- public RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath identifier, final PatchBody body) {
+ public RestconfFuture<DataYangPatchResult> dataPATCH(final ApiPath identifier, final PatchBody body) {
final StrategyAndTail strategyAndTail;
try {
strategyAndTail = localStrategy().resolveStrategy(identifier);
public final void testDeleteNonexistentData() {
final var status = deleteNonexistentDataTestStrategy().patchData(new PatchContext("patchD",
List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))))
- .getOrThrow();
+ .getOrThrow().status();
assertEquals("patchD", status.patchId());
assertFalse(status.ok());
final var edits = status.editCollection();
}
private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
- final var patchStatusContext = strategy.patchData(patchContext).getOrThrow();
+ final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status();
for (var entity : patchStatusContext.editCollection()) {
if (failed) {
assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());