Repository: eclipse/microprofile-lra (java)

Issue:
Fix participant responses HTTP statuses to 200 (Compensated/Completed), 202 (Compensating/Completing), and 409 (FailedToCompensate/FailedToComplete)
... and distinguish the LRA participant outcome based on the HTTP message body:

* 200 + ParticipantStatus.Compensated / ParticipantStatus.Completed
* 200 + ParticipantStatus.Compensating / ParticipantStatus.Completing
* 200 + ParticipantStatus.FailedToCompensate / ParticipantStatus.FailedToComplete

Pull request:
#299 Fix participant HTTP responses to 200, 202, 409, 410
resolves #299
﻿


Patch:
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/LRAResponse.java b/api/src/main/java/org/eclipse/microprofile/lra/LRAResponse.java
new file mode 100644
index 0000000..990139c
--- /dev/null
+++ b/api/src/main/java/org/eclipse/microprofile/lra/LRAResponse.java
@@ -0,0 +1,130 @@
+/*
+ *******************************************************************************
+ * Copyright (c) 2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+
+package org.eclipse.microprofile.lra;
+
+import javax.ws.rs.core.Response;
+
+/**
+ * Utility class that will create the correct {@link Response} or {@link Response.ResponseBuilder}
+ * for the response that should be returned from the LRA JAX-RS methods.
+ */
+public final class LRAResponse {
+    
+    private LRAResponse() {}
+    
+    public static Response compensated() {
+        return Builder.compensated().build();
+    }
+
+    public static Response compensated(Object entity) {
+        return Builder.compensated(entity).build();
+    }
+    
+    public static Response compensating() {
+        return Builder.compensating().build();
+    }
+
+    public static Response compensating(Object entity) {
+        return Builder.compensating(entity).build();
+    }
+    
+    public static Response failedToCompensate() {
+        return Builder.failedToCompensate().build();
+    }
+
+    public static Response failedToCompensate(Object entity) {
+        return Builder.failedToCompensate(entity).build();
+    }
+    
+    public static Response completed() {
+        return Builder.completed().build();
+    }
+
+    public static Response completed(Object entity) {
+        return Builder.completed(entity).build();
+    }
+    
+    public static Response completing() {
+        return Builder.completing().build();
+    }
+
+    public static Response completing(Object entity) {
+        return Builder.completing(entity).build();
+    }
+    
+    public static Response failedToComplete() {
+        return Builder.failedToComplete().build();
+    }
+
+    public static Response failedToComplete(Object entity) {
+        return Builder.failedToComplete(entity).build();
+    }
+    
+    public static final class Builder {
+        public static Response.ResponseBuilder compensated() {
+            return Response.ok();
+        }
+
+        public static Response.ResponseBuilder compensated(Object entity) {
+            return Response.ok(entity);
+        }
+
+        public static Response.ResponseBuilder compensating() {
+            return Response.accepted();
+        }
+
+        public static Response.ResponseBuilder compensating(Object entity) {
+            return Response.accepted(entity);
+        }
+
+        public static Response.ResponseBuilder failedToCompensate() {
+            return Response.status(Response.Status.CONFLICT);
+        }
+
+        public static Response.ResponseBuilder failedToCompensate(Object entity) {
+            return Response.status(Response.Status.CONFLICT).entity(entity);
+        }
+
+        public static Response.ResponseBuilder completed() {
+            return Response.ok();
+        }
+
+        public static Response.ResponseBuilder completed(Object entity) {
+            return Response.ok(entity);
+        }
+
+        public static Response.ResponseBuilder completing() {
+            return Response.accepted();
+        }
+
+        public static Response.ResponseBuilder completing(Object entity) {
+            return Response.accepted(entity);
+        }
+
+        public static Response.ResponseBuilder failedToComplete() {
+            return Response.status(Response.Status.CONFLICT);
+        }
+
+        public static Response.ResponseBuilder failedToComplete(Object entity) {
+            return Response.status(Response.Status.CONFLICT).entity(entity);
+        }
+    }
+}
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java
index c4258b8..bf0d848 100644
--- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java
+++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java
@@ -122,12 +122,7 @@ import java.lang.annotation.Target;
  *   <td scope="row">The resource is still attempting compensation</td>
  * </tr>
  * <tr>
- *   <td scope="row">410</td>
- *   <td scope="row">Empty</td>
- *   <td scope="row">The resource does not know about the LRA</td>
- * </tr>
- * <tr>
- *   <td scope="row">500</td>
+ *   <td scope="row">409</td>
  *   <td scope="row">{@link ParticipantStatus} enum value</td>
  *   <td scope="row"><p>The resource has failed to compensate.
  *   The payload contains the reason for the failure.
@@ -146,6 +141,11 @@ import java.lang.annotation.Target;
  *   actual state as reported by the {@link Status} method MUST
  *   be {@link ParticipantStatus#FailedToCompensate}</p></td>
  * </tr>
+ * <tr>
+ *   <td scope="row">410</td>
+ *   <td scope="row">Empty</td>
+ *   <td scope="row">The resource does not know about the LRA</td>
+ * </tr>
  * </table>
  *
  * <p>
@@ -158,7 +158,7 @@ import java.lang.annotation.Target;
  * </p>
  *
  * <p>
- * If any other code is returned (or, in the 500 case, the body does not
+ * If any other code is returned (or, in the 409 case, the body does not
  * correspond to a valid state) then the implementation SHOULD either keep
  * retrying or attempt to discover the status by calling the
  * {@link Status} method if present or a combination of both.
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java
index cfd55e9..f48f9c9 100644
--- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java
+++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java
@@ -121,12 +121,7 @@ import java.lang.annotation.Target;
  *   <td scope="row">The resource is still attempting completion</td>
  * </tr>
  * <tr>
- *   <td scope="row">410</td>
- *   <td scope="row">Empty</td>
- *   <td scope="row">The resource does not know about the LRA</td>
- * </tr>
- * <tr>
- *   <td scope="row">500</td>
+ *   <td scope="row">409</td>
  *   <td scope="row">{@link ParticipantStatus} enum value</td>
  *   <td scope="row"><p>The resource has failed to complete.
  *   The payload contains the reason for the failure.
@@ -148,6 +143,11 @@ import java.lang.annotation.Target;
  *   actual state as reported by the {@link Status} method MUST
  *   be {@link ParticipantStatus#FailedToComplete}</p></td>
  * </tr>
+ * <tr>
+ *   <td scope="row">410</td>
+ *   <td scope="row">Empty</td>
+ *   <td scope="row">The resource does not know about the LRA</td>
+ * </tr>
  * </table>
  *
  * <p>
@@ -160,8 +160,8 @@ import java.lang.annotation.Target;
  * </p>
  *
  * <p>
- * If any other code is returned (or, in the 500 case, the body does not
- * correspond to a valid state)  then the implementation SHOULD either keep
+ * If any other code is returned (or, in the 409 case, the body does not
+ * correspond to a valid state) then the implementation SHOULD either keep
  * retrying or attempt to discover the status by calling the
  * {@link Status} method if present or a combination of both.
  * If the implementation stops retrying then it SHOULD log a warning.
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
index 7ba4222..bde8c79 100644
--- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
+++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
@@ -23,6 +23,7 @@ package org.eclipse.microprofile.lra.annotation.ws.rs;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.core.Response;
 
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
 import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.annotation.Compensate;
 import org.eclipse.microprofile.lra.annotation.Complete;
@@ -312,7 +313,7 @@ public @interface LRA {
          *     then the {@link Complete} annotated method, if present,
          *     <i>is not</i> called again. If the LRA method causes the LRA
          *     to cancel then the nested LRA is moved to the
-         *     {@link org.eclipse.microprofile.lra.annotation.LRAStatus#Cancelling}
+         *     {@link LRAStatus#Cancelling}
          *     state and the {@link Compensate} callback will be invoked.
          * </p>
          * <p>
diff --git a/spec/src/main/asciidoc/microprofile-lra-spec.adoc b/spec/src/main/asciidoc/microprofile-lra-spec.adoc
index c5213c2..a79440d 100644
--- a/spec/src/main/asciidoc/microprofile-lra-spec.adoc
+++ b/spec/src/main/asciidoc/microprofile-lra-spec.adoc
@@ -823,7 +823,8 @@ participant state model>>.
 
 But what if the business logic is unable to compensate for a previous action?
 In this case the participant must remember that it was unable to compensate
-by reporting `FailedToCompensate` either via the compensate method, for example
+by reporting `FailedToCompensate` either via the compensate method using 
+the `409 Conflict` status code, for example:
 
 [source,java]
 ----
@@ -838,7 +839,7 @@ by reporting `FailedToCompensate` either via the compensate method, for example
         }
 
         if (isFailed(lraId)) // Business logic (provided by the business developer)
-              return Response.ok().entity(ParticipantStatus.FailedToCompensate).build();
+              return Response.status(Response.Status.CONFLICT).build();
         ...
     }
 ----
@@ -974,12 +975,12 @@ annotations when associated with JAX-RS resource methods:
 
 | `@Compensate`
 | PUT
-| 200, 202, 410, 500
+| 200, 202, 409, 410
 | <<source-Compensate,see javadoc>>
 
 | `@Complete`
 | PUT
-| 200, 202, 410, 500
+| 200, 202, 409, 410
 | <<source-Complete,see javadoc>>
 
 | `@Status`
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java
index f403e76..652d444 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java
@@ -19,6 +19,7 @@
  *******************************************************************************/
 package org.eclipse.microprofile.lra.tck.participant.api;
 
+import org.eclipse.microprofile.lra.LRAResponse;
 import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.annotation.Compensate;
 import org.eclipse.microprofile.lra.annotation.Complete;
@@ -60,7 +61,7 @@ public class AfterLRAParticipant {
     @Complete
     public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
         lraMetricService.incrementMetric(LRAMetricType.Completed, lraId, AfterLRAParticipant.class);
-        return Response.ok().build();
+        return LRAResponse.completed();
     }
 
     @PUT
@@ -69,7 +70,7 @@ public class AfterLRAParticipant {
     public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
         lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId, AfterLRAParticipant.class);
 
-        return Response.ok().build();
+        return LRAResponse.compensated();
     }
 
     @PUT
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnResource.java
index 831af18..b0d6a7e 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnResource.java
@@ -38,6 +38,7 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.Response.Status.Family;
 import javax.ws.rs.core.UriInfo;
 
+import org.eclipse.microprofile.lra.LRAResponse;
 import org.eclipse.microprofile.lra.annotation.Compensate;
 import org.eclipse.microprofile.lra.annotation.Complete;
 import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
@@ -177,7 +178,7 @@ public class LraCancelOnResource {
         lraMetricService.incrementMetric(LRAMetricType.Completed, lraId, LraCancelOnResource.class);
 
         LOGGER.info(String.format("LRA id '%s' was completed", lraId.toASCIIString()));
-        return Response.ok().build();
+        return LRAResponse.completed();
     }
 
     @PUT
@@ -191,6 +192,6 @@ public class LraCancelOnResource {
         lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId, LraCancelOnResource.class);
 
         LOGGER.info(String.format("LRA id '%s' was compensated", lraId.toASCIIString()));
-        return Response.ok().build();
+        return LRAResponse.compensated();
     }
 }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraResource.java
index c37d828..79ddc22 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraResource.java
@@ -19,6 +19,7 @@
  *******************************************************************************/
 package org.eclipse.microprofile.lra.tck.participant.api;
 
+import org.eclipse.microprofile.lra.LRAResponse;
 import org.eclipse.microprofile.lra.annotation.Forget;
 import org.eclipse.microprofile.lra.tck.LRAClientOps;
 import org.eclipse.microprofile.lra.tck.LraTckConfigBean;
@@ -164,14 +165,14 @@ public class LraResource {
             activity.setStatusUrl(String.format("%s/%s/%s/status", context.getBaseUri(),
                     LRA_RESOURCE_PATH, lraId));
 
-            return Response.accepted().location(URI.create(activity.getStatusUrl())).build();
+            return LRAResponse.Builder.completing().location(URI.create(activity.getStatusUrl())).build();
         }
 
         activity.setStatus(ParticipantStatus.Completed);
         activity.setStatusUrl(String.format("%s/%s/activity/completed", context.getBaseUri(), lraId.toASCIIString()));
 
         LOGGER.info(String.format("LRA id '%s' was completed", lraId.toASCIIString()));
-        return Response.ok(activity.getStatusUrl()).build();
+        return LRAResponse.completed(activity.getStatusUrl());
     }
 
     @PUT
@@ -193,14 +194,14 @@ public class LraResource {
             activity.setStatusUrl(String.format("%s/%s/%s/status", context.getBaseUri(),
                     LRA_RESOURCE_PATH, lraId));
 
-            return Response.accepted().location(URI.create(activity.getStatusUrl())).build();
+            return LRAResponse.Builder.compensating().location(URI.create(activity.getStatusUrl())).build();
         }
 
         activity.setStatus(ParticipantStatus.Compensated);
         activity.setStatusUrl(String.format("%s/%s/activity/compensated", context.getBaseUri(), lraId));
 
         LOGGER.info(String.format("LRA id '%s' was compensated", lraId));
-        return Response.ok(activity.getStatusUrl()).build();
+        return LRAResponse.compensated(activity.getStatusUrl());
     }
 
     @DELETE


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java
index 78d3739..b5af593 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java
@@ -19,6 +19,7 @@
  *******************************************************************************/
 package org.eclipse.microprofile.lra.tck;
 
+import org.eclipse.microprofile.lra.LRAResponse;
 import org.eclipse.microprofile.lra.tck.participant.activity.Activity;
 import org.eclipse.microprofile.lra.tck.participant.api.LraResource;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
@@ -71,6 +72,7 @@ public class TckTestBase {
         return ShrinkWrap
             .create(WebArchive.class, archiveName + ".war")
             .addPackages(false, TckTestBase.class.getPackage(),
+                LRAResponse.class.getPackage(),
                 Activity.class.getPackage(),
                 LraResource.class.getPackage(),
                 LRAMetricService.class.getPackage(),