Repository: eclipse/microprofile-lra (java)

Issue:
Conflict in TCK: unable to cancel already closed nested LRA
With #244 we require now that every LRA resource must contain either Compensate or AfterLRA method:

* Compensate - requires that received/created LRA is active when the LRA method is invoked
* AfterLRA - requires that received/created LRA is active, Closing, or Cancelling when the LRA method is invoked

In the TCK test `TckTests#mixedMultiLevelNestedActivity` we test the scenario where the nested LRA is first closed and then subsequently canceled. That means that we are invoking LRA method with already closed LRA which together with the requirement of #244 means that such invocations will always fail.

This means that #244 basically removed the possibility of mixed nested outcomes from 1.0. It will be possible to simulate `TckTests#mixedMultiLevelNestedActivity` scenario with the `LRAClient` when it will be introduced later.

For now, I added ignore to `TckTests#mixedMultiLevelNestedActivity` in PR #273.

Pull request description:
issue274 Unignore and fix TckTests#mixedMultiLevelNestedActivity
resolves #274 

Patch:
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 dbb4770..94b2351 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
@@ -114,7 +114,10 @@ public @interface LRA {
     String LRA_HTTP_ENDED_CONTEXT_HEADER = "Long-Running-Action-Ended";
 
     /**
-     * When a JAX-RS invocation is made with an active LRA which is nested,
+     * When an implementation of the LRA specification invokes any of the participant
+     * callbacks (namely {@link Compensate}, {@link Complete},
+     * {@link org.eclipse.microprofile.lra.annotation.Status} and
+     * {@link Forget}) in the context of a nested LRA it must ensure that
      * the parent LRA is made available via an HTTP header field with the
      * following name. The value contains the parent LRA id associated with
      * the HTTP request/response and is of type {@link java.net.URI}.
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 74c00ac..9e0cda5 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
@@ -72,6 +72,7 @@ public class LraResource {
     public static final String ACCEPT_WORK = "acceptWork";
     public static final String TIME_LIMIT = "/timeLimit";
     public static final String TIME_LIMIT_HALF_SEC = "/timeLimit2";
+    public static final String CANCEL_PATH = "/cancel";
     static final String MANDATORY_LRA_RESOURCE_PATH = "/mandatory";
 
     private static final Logger LOGGER = Logger.getLogger(LraResource.class.getName());
@@ -357,6 +358,19 @@ public class LraResource {
         return Response.ok(nestedLRAId).build();
     }
 
+    /**
+     * Used to close nested LRA in which this resource is enlisted
+     *
+     * @see org.eclipse.microprofile.lra.tck.TckTests#mixedMultiLevelNestedActivity
+     */
+    @PUT
+    @Path(CANCEL_PATH)
+    @LRA(value = LRA.Type.MANDATORY,
+        cancelOnFamily = Response.Status.Family.SERVER_ERROR)
+    public Response cancelLRA(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
+        return Response.status(500).entity(lraId).build();
+    }
+
     @PUT
     @Path("/multiLevelNestedActivity")
     @LRA(value = LRA.Type.MANDATORY, end = false)
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/RecoveryResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/RecoveryResource.java
index c3f2614..8f31d06 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/RecoveryResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/RecoveryResource.java
@@ -23,7 +23,6 @@ import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.annotation.Compensate;
 import org.eclipse.microprofile.lra.annotation.LRAStatus;
 import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
-import org.eclipse.microprofile.lra.tck.TckRecoveryTests;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricType;
 import org.eclipse.microprofile.lra.tck.service.LRATestService;
@@ -39,7 +38,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
 import java.net.URI;
-import java.net.URL;
 import java.time.temporal.ChronoUnit;
 
 import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
@@ -71,16 +69,11 @@ public class RecoveryResource {
      */
     @GET
     @Path(PHASE_1)
-    public Response phase1(@QueryParam("timeout") @DefaultValue("false") boolean timeout,
-                           @HeaderParam(TckRecoveryTests.LRA_TCK_DEPLOYMENT_URL) URL deploymentURL) {
-        
-        lraTestService.start(deploymentURL);
-        
+    public Response phase1(@QueryParam("timeout") @DefaultValue("false") boolean timeout) {
         // start a new LRA and join it with this resource
         URI lra;
         Response response;
         
-
         response = lraTestService.getTCKSuiteTarget().path(RecoveryResource.RECOVERY_RESOURCE_PATH)
                 .path(timeout ? RecoveryResource.REQUIRED_TIMEOUT_PATH : RecoveryResource.REQUIRED_PATH)
                 .request().put(Entity.text(""));
@@ -100,9 +93,7 @@ public class RecoveryResource {
      */
     @GET
     @Path(PHASE_2)
-    public Response phase2(@HeaderParam(LRA_HEADER) URI lraId,
-                           @HeaderParam(TckRecoveryTests.LRA_TCK_DEPLOYMENT_URL) URL deploymentURL) {
-        lraTestService.start(deploymentURL);
+    public Response phase2(@HeaderParam(LRA_HEADER) URI lraId) {
         lraTestService.getLRAClient().cancelLRA(lraId);
         lraTestService.waitForCallbacks(lraId);
 
@@ -127,9 +118,7 @@ public class RecoveryResource {
 
     @GET
     @Path(TRIGGER_RECOVERY)
-    public Response triggerRecovery(@HeaderParam(LRA_HEADER) URI lraId,
-                                    @HeaderParam(TckRecoveryTests.LRA_TCK_DEPLOYMENT_URL) URL deploymentURL) {
-        lraTestService.start(deploymentURL);
+    public Response triggerRecovery(@HeaderParam(LRA_HEADER) URI lraId) {
         lraTestService.waitForRecovery(lraId);
         return Response.ok().build();
     } 


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckRecoveryTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckRecoveryTests.java
index f0f43ff..d909a48 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckRecoveryTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckRecoveryTests.java
@@ -56,8 +56,6 @@ import java.util.logging.Logger;
  */
 @RunWith(Arquillian.class)
 public class TckRecoveryTests {
-    
-    public static final String LRA_TCK_DEPLOYMENT_URL = "LRA-TCK-Deployment-URL";
 
     private static final String DEPLOYMENT_NAME = "tck-recovery";
     private static final Logger LOGGER = Logger.getLogger(TckRecoveryTests.class.getName());
@@ -113,12 +111,8 @@ public class TckRecoveryTests {
         deployer.deploy(DEPLOYMENT_NAME);
         
         // invoke phase 1 inside the container (start new LRA and enlist resource)
-        Response response1 = deploymentTarget
-            .path(RecoveryResource.RECOVERY_RESOURCE_PATH)
-            .path(RecoveryResource.PHASE_1)
-            .request()
-            .header(LRA_TCK_DEPLOYMENT_URL, deploymentURL.toExternalForm())
-            .get();
+        Response response1 = deploymentTarget.path(RecoveryResource.RECOVERY_RESOURCE_PATH)
+            .path(RecoveryResource.PHASE_1).request().get();
         
         Assert.assertEquals(200, response1.getStatus());
         URI lra = URI.create(response1.readEntity(String.class));
@@ -135,7 +129,6 @@ public class TckRecoveryTests {
             .path(RecoveryResource.PHASE_2)
             .request()
                 .header(RecoveryResource.LRA_HEADER, lra)
-                .header(LRA_TCK_DEPLOYMENT_URL, deploymentURL.toExternalForm())
                 .get();
         
         Assert.assertEquals(response2.readEntity(String.class), 200, response2.getStatus());
@@ -167,9 +160,7 @@ public class TckRecoveryTests {
         Response response1 = deploymentTarget.path(RecoveryResource.RECOVERY_RESOURCE_PATH)
             .path(RecoveryResource.PHASE_1)
             .queryParam("timeout", true)
-            .request()
-            .header(LRA_TCK_DEPLOYMENT_URL, deploymentURL.toExternalForm())
-            .get();
+            .request().get();
 
         Assert.assertEquals(200, response1.getStatus());
         URI lra = URI.create(response1.readEntity(String.class));
@@ -201,7 +192,6 @@ public class TckRecoveryTests {
             .path(RecoveryResource.TRIGGER_RECOVERY)
             .request()
                 .header(RecoveryResource.LRA_HEADER, lra)
-                .header(LRA_TCK_DEPLOYMENT_URL, deploymentURL.toExternalForm())
                 .get();
 
         // invoke phase 2 inside the container (execute checks that verify that callbacks have been called)
@@ -209,7 +199,6 @@ public class TckRecoveryTests {
             .path(RecoveryResource.PHASE_2)
             .request()
                 .header(RecoveryResource.LRA_HEADER, lra)
-                .header(LRA_TCK_DEPLOYMENT_URL, deploymentURL.toExternalForm())
                 .get();
 
         Assert.assertEquals(response2.readEntity(String.class), 200, response2.getStatus());
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 b3da654..5fbf6f2 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
@@ -52,9 +52,6 @@ public class TckTestBase {
     
     @Inject
     private LRATestService lraTestService;
-
-    @Inject
-    private LRAMetricService lraMetricService;
     
     @ArquillianResource
     private URL deploymentURL;
@@ -84,7 +81,6 @@ public class TckTestBase {
         LOGGER.info("Running test: " + testName.getMethodName());
         
         lraTestService.start(deploymentURL);
-        lraMetricService.clear();
         this.lraClient = lraTestService.getLRAClient();
         this.tckSuiteTarget = lraTestService.getTCKSuiteTarget();
     }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
index b8e87ca..f49cdda 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
@@ -22,6 +22,7 @@ package org.eclipse.microprofile.lra.tck;
 import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
 import static org.eclipse.microprofile.lra.tck.participant.api.AfterLRAListener.AFTER_LRA_LISTENER_WORK;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraResource.ACCEPT_WORK;
+import static org.eclipse.microprofile.lra.tck.participant.api.LraResource.CANCEL_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraResource.LRA_RESOURCE_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraResource.TIME_LIMIT;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraResource.TIME_LIMIT_HALF_SEC;
@@ -64,7 +65,6 @@ import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.junit.Arquillian;
 import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -178,7 +178,6 @@ public class TckTests extends TckTestBase {
     }
 
     @Test
-    @Ignore("https://github.com/eclipse/microprofile-lra/issues/274")
     public void mixedMultiLevelNestedActivity() throws WebApplicationException {
         multiLevelNestedActivity(CompletionType.mixed, 2);
     }
@@ -635,11 +634,11 @@ public class TckTests extends TckTestBase {
             lraClient.closeLRA(lra);
         } else {
             /*
-             * The test is calling for a mixed outcome (a top level LRA L! and nestedCnt nested LRAs (L2, L3, ...)::
-             * L1 the mandatory call (PUT "activities/multiLevelNestedActivity") registers participant C1
-             *   the resource makes nestedCnt calls to "activities/nestedActivity" each of which create nested LRAs
+             * The test is calling for a mixed outcome (a top level LRA L1 and nestedCnt nested LRAs (L2, L3, ...)::
+             * L1 the mandatory call (PUT "lraresource/multiLevelNestedActivity") registers participant C1
+             * the resource makes nestedCnt calls to "lraresource/nestedActivity" each of which create nested LRAs
              * L2, L3, ... each of which enlists a participant (C2, C3, ...) which are completed when the call returns
-             * L2 is cancelled  which causes C2 to compensate
+             * L2 is cancelled which causes C2 to compensate
              * L1 is closed which triggers the completion of C1
              *
              * To summarise:
@@ -648,7 +647,14 @@ public class TckTests extends TckTestBase {
              * - C2 is completed and then compensated
              * - C3, ... are completed
              */
-            lraClient.cancelLRA(uris[1]); // compensate the first nested LRA
+
+            // compensate the first nested LRA in the enlisted resource
+            tckSuiteTarget
+                .path(LRA_RESOURCE_PATH).path(CANCEL_PATH)
+                .request()
+                .header(LRA_HTTP_CONTEXT_HEADER, uris[1])
+                .put(Entity.text(""));
+
             lraClient.closeLRA(lra); // should not complete any nested LRAs (since they have already completed via the interceptor)
         }
 
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRATestService.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRATestService.java
index 9e5b6ea..d6ac11b 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRATestService.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRATestService.java
@@ -38,6 +38,9 @@ import java.util.ServiceLoader;
 @ApplicationScoped
 public class LRATestService {
 
+    @Inject
+    LRAMetricService lraMetricService;
+    
     @Inject
     LraTckConfigBean config;
 
@@ -51,6 +54,7 @@ public class LRATestService {
 
     public void start(URL deploymentURL) {
         tckSuiteClient = ClientBuilder.newClient();
+        lraMetricService.clear();
         tckSuiteTarget = tckSuiteClient.target(URI.create(deploymentURL.toExternalForm()));
         lraClient = new LRAClientOps(tckSuiteTarget);
     }