Repository: eclipse/microprofile-lra
Pull request #128 (2019-03-29 08:42:04)

issue120 test the behaviour of the LRA#type annotation
#120

Adds tests that validate that the implementation of the {@link LRA.Type} attribute is correct:
For each of the type values there are 4 tests:
 * start an LRA before invoking the resource method annotated with {@link LRA#end()} set to true
 * do not start an LRA before invoking the resource method annotated with {@link LRA#end()} set to true
 * start an LRA before invoking the resource method annotated with {@link LRA#end()} set to false
 * do not start an LRA before invoking the resource method annotated with {@link LRA#end()} set to false
 
Each test then validates {@link TckLRATypeTests#resourceRequest} that:
 * the correct JAX-RS status was returned
 * the resource method executed with the correct LRA context
 * the LRA started by the resource method is closed if {@link LRA#end()} is true
 * the LRA started by the resource method is not closed if {@link LRA#end()} is false



Patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
index f7105e7..210c341 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
@@ -30,6 +30,14 @@ import javax.ws.rs.core.Response;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
 
 import static junit.framework.TestCase.fail;
 import static org.eclipse.microprofile.lra.tck.participant.api.NonParticipatingTckResource.END_PATH;
@@ -41,8 +49,11 @@ import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckR
 import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.TCK_PARTICIPANT_RESOURCE_PATH;
 
 public class LRAClientOps {
+    private static final Logger LOGGER = Logger.getLogger(TckLRATypeTests.class.getName());
+
     /**
-     * TODO recovery needs to be properly speced
+     * TODO recovery needs to be properly speced: see the proposal
+     * on issue https://github.com/eclipse/microprofile-lra/issues/116
      * It is used in the two tests for participants that return 202:
      * {@link TckTests#acceptCancelTest()}  and {@link TckTests#acceptCloseTest()}
      * to connect to an endpoint that will trigger recovery (the alternative would
@@ -68,9 +79,13 @@ public class LRAClientOps {
     static final String LRA_RECOVERY_PATH_KEY = "lra.http.recovery.path";
 
     private final WebTarget target;
+    private final ScheduledExecutorService executor;
+    private final Map<LRATask, ScheduledFuture<?>> lraTasks;
 
     public LRAClientOps(WebTarget target) {
         this.target = target;
+        this.executor = Executors.newSingleThreadScheduledExecutor();
+        this.lraTasks = new HashMap<>();
     }
 
     // see if it is possible to join with an LRA - if it is possible to do that then the LRA is still active
@@ -105,7 +120,8 @@ public class LRAClientOps {
         return invokeRestEndpoint(lra == null ? null : lra.toASCIIString(), basePath, path, coerceResponse);
     }
 
-    private Response invokeRestEndpoint(String lra, String basePath, String path, int coerceResponse) {
+    // synchronize access to the connection since it is shared with the LRA background cancellation code
+    private synchronized Response invokeRestEndpoint(String lra, String basePath, String path, int coerceResponse) {
         WebTarget resourcePath = target.path(basePath).path(path).queryParam(STATUS_CODE_QUERY_NAME, coerceResponse);
         Invocation.Builder builder = resourcePath.request();
 
@@ -144,26 +160,122 @@ public class LRAClientOps {
         }
     }
 
-    public URI startLRA(URI parentLRA, String clientID, Long timeout, ChronoUnit unit)
+    public URI startLRA(URI parentLRA, String clientID, long timeout, ChronoUnit unit)
             throws GenericLRAException {
-        return toURI(invokeRestEndpoint(parentLRA,
+        String lra = invokeRestEndpoint(parentLRA,
                 TCK_NON_PARTICIPANT_RESOURCE_PATH, START_BUT_DONT_END_PATH, 200)
-                .readEntity(String.class));
-    }
+                .readEntity(String.class);
 
-    void cancelLRA(String lra) {
-        invokeRestEndpointAndReturnLRA(lra, TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 200);
+        if (timeout > 0L) {
+            scheduleCancelation(clientID, toURI(lra), timeout, unit);
+        }
+
+        return toURI(lra);
     }
 
     void cancelLRA(URI lraId) throws GenericLRAException {
+        cancelCancelation(lraId);
+
         invokeRestEndpointAndReturnLRA(lraId.toASCIIString(), TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 500);
     }
 
+    private void cancelLRA(String clientId, URI lra) {
+        LOGGER.warning("cancelling LRA from the timer: clientId: " + clientId + " LRA id: " + lra.toASCIIString());
+        cancelLRA(lra);
+        throw new IllegalArgumentException("LRA timed out prematurely");
+    }
+
     void closeLRA(URI lraId) throws GenericLRAException {
+        cancelCancelation(lraId);
+
         invokeRestEndpointAndReturnLRA(lraId.toASCIIString(), TCK_NON_PARTICIPANT_RESOURCE_PATH, END_PATH, 200);
     }
 
-    void leaveLRA(URI lra, String basePath, String resourcePath) throws GenericLRAException {
+    void closeLRA(String lraId) {
+        closeLRA(toURI(lraId));
+    }
+
+    private void leaveLRA(URI lra, String basePath, String resourcePath) throws GenericLRAException {
         invokeRestEndpoint(lra, basePath, resourcePath, 200);
     }
+
+    /*
+     * Include support for timing out LRAs. A timed out LRA will generally produce a test failure.
+     */
+
+    private static TimeUnit timeUnit(ChronoUnit unit) {
+        switch (unit) {
+            case NANOS:
+                return TimeUnit.NANOSECONDS;
+            case MICROS:
+                return TimeUnit.MICROSECONDS;
+            case MILLIS:
+                return TimeUnit.MILLISECONDS;
+            case SECONDS:
+                return TimeUnit.SECONDS;
+            case MINUTES:
+                return TimeUnit.MINUTES;
+            case HOURS:
+                return TimeUnit.HOURS;
+            case DAYS:
+                return TimeUnit.DAYS;
+            default:
+                throw new IllegalArgumentException("ChronoUnit cannot be converted to TimeUnit: " + unit);
+        }
+    }
+
+    /**
+     * arrange for an LRA to be automatically cancelled after a specified timeout
+     *
+     * @param clientId client assigned arbitrary identifier for the LRA
+     * @param lra the LRA that should be cancelled when the time limit is reached
+     * @param timeout the time to wait before attempting cancellation of the LRA
+     * @param unit the time unit
+     */
+    private void scheduleCancelation(String clientId, URI lra, long timeout, ChronoUnit unit) {
+        lraTasks.put(new LRATask(clientId, lra), executor.schedule(() -> cancelLRA(clientId, lra), timeout, timeUnit(unit)));
+    }
+
+    private void cancelCancelation(URI lraId) {
+        LRATask lra = new LRATask(null, lraId);
+
+        if (lraTasks.containsKey(lra)) {
+            lraTasks.remove(lra).cancel(false);
+        }
+    }
+
+    void cleanUp(Logger logger, String testName) {
+        lraTasks.forEach((lra, future) -> {
+            logger.warning("Test: " + testName + " didn't finish LRA " + lra.lra + " with clientId " + lra.clientId);
+            cancelLRA(lra.lra);
+        });
+    }
+
+    // class to store the clientId associated with an LRA
+    private class LRATask {
+        final String clientId; // client assigned arbitrary identifier for the LRA
+        final URI lra; // the LRA that clientId is associated with
+
+        LRATask(String clientId, URI lra) {
+            this.clientId = clientId;
+            this.lra = lra;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            LRATask lraTask = (LRATask) o;
+            return lra.equals(lraTask.lra);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(lra);
+        }
+    }
 }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LRATypeTckResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LRATypeTckResource.java
new file mode 100644
index 0000000..230279d
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LRATypeTckResource.java
@@ -0,0 +1,126 @@
+/*
+ *******************************************************************************
+ * Copyright (c) 2019 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.tck.participant.api;
+
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_HEADER;
+
+@ApplicationScoped
+@Path(LRATypeTckResource.TCK_LRA_TYPE_RESOURCE_PATH)
+public class LRATypeTckResource {
+    public static final String TCK_LRA_TYPE_RESOURCE_PATH = "lra-type-tck-resource";
+
+    public static final String REQUIRED_PATH = "/required";
+    public static final String REQUIRES_NEW_PATH = "/requires-new";
+    public static final String SUPPORTS_PATH = "/supports";
+    public static final String NOT_SUPPORTED_PATH = "/not-supported";
+    public static final String MANDATORY_PATH = "/mandatory";
+    public static final String NEVER_PATH = "/never";
+
+    public static final String REQUIRED_WITH_END_FALSE_PATH = "/end-required";
+    public static final String REQUIRES_NEW_WITH_END_FALSE_PATH = "/end-requires-new";
+    public static final String SUPPORTS_WITH_END_FALSE_PATH = "/end-supports";
+    public static final String NOT_SUPPORTED_WITH_END_FALSE_PATH = "/end-not-supported";
+    public static final String MANDATORY_WITH_END_FALSE_PATH = "/end-mandatory";
+    public static final String NEVER_WITH_END_FALSE_PATH = "/end-never";
+
+    // resource methods for each LRA.Type attribute
+    @GET
+    @Path(REQUIRED_PATH)
+    @LRA(value = LRA.Type.REQUIRED)
+    public Response requiredLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(REQUIRES_NEW_PATH)
+    @LRA(value = LRA.Type.REQUIRES_NEW)
+    public Response requiresNewLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(MANDATORY_PATH)
+    @LRA(value = LRA.Type.MANDATORY)
+    public Response mandatoryLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(SUPPORTS_PATH)
+    @LRA(value = LRA.Type.SUPPORTS)
+    public Response supportsLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(NOT_SUPPORTED_PATH)
+    @LRA(value = LRA.Type.NOT_SUPPORTED)
+    public Response notSupportedLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(NEVER_PATH)
+    @LRA(value = LRA.Type.NEVER)
+    public Response neverLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+
+    // similar set of resources methods but with the LRA.end attribute set to false
+    @GET
+    @Path(REQUIRED_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.REQUIRED, end = false)
+    public Response requiredEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(REQUIRES_NEW_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.REQUIRES_NEW, end = false)
+    public Response requiresNewEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(MANDATORY_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.MANDATORY, end = false)
+    public Response mandatoryEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(SUPPORTS_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.SUPPORTS, end = false)
+    public Response supportsEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(NOT_SUPPORTED_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.NOT_SUPPORTED, end = false)
+    public Response notSupportedEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+    @GET
+    @Path(NEVER_WITH_END_FALSE_PATH)
+    @LRA(value = LRA.Type.NEVER, end = false)
+    public Response neverEndLRA(@HeaderParam(LRA_HTTP_HEADER) String lraId) {
+        return Response.ok(lraId).build();
+    }
+}
\ No newline at end of file
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
index cc37905..70be30a 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
@@ -66,6 +66,7 @@ public class LraCancelOnController {
     public static final String CANCEL_ON_FAMILY_DEFAULT_4XX = "cancelOnFamilyDefault4xx";
     /**
      * Default return status for cancelling LRA is <code>4xx</code> and <code>5xx</code>
+     * @return JAX-RS response
      */
     @GET
     @Path(CANCEL_ON_FAMILY_DEFAULT_4XX)
@@ -77,6 +78,7 @@ public class LraCancelOnController {
     public static final String CANCEL_ON_FAMILY_DEFAULT_5XX = "cancelOnFamilyDefault5xx";
     /**
      * Default return status for cancelling LRA is <code>4xx</code> and <code>5xx</code>
+     * @return JAX-RS response
      */
     @GET
     @Path(CANCEL_ON_FAMILY_DEFAULT_5XX)
@@ -89,6 +91,7 @@ public class LraCancelOnController {
     /**
      * Cancel on family is set to <code>3xx</code>. The <code>3xx</code> return code
      * has to cancel the LRA.
+     * @return JAX-RS response
      */
     @GET
     @Path(CANCEL_ON_FAMILY_3XX)
@@ -102,6 +105,7 @@ public class LraCancelOnController {
     /**
      * Cancel on is set to <code>301</code>. The <code>301</code> return code
      * has to cancel the LRA.
+     * @return JAX-RS response
      */
     @GET
     @Path(CANCEL_ON_301)
@@ -116,6 +120,7 @@ public class LraCancelOnController {
      * Cancel on family is set to <code>4xx</code>,
      * the code from other families (e.g. for <code>5xx</code>
      * should not cancel but should go with close the LRA.
+     * @return JAX-RS response
      */
     @GET
     @Path(NOT_CANCEL_ON_FAMILY_5XX)
@@ -141,6 +146,7 @@ public class LraCancelOnController {
      * the {@link Compensate} method {@link #compensateWork(String, String)}
      * will be called only once for the test invocation.
      * </p>
+     * @return JAX-RS response
      */
     @GET
     @Path(CANCEL_FROM_REMOTE_CALL)
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/Util.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/Util.java
index 58c315d..2138acc 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/Util.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/Util.java
@@ -163,6 +163,8 @@ public class Util {
 
     /**
      * see {@link Util#adjust(int, double)}
+     *
+     * @return adjusted value by factor
      */
     public static long adjust(long value, double factor) {
         if(value < 0){


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java
new file mode 100644
index 0000000..03b5f8f
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java
@@ -0,0 +1,357 @@
+/*
+ *******************************************************************************
+ * Copyright (c) 2019 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.tck;
+
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.eclipse.microprofile.lra.tck.participant.api.Util;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.EmptyAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import javax.inject.Inject;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Invocation;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.time.temporal.ChronoUnit;
+import java.util.logging.Logger;
+
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.MANDATORY_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.MANDATORY_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NEVER_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NEVER_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NOT_SUPPORTED_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.NOT_SUPPORTED_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRED_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRED_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRES_NEW_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.REQUIRES_NEW_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.SUPPORTS_WITH_END_FALSE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.SUPPORTS_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.LRATypeTckResource.TCK_LRA_TYPE_RESOURCE_PATH;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * <p>
+ * Tests that validate that the implementation of the {@link LRA.Type} attribute is correct
+ * </p>
+ *
+ * <p>
+ * For each of the type values there are 4 tests:
+ * <ul>
+ *     <li>start an LRA before invoking the resource method annotated with {@link LRA#end()} set to true</li>
+ *     <li>do not start an LRA before invoking the resource method annotated with {@link LRA#end()} set to true</li>
+ *     <li>start an LRA before invoking the resource method annotated with {@link LRA#end()} set to false</li>
+ *     <li>do not start an LRA before invoking the resource method annotated with {@link LRA#end()} set to false</li>
+ * </ul>
+ *  *
+ * <p>
+ * Each test then validates {@link TckLRATypeTests#resourceRequest} that:
+ * <ul>
+ *     <li>the correct JAX-RS status was returned</li>
+ *     <li>the resource method executed with the correct LRA context</li>
+ *     <li>the LRA started by the resource method is closed if {@link LRA#end()} is true</li>
+ *     <li>the LRA started by the resource method is not closed if {@link LRA#end()} is false</li>
+ * </ul>
+ */
+@RunWith(Arquillian.class)
+public class TckLRATypeTests {
+    private static final Logger LOGGER = Logger.getLogger(TckLRATypeTests.class.getName());
+
+    @Rule public TestName testName = new TestName();
+
+    @Inject
+    private LraTckConfigBean config;
+
+    private LRAClientOps lraClient;
+
+    private static Client tckSuiteClient;
+
+    private WebTarget tckSuiteTarget;
+
+    @Deployment(name = "lra-type-tck-tests")
+    public static WebArchive deploy() {
+        String archiveName = TckLRATypeTests.class.getSimpleName().toLowerCase();
+        return ShrinkWrap
+            .create(WebArchive.class, archiveName + ".war")
+            .addPackages(true, "org.eclipse.microprofile.lra.tck")
+            .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
+    }
+    
+    @AfterClass
+    public static void afterClass() {
+        if(tckSuiteClient != null) {
+            tckSuiteClient.close();
+        }
+    }
+
+    @Before
+    public void before() {
+        LOGGER.info("Running test: " + testName.getMethodName());
+        setUpTestCase();
+
+        try {
+            tckSuiteTarget = tckSuiteClient.target(URI.create(new URL(config.tckSuiteBaseUrl()).toExternalForm()));
+            lraClient = new LRAClientOps(tckSuiteTarget);
+        } catch (MalformedURLException mfe) {
+            throw new IllegalStateException("Cannot create URL for the LRA TCK suite base url " + config.tckSuiteBaseUrl(), mfe);
+        }
+    }
+
+    @After
+    public void after() {
+        lraClient.cleanUp(LOGGER, testName.getMethodName());
+    }
+
+    private void setUpTestCase() {
+        tckSuiteClient = ClientBuilder.newClient();
+    }
+
+    // enum to indicate which checks to perform on the expected and actual active LRA after running a resource method
+    private enum MethodLRACheck {
+        NONE, NOT_PRESENT, EQUALS, NOT_EQUALS
+    }
+
+    @Test
+    public void requiredWithLRA() {
+        resourceRequest(REQUIRED_PATH, true, 200, MethodLRACheck.EQUALS, false);
+    }
+
+    @Test
+    public void requiredWithoutLRA() {
+        resourceRequest(REQUIRED_PATH, false, 200, MethodLRACheck.NOT_EQUALS, false);
+    }
+
+    @Test
+    public void requiresNewWithLRA() {
+        resourceRequest(REQUIRES_NEW_PATH, true, 200, MethodLRACheck.NOT_EQUALS, false);
+    }
+
+    @Test
+    public void requiresNewWithoutLRA() {
+        resourceRequest(REQUIRES_NEW_PATH, false, 200, MethodLRACheck.NOT_EQUALS, false);
+    }
+
+    @Test
+    public void mandatoryWithLRA() {
+        resourceRequest(MANDATORY_PATH, true, 200, MethodLRACheck.EQUALS, false);
+    }
+
+    @Test
+    public void mandatoryWithoutLRA() {
+        resourceRequest(MANDATORY_PATH, false, 412, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void supportsWithLRA() {
+        resourceRequest(SUPPORTS_PATH, true, 200, MethodLRACheck.EQUALS, false);
+    }
+
+    @Test
+    public void supportsWithoutLRA() {
+        resourceRequest(SUPPORTS_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void notSupportedWithRA() {
+        resourceRequest(NOT_SUPPORTED_PATH, true, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void notSupportedWithoutLRA() {
+        resourceRequest(NOT_SUPPORTED_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void neverWithRA() {
+        resourceRequest(NEVER_PATH, true, 412, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void neverWithoutLRA() {
+        resourceRequest(NEVER_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    // same set of tests except that the invoked method has end = false set on the LRA annotation
+
+    @Test
+    public void requiredEndWithLRA() {
+        resourceRequest(REQUIRED_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
+    }
+
+    @Test
+    public void requiredEndWithoutLRA() {
+        resourceRequest(REQUIRED_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
+    }
+
+    @Test
+    public void requiresEndNewWithLRA() {
+        resourceRequest(REQUIRES_NEW_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.NOT_EQUALS, true);
+    }
+
+    @Test
+    public void requiresEndNewWithoutLRA() {
+        resourceRequest(REQUIRES_NEW_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_EQUALS, true);
+    }
+
+    @Test
+    public void mandatoryEndWithLRA() {
+        resourceRequest(MANDATORY_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
+    }
+
+    @Test
+    public void mandatoryEndWithoutLRA() {
+        resourceRequest(MANDATORY_WITH_END_FALSE_PATH, false, 412, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void supportsEndWithLRA() {
+        resourceRequest(SUPPORTS_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.EQUALS, true);
+    }
+
+    @Test
+    public void supportsEndWithoutLRA() {
+        resourceRequest(SUPPORTS_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void notSupportedEndWithRA() {
+        resourceRequest(NOT_SUPPORTED_WITH_END_FALSE_PATH, true, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void notSupportedEndWithoutLRA() {
+        resourceRequest(NOT_SUPPORTED_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void neverWithEndRA() {
+        resourceRequest(NEVER_WITH_END_FALSE_PATH, true, 412, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    @Test
+    public void neverWithoutEndLRA() {
+        resourceRequest(NEVER_WITH_END_FALSE_PATH, false, 200, MethodLRACheck.NOT_PRESENT, false);
+    }
+
+    /**
+     * Perform a JAX-RS resource request and check the resulting status and whether or not it ran with
+     * the correct LRA context.
+     *
+     * @param path the resource path of the JAX-RS method to invoke
+     * @param startLRA indicates whether or not an active context should be present on the request
+     * @param expectedStatus the expected JAX-RS status code in the response
+     * @param lraCheckType the type of check to perform on the request context and the context specified
+     *                     by the {@link LRA.Type} annotation
+     * @param methodLRAShouldBeActive if true the LRA started by the invoked method should still
+     *                               be active after the resource invocation completes
+     */
+    private void resourceRequest(String path, boolean startLRA, int expectedStatus, MethodLRACheck lraCheckType,
+                                 boolean methodLRAShouldBeActive) {
+        Invocation.Builder target = tckSuiteTarget.path(TCK_LRA_TYPE_RESOURCE_PATH)
+                .path(path).request();
+        URI lra = startLRA ? lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS) : null;
+
+        if (lra != null) {
+            target = target.header(LRA.LRA_HTTP_HEADER, lra);
+        }
+
+        Response response = target.get();
+
+        try {
+            String methodLRA = response.readEntity(String.class);
+            String incomingLRA = lra == null ? "" : lra.toASCIIString();
+
+            assertEquals(testName.getMethodName() + ": Unexpected status", expectedStatus, response.getStatus());
+
+            if (response.getStatus() == Response.Status.PRECONDITION_FAILED.getStatusCode()) {
+                // 412 errors should abort running the target method so skip the LRA check
+                lraCheckType = MethodLRACheck.NONE;
+                methodLRAShouldBeActive = false;
+            }
+
+            switch (lraCheckType) {
+                case NOT_PRESENT:
+                    assertEquals(testName.getMethodName() + ": Resource method should not have run with an LRA: " + methodLRA,
+                            0, methodLRA.length());
+                    break;
+                case EQUALS:
+                    assertEquals(testName.getMethodName() + ": Resource method should have ran with the incoming LRA",
+                            incomingLRA, methodLRA);
+                    break;
+                case NOT_EQUALS:
+                    assertNotEquals(testName.getMethodName() + ": Resource method should not have run with the incoming LRA",
+                            incomingLRA, methodLRA);
+                    break;
+                default:
+                    break;
+            }
+
+            if (methodLRAShouldBeActive) {
+                // validate that the method ran with an LRA and that it is still active
+                assertNotEquals(testName.getMethodName() + ": Resource method should not have run with an LRA: " + methodLRA,
+                        0, methodLRA.length());
+                assertFalse(lraClient.isLRAFinished(methodLRA));
+                lraClient.closeLRA(methodLRA);
+            } else if (methodLRA.length() != 0) {
+                // otherwise it should be finished
+                assertTrue(lraClient.isLRAFinished(methodLRA));
+            }
+
+            if (lra != null) {
+                lraClient.closeLRA(lra);
+            }
+        } finally {
+            response.close();
+        }
+    }
+
+    /**
+     * The started LRA will be named based on the class name and the running test name.
+     */
+    private String lraClientId() {
+        return this.getClass().getSimpleName() + "#" + testName.getMethodName();
+    }
+
+    /**
+     * Adjusting the default timeout by the specified timeout factor
+     * which can be defined by user.
+     */
+    private long lraTimeout() {
+        return Util.adjust(LraTckConfigBean.LRA_TIMEOUT_MILLIS, config.timeoutFactor());
+    }
+}