Allow dataPOST() to control Etag/Last-Modified 56/109156/3
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Dec 2023 07:58:28 +0000 (08:58 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Dec 2023 11:09:29 +0000 (12:09 +0100)
In Create Resource Mode, each RestconfStrategy should be able to
communicate ETag and Last-Modified headers.

Retrofit DataPostResult.CreateResource to implement
ConfigurationMetadata and propagate its content when generating the HTTP
response.

JIRA: NETCONF-1207
Change-Id: I6b4440f018c17a41887fecc19b220542f360e2c6
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/DataPostResult.java
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/server/api/RestconfServer.java

index 28eff8cb63d990a587b20989aae86ab5dbd350b4..1b7d8b90ee26f676938c1f3004060fce00069220 100644 (file)
@@ -37,6 +37,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.ParamConverter;
@@ -64,6 +65,7 @@ import org.opendaylight.restconf.nb.rfc8040.databind.XmlResourceBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.jaxrs.QueryParams;
 import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.server.api.ConfigurationMetadata;
 import org.opendaylight.restconf.server.api.DataGetResult;
 import org.opendaylight.restconf.server.api.DataPostResult;
 import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
@@ -191,19 +193,23 @@ public final class JaxRsRestconf implements ParamConverterProvider {
                 final var builder = Response.status(Status.OK)
                     .entity(result.payload())
                     .cacheControl(NO_CACHE);
-                final var etag = result.entityTag();
-                if (etag != null) {
-                    builder.tag(new EntityTag(etag.value(), etag.weak()));
-                }
-                final var lastModified = result.lastModified();
-                if (lastModified != null) {
-                    builder.lastModified(Date.from(lastModified));
-                }
+                fillConfigurationMetadata(builder, result);
                 return builder.build();
             }
         });
     }
 
+    private static void fillConfigurationMetadata(final ResponseBuilder builder, final ConfigurationMetadata metadata) {
+        final var etag = metadata.entityTag();
+        if (etag != null) {
+            builder.tag(new EntityTag(etag.value(), etag.weak()));
+        }
+        final var lastModified = metadata.lastModified();
+        if (lastModified != null) {
+            builder.lastModified(Date.from(lastModified));
+        }
+    }
+
     /**
      * Partially modify the target data store, as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.6.1">RFC8040, section 4.6.1</a>.
@@ -498,11 +504,12 @@ public final class JaxRsRestconf implements ParamConverterProvider {
             @Override
             Response transform(final DataPostResult result) {
                 if (result instanceof CreateResource createResource) {
-                    return Response.created(uriInfo.getBaseUriBuilder()
-                            .path("data")
-                            .path(createResource.createdPath())
-                            .build())
-                        .build();
+                    final var builder = Response.created(uriInfo.getBaseUriBuilder()
+                        .path("data")
+                        .path(createResource.createdPath())
+                        .build());
+                    fillConfigurationMetadata(builder, createResource);
+                    return builder.build();
                 }
                 if (result instanceof InvokeOperation invokeOperation) {
                     final var output = invokeOperation.output();
index 05f5eccee48ff64da58185c7ce94d3c2f0c97eaa..35a4540275c30e34dd628b3bc4b7526839730d95 100644 (file)
@@ -9,7 +9,8 @@ package org.opendaylight.restconf.server.api;
 
 import static java.util.Objects.requireNonNull;
 
-import org.eclipse.jdt.annotation.NonNullByDefault;
+import java.time.Instant;
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 
@@ -17,19 +18,27 @@ import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
  * Result of a {@code POST} request as defined in
  * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4">RFC8040 section 4.4</a>.
  */
-@NonNullByDefault
 public sealed interface DataPostResult {
     /**
      * Result of a {@code POST} request in as defined in
      * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-4.4.1">RFC8040 Create Resource Mode</a>.
      *
      * @param createdPath API path of the newly-created resource
+     * @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
      */
-    // FIXME: use ApiPath instead of String
-    record CreateResource(String createdPath) implements DataPostResult {
+    record CreateResource(
+            // FIXME: use ApiPath instead of String
+            @NonNull String createdPath,
+            @Nullable EntityTag entityTag,
+            @Nullable Instant lastModified) implements DataPostResult, ConfigurationMetadata {
         public CreateResource {
             requireNonNull(createdPath);
         }
+
+        public CreateResource(final @NonNull String createdPath) {
+            this(createdPath, null, null);
+        }
     }
 
     /**
index 3751f21859cf3e17dccc67a02ac065c3e216329d..5b269718eea4e69c0eba5b1f095033dfdf716bef 100644 (file)
@@ -20,7 +20,6 @@ import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
 import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
 import org.opendaylight.yangtools.yang.common.Empty;
 
 /**
@@ -28,7 +27,7 @@ import org.opendaylight.yangtools.yang.common.Empty;
  * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3">RESTCONF API Resource</a>.
  */
 // FIXME: NETCONF-1207: configuration datastore should maintain ETag and Last-Modified headers, so that these can be
-//                      returned when PATCH/POST/PUT modify the data.
+//                      returned when PATCH/PUT modify the data.
 @NonNullByDefault
 public interface RestconfServer {
     /**
@@ -95,7 +94,7 @@ public interface RestconfServer {
      */
     RestconfFuture<PatchStatusContext> dataPATCH(ApiPath identifier, PatchBody body);
 
-    RestconfFuture<CreateResource> dataPOST(ChildBody body, Map<String, String> queryParameters);
+    RestconfFuture<DataPostResult.CreateResource> dataPOST(ChildBody body, Map<String, String> queryParameters);
 
     RestconfFuture<? extends DataPostResult> dataPOST(ApiPath identifier, DataPostBody body,
         Map<String, String> queryParameters);