From 62a3cb65593df87d16087d7160545b16c0ca9b7f Mon Sep 17 00:00:00 2001 From: Ondra Chaloupka Date: Thu, 10 Jan 2019 21:31:30 +0100 Subject: [PATCH] issue #94: TCK refactoring for Arquillian Signed-off-by: Ondra Chaloupka Applying changes based on issue59 --- pom.xml | 2 +- tck/pom.xml | 35 +- tck/running_the_tck.asciidoc | 98 +- .../microprofile/lra/tck/JaxRsActivator.java | 3 +- .../microprofile/lra/tck/TckMethodMarker.java | 30 - .../microprofile/lra/tck/TckMethodResult.java | 106 -- .../microprofile/lra/tck/TckResource.java | 70 -- .../microprofile/lra/tck/TckResult.java | 85 -- .../lra/tck/TckTestJsonMarshaller.java | 61 - .../microprofile/lra/tck/TckTests.java | 1012 +++++++++-------- .../{model => activity}/Activity.java | 51 +- .../ActivityStorage.java} | 33 +- ...vityController.java => LraController.java} | 201 ++-- ...rdController.java => NoLRAController.java} | 16 +- .../lra/tck/participant/api/Util.java | 65 +- 15 files changed, 839 insertions(+), 1029 deletions(-) delete mode 100644 tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodMarker.java delete mode 100644 tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodResult.java delete mode 100644 tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResource.java delete mode 100644 tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResult.java delete mode 100644 tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestJsonMarshaller.java rename tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/{model => activity}/Activity.java (67%) rename tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/{service/ActivityService.java => activity/ActivityStorage.java} (63%) rename tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/{ActivityController.java => LraController.java} (77%) rename tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/{StandardController.java => NoLRAController.java} (86%) diff --git a/pom.xml b/pom.xml index a01ba885..abcd1e9e 100644 --- a/pom.xml +++ b/pom.xml @@ -207,7 +207,7 @@ - + diff --git a/tck/pom.xml b/tck/pom.xml index 345e7273..90c0f6c4 100644 --- a/tck/pom.xml +++ b/tck/pom.xml @@ -34,11 +34,24 @@ false - 1.0-SP1 + 1.4.1.Final 1.0 4.12 + 1.3 + + + + org.jboss.arquillian + arquillian-bom + ${version.arquillian} + pom + import + + + + org.eclipse.microprofile.lra @@ -49,10 +62,12 @@ javax.ws.rs javax.ws.rs-api + provided javax.enterprise cdi-api + provided @@ -62,10 +77,28 @@ provided + + org.eclipse.microprofile.config + microprofile-config-api + ${version.microprofile.config} + provided + + junit junit ${version.junit} + compile + + + org.jboss.arquillian.junit + arquillian-junit-container + compile + + + org.jboss.shrinkwrap + shrinkwrap-api + compile diff --git a/tck/running_the_tck.asciidoc b/tck/running_the_tck.asciidoc index 7fbc69ca..91d040ae 100644 --- a/tck/running_the_tck.asciidoc +++ b/tck/running_the_tck.asciidoc @@ -1,3 +1,4 @@ + // Copyright (c) 2018 Contributors to the Eclipse Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,38 +15,97 @@ = Running the Microprofile LRA TCK -The TCK provides a JAX-RS participant resource together with a resource for invoking the test sutie. +The TCK uses `JUnit 4` and `Arquillian`. + +== Configuration + +The LRA TCK suite can be parametrized by following properties, handled in the suite with MicroProfile Config + +`lra.tck.base.url`:: + The URL where the TCK suite deployment is exposed at. The TCK suite will construct path based on this URL. + The default base URL is `http://localhost:8180`. +`lra.tck.timeout.factor`:: + Timeout factor adjust timeout values used in the TCK suite. The default value is `1.0`. + When set bigger than `1.0` then timeout value will be bigger and waiting time is longer. + When set-up lower then the timeouts will be shorter. + Thus on slower machines it's expected longer timeouts will be needed. For example if test expects + some waiting time to be 10 seconds and this factor is set to `1.5` then the result waiting time is 15 seconds. +`lra.http.recovery.host`, `lra.http.recovery.port`, `lra.http.recovery.path`:: + Hostname, port and path for the recovery endpoint that will be contacted in tests checking recovery capabilities. + +== Prerequisites for the MicroProfile LRA TCK implementation -== Dependencies +* `pom.xml` dependencies are set-up +* the LRA TCK suite requires MicroProfile Config +* a default Arquillian container is configured in `arquillian.xml` (tests manually deploy with use of `@ArquillianResource Deployer`) +* implementation provides one implementation of `org.eclipse.microprofile.lra.client.LRAClient` as it's injected by TCK suite +* implementation has to provide one implementation of `org.eclipse.microprofile.lra.tck.spi.ManagementSPI`. This is an interface + with definition of util methods used by the TCK suite for its run. -To enable the tests in your implementation of this specification you need to add the -following dependency to your build: +=== Setting-up pom.xml dependencies and running the tests + +To enable the tests in your implementation of this specification you need to add the following dependency to your build: [source, xml] ---- org.eclipse.microprofile.lra microprofile-lra-tck - ${version.microprofile-lra-tck-to-test} - + ${version.microprofile-lra-tck-to-test} + test ---- -and then start a MicroProfile enabled container that scans this dependency for the JAX-RS resources -that comrise the TCK. The TCK relies upon an instance of `org.eclipse.microprofile.lra.client.LRAClient` -to be injected, via CDI, into the test suite. Your own dependencies should provide an already configured implementation -of LRAClient that can be used by CDI to satisfy the injection point. +Next you need to specify what are tests to be run. You need to inject the TCK suite tests to be run automatically. +Here we use integration tests with `maven failsafe` where dependency to scan is set to include particular test classes. -With this set up, you may trigger the TCK by sending a PUT request to the path tck/all: +[source, xml] +---- + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.eclipse.microprofile.lra:microprofile-lra-tck + + + **/*Test*.java + + + + + +---- - curl -XPUT http://localhost:8080/tck/all?verbose=false | jq +=== MicroProfile Config being available -where jq is a json pretty printer such as https://stedolan.github.io/jq and the `tck/all` path runs -all tests in the TCK. Setting verbose=true will include the full stack trace of any test failures. +The testsuite uses configuration while expecting MicroProfile Config is available. The maven coordinates +of config are `org.eclipse.microprofile.config:microprofile-config-api`. + +=== Arquillian container + +The `arquillian-*.xml` has to define a container that will be started and managed by Arquillian lifecycle +but that provide a way to deploy and undeploy deployments. That container to be expected with default type `suite`. + + +[source, xml] +---- + + + + + + ... + + + +---- -If you want to run a single test replace `all` with the name of the test you wish to run chosen from: -`timeLimit`, `startLRA`, `cancelLRA`, `closeLRA`, `getActiveLRAs`, `getAllLRAs`, `isActiveLRA, -`nestedActivity`, `completeMultiLevelNestedActivity`, `compensateMultiLevelNestedActivity`, -`mixedMultiLevelNestedActivity`, `joinLRAViaHeader`, `join`, `leaveLRA`, `leaveLRAViaAPI`, -`dependentLRA`, `cancelOn`, `cancelOnFamily` or `acceptTest`. +== Debugging tests +Debugging is dependent on the TCK implementor. The implementor configures Arquillian to use particular runtime +to deploy the test deployments and run the LRA client side implementation there. In general we can say that +the runtime needs to define java debug properties `-Xdebug -Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y`. +Then debugger may be connected to port 8787 and to track the test execution. diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/JaxRsActivator.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/JaxRsActivator.java index ea60effd..c02a3a20 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/JaxRsActivator.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/JaxRsActivator.java @@ -16,7 +16,8 @@ * 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; + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodMarker.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodMarker.java deleted file mode 100644 index 38fc0d5e..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodMarker.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 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 java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface TckMethodMarker { -} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodResult.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodResult.java deleted file mode 100644 index b9cf09de..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckMethodResult.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 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 java.util.Objects; -import java.util.function.Function; - -class TckMethodResult { - private String testName; - private Function testMethod; - - private boolean passed; - private boolean ran; - private boolean verbose; - private String result; - private Throwable failureReason; - - TckMethodResult(String testName, Function testMethod, boolean verbose) { - this.testName = testName; - this.testMethod = testMethod; - this.verbose = verbose; - } - - public String getTestName() { - return testName; - } - - public boolean isPassed() { - return passed; - } - - public boolean isRan() { - return ran; - } - - public String getResult() { - return result; - } - - public Throwable getFailureReason() { - return failureReason; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - TckMethodResult tckMethodResult = (TckMethodResult) o; - return Objects.equals(testName, tckMethodResult.testName); - } - - @Override - public int hashCode() { - - return Objects.hash(testName); - } - - void test(TckTests suite) { - System.out.printf("Starting test %s%n", testName); - - suite.before(); - - try { - ran = true; - result = testMethod.apply(suite); - passed = true; - failureReason = null; - System.out.printf("Test %s passed%n", testName); - } catch (Throwable t) { - result = t.getMessage(); - passed = false; - System.out.printf("Test %s failed: %s: %s%n", testName, t.getClass().getName(), result); - - if (verbose) { - t.printStackTrace(System.out); - failureReason = t; - } else { - failureReason = null; - } - } finally { - - suite.after(); - } - } -} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResource.java deleted file mode 100644 index 7881ad16..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResource.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 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.client.LRAClient; -import org.eclipse.microprofile.lra.tck.spi.ManagementSPI; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.MediaType; - -@Path("/tck") -public class TckResource { - - private static final String VERBOSE = "verbose"; - @Inject - private LRAClient lraClient; - @Inject - private ManagementSPI managementSPI; - - private TckTests test; - - @PostConstruct - private void setup() { - TckTests.beforeClass(lraClient, managementSPI); - test = new TckTests(); - } - - @PreDestroy - private void tearDown() { - TckTests.afterClass(); - } - - @PUT - @Path("{name}") - @Produces(MediaType.APPLICATION_JSON) - public TckResult runTck(@PathParam("name") String testName, @DefaultValue("true") @QueryParam(VERBOSE) boolean isVerbose) { - test.before(); - - TckResult results = test.runTck(testName, isVerbose); - - test.after(); - - return results; - } -} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResult.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResult.java deleted file mode 100644 index 24f0ad81..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckResult.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 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 java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -public class TckResult { - private List tests; - private List results; - private List failures; - - TckResult() { - tests = new ArrayList<>(); - failures = new ArrayList<>(); - } - - void add(String testName, Function testMethod, boolean verbose) { - tests.add(new TckMethodResult(testName, testMethod, verbose)); - } - - void runTests(TckTests testSpec, String testname) { - Optional tckTest = tests.stream() - .filter(name -> name.getTestName().equalsIgnoreCase(testname)) - .findFirst(); - - if (tckTest.isPresent()) { - tckTest.get().test(testSpec); - } else { - List testNames = Arrays.asList(testname.split(",")); - List intersect = tests.stream() - .filter(t -> testNames.contains(t.getTestName())) - .collect(Collectors.toList()); - - if (intersect.size() != 0) { - intersect.forEach(t -> t.test(testSpec)); - } else { - tests.forEach(t -> t.test(testSpec)); - } - } - - failures = tests.stream() - .filter(t -> !t.isPassed() && t.isRan()) - .map(TckMethodResult::getTestName) - .collect(Collectors.toList()); - - results = tests.stream() - .filter(TckMethodResult::isRan) - .collect(Collectors.toList()); - } - - public int getNumberOfFailures() { - return failures.size(); - } - - public List getFailures() { - return failures; - } - - public List getTestResults() { - return results; - } -} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestJsonMarshaller.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestJsonMarshaller.java deleted file mode 100644 index 56fad6ac..00000000 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestJsonMarshaller.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 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 javax.json.Json; -import javax.json.JsonObject; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; - -@Provider -@Produces(MediaType.APPLICATION_JSON) -public class TckTestJsonMarshaller implements MessageBodyWriter { - - @Override - public long getSize(TckMethodResult test, Class clazz, Type type, Annotation[] annotations, MediaType mediaType) { - return -1; - } - - @Override - public boolean isWriteable(Class clazz, Type type, Annotation[] annotations, MediaType mediaType) { - return clazz == TckMethodResult.class; - } - - @Override - public void writeTo(TckMethodResult test, Class clazz, Type type, Annotation[] annotations, MediaType mediaType, - MultivaluedMap valueMap, OutputStream stream) throws IOException, WebApplicationException { - - JsonObject jsonObject = Json.createObjectBuilder() - .add("name", test.getTestName()) - .add("result", test.getResult()).build(); - - DataOutputStream outputStream = new DataOutputStream(stream); - outputStream.writeBytes(jsonObject.toString()); - } -} 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 ca5fe008..0e21856f 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 @@ -19,24 +19,16 @@ *******************************************************************************/ package org.eclipse.microprofile.lra.tck; -import org.eclipse.microprofile.lra.annotation.LRAStatus; -import org.eclipse.microprofile.lra.client.GenericLRAException; -import org.eclipse.microprofile.lra.client.LRAClient; -import org.eclipse.microprofile.lra.tck.spi.ManagementSPI; -import org.eclipse.microprofile.lra.tck.participant.api.StandardController; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import static org.eclipse.microprofile.lra.tck.participant.api.LraController.ACCEPT_WORK; +import static org.eclipse.microprofile.lra.tck.participant.api.LraController.LRA_CONTROLLER_PATH; +import static org.eclipse.microprofile.lra.tck.participant.api.LraController.TRANSACTIONAL_WORK_PATH; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URI; @@ -45,398 +37,485 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import java.util.stream.IntStream; -import static org.eclipse.microprofile.lra.client.LRAClient.LRA_COORDINATOR_HOST_KEY; -import static org.eclipse.microprofile.lra.client.LRAClient.LRA_COORDINATOR_PORT_KEY; -import static org.eclipse.microprofile.lra.client.LRAClient.LRA_RECOVERY_PATH_KEY; -import static org.eclipse.microprofile.lra.tck.participant.api.ActivityController.ACCEPT_WORK; -import static org.eclipse.microprofile.lra.tck.participant.api.ActivityController.ACTIVITIES_PATH; +import javax.inject.Inject; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.lra.annotation.LRAStatus; +import org.eclipse.microprofile.lra.client.LRAClient; +import org.eclipse.microprofile.lra.tck.participant.api.LraController; +import org.eclipse.microprofile.lra.tck.participant.api.NoLRAController; +import org.eclipse.microprofile.lra.tck.participant.api.Util; +import org.eclipse.microprofile.lra.tck.spi.ManagementSPI; +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.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) public class TckTests { - private static final Long LRA_TIMEOUT_MILLIS = 50000L; - private static URL micrserviceBaseUrl; - private static URL rcBaseUrl; + private static final Logger LOGGER = Logger.getLogger(TckTests.class.getName()); + private final Long LRA_TIMEOUT_MILLIS = 50000L; + + /** + *

+ * Timeout factor which adjusts waiting time and timeouts for the TCK suite. + *

+ * The default value is set to 1.0 which means the defined timeout + * is multiplied by 1. + *

+ * If you wish the test waits longer then set the value bigger than 1.0. + * If you wish the test waits shorter time than designed + * or the timeout is elapsed faster then set the value less than 1.0 + */ + @Inject @ConfigProperty(name = "lra.tck.timeout.factor", defaultValue = "1.0") + private double timeoutFactor; + + /** + * Host name where LRA recovery is expected to be launch and TCK suite tries to connect to it at. + * The port is specifed by {@link #recoveryPort}. + */ + @Inject @ConfigProperty(name = LRAClient.LRA_RECOVERY_HOST_KEY, defaultValue = "localhost") + private String recoveryHostName; + + /** + * Port where LRA recovery is expected to be launch and TCK suite tries to connect to it at. + * The hostname is specifed by {@link #recoveryHostName}. + */ + @Inject @ConfigProperty(name = LRAClient.LRA_RECOVERY_PORT_KEY, defaultValue = "8080") + private int recoveryPort; + + /** + * Path where recovery is available to accept requests. + * The hostname of LRA recovery is specifed by {@link #recoveryHostName}, + * the port of LRA recovery is defined by {@link #recoveryPort}. + */ + @Inject @ConfigProperty(name = LRAClient.LRA_RECOVERY_PATH_KEY, defaultValue = "lra-recovery-coordinator") + private int recoveryPath; + + /** + * Base URL of LRA suite is started at. It's URL where container exposes the test suite deployment. + * The test paths will be constructed based on this base URL. + *

+ * The default base URL where TCK suite is expected to be started is http://localhost:8180/. + */ + @Inject @ConfigProperty(name = "lra.tck.base.url", defaultValue = "http://localhost:8180/") + private String tckSuiteBaseUrl; + + @Rule public TestName testName = new TestName(); - private static final int COORDINATOR_SWARM_PORT = 8082; - private static final int TEST_SWARM_PORT = 8080; + @Inject + private LRAClient lraClient; - private static final String RECOVERY_PATH_TEXT = "recovery"; - private static final String PASSED_TEXT = "passed"; - private static final String WORK_TEXT = "work"; + @Inject + private ManagementSPI lraManagement; - private static LRAClient lraClient; - private static ManagementSPI lraSPI; - private static Client msClient; - private static Client rcClient; + private static URL recoveryCoordinatorBaseUrl; + private static Client tckSuiteClient; + private static Client recoveryCoordinatorClient; - private WebTarget msTarget; + private WebTarget tckSuiteTarget; private WebTarget recoveryTarget; - private static List oldLRAs; + private static List notProperlyClosedLRAs = new ArrayList<>(); private enum CompletionType { complete, compensate, mixed } - @BeforeClass - public static void beforeClass(LRAClient lraClient, ManagementSPI managementSPI) { - initTck(lraClient, managementSPI); + @Deployment(name = "tcktests", managed = true, testable = true) + public static WebArchive deploy() { + String archiveName = TckTests.class.getSimpleName().toLowerCase(); + return ShrinkWrap + .create(WebArchive.class, archiveName + ".war") + .addPackages(true, "org.eclipse.microprofile.lra.tck") + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); } - - public TckResult runTck(String testname, boolean verbose) { - TckResult run = new TckResult(); - - run.add("timeLimit", TckTests::timeLimit, verbose); - run.add("startLRA", TckTests::startLRA, verbose); - run.add("cancelLRA", TckTests::cancelLRA, verbose); - run.add("closeLRA", TckTests::closeLRA, verbose); - run.add("getActiveLRAs", TckTests::getActiveLRAs, verbose); - run.add("getAllLRAs", TckTests::getAllLRAs, verbose); - run.add("isActiveLRA", TckTests::isActiveLRA, verbose); - run.add("nestedActivity", TckTests::nestedActivity, verbose); - run.add("completeMultiLevelNestedActivity", TckTests::completeMultiLevelNestedActivity, verbose); - run.add("compensateMultiLevelNestedActivity", TckTests::compensateMultiLevelNestedActivity, verbose); - run.add("mixedMultiLevelNestedActivity", TckTests::mixedMultiLevelNestedActivity, verbose); - run.add("joinLRAViaHeader", TckTests::joinLRAViaHeader, verbose); - run.add("join", TckTests::join, verbose); - run.add("leaveLRA", TckTests::leaveLRA, verbose); - run.add("leaveLRAViaAPI", TckTests::leaveLRAViaAPI, verbose); - run.add("dependentLRA", TckTests::dependentLRA, verbose); - run.add("cancelOn", TckTests::cancelOn, verbose); - run.add("cancelOnFamily", TckTests::cancelOnFamily, verbose); - run.add("acceptTest", TckTests::acceptTest, verbose); - run.add("noLRATest", TckTests::noLRATest, verbose); - - run.runTests(this, testname); - - return run; - } - - private static void initTck(LRAClient lraClient, ManagementSPI managementSPI) { - TckTests.lraClient = lraClient; - TckTests.lraSPI = managementSPI; - - try { - if (Boolean.valueOf(System.getProperty("enablePause", "true"))) { - System.out.println("Getting ready to connect - expecting swarm lra coordinator is already up..."); - Thread.sleep(1000); - } - - int servicePort = Integer.getInteger("service.http.port", TEST_SWARM_PORT); - // TODO issue 42 will be removing these endpoint references - String rcHost = System.getProperty(LRA_COORDINATOR_HOST_KEY, "localhost"); - String rcPath = System.getProperty(LRA_RECOVERY_PATH_KEY, "lra-recovery-coordinator"); - int rcPort = Integer.getInteger(LRA_COORDINATOR_PORT_KEY, COORDINATOR_SWARM_PORT); - - micrserviceBaseUrl = new URL(String.format("http://localhost:%d", servicePort)); - rcBaseUrl = new URL(String.format("http://%s:%d/%s", rcHost, rcPort, rcPath)); - - msClient = ClientBuilder.newClient(); - rcClient = ClientBuilder.newClient(); - - oldLRAs = new ArrayList<>(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - + @AfterClass public static void afterClass() { - oldLRAs.clear(); - lraClient.close(); - msClient.close(); - rcClient.close(); + if(tckSuiteClient != null) { + tckSuiteClient.close(); + } + if(recoveryCoordinatorClient != null) { + recoveryCoordinatorClient.close(); + } } @Before public void before() { + setUpTestCase(); + try { - msTarget = msClient.target(URI.create(new URL(micrserviceBaseUrl, "/").toExternalForm())); - recoveryTarget = rcClient.target(URI.create(rcBaseUrl.toExternalForm())); - } catch (MalformedURLException e) { - throw new RuntimeException(e); + tckSuiteTarget = tckSuiteClient.target(URI.create(new URL(tckSuiteBaseUrl).toExternalForm())); + } catch (MalformedURLException mfe) { + throw new IllegalStateException("Cannot create URL for the LRA TCK suite base url " + tckSuiteBaseUrl, mfe); } + recoveryTarget = recoveryCoordinatorClient.target(URI.create(recoveryCoordinatorBaseUrl.toExternalForm())); } @After public void after() { - List activeLRAs = lraSPI.getLRAs(LRAStatus.Active); + List activeLRAs = lraManagement.getLRAs(LRAStatus.Active); if (activeLRAs.size() != 0) { activeLRAs.forEach(lra -> { try { - if (!oldLRAs.contains(lra)) { - System.out.printf("%s: WARNING: test did not close %s%n", "testName.getMethodName()", lra.getLraId()); - oldLRAs.add(lra); + if (!notProperlyClosedLRAs.contains(lra)) { + LOGGER.warning(String.format( + " %s: test did not close %s%n", testName.getMethodName(), lra.getLraId())); + notProperlyClosedLRAs.add(lra); lraClient.closeLRA(new URL(lra.getLraId())); } } catch (WebApplicationException | MalformedURLException e) { - System.out.printf("After Test: exception %s closing %s%n", e.getMessage(), lra.getLraId()); + LOGGER.warning(String.format(" %s: exception %s closing %s%n", + testName.getMethodName(), e.getMessage(), lra.getLraId())); } }); } -// Current.popAll(); } + /** + * Checking if coordinator is running, set ups the client to contact the recovery manager and the TCK suite itself. + */ + private void setUpTestCase() { + if(recoveryCoordinatorBaseUrl != null) { + // we've already set up the recovery urls and REST clients for the tests + return; + } + + try { + // TODO: what to do with this? recovery tests are valid? + recoveryCoordinatorBaseUrl = new URL(String.format("http://%s:%d/%s", recoveryHostName, recoveryPort, recoveryPath)); + + tckSuiteClient = ClientBuilder.newClient(); + recoveryCoordinatorClient = ClientBuilder.newClient(); + } catch (MalformedURLException e) { + throw new RuntimeException("Cannot properly setup the TCK tests (coordinator endpoint, testsuite endpoints...)", e); + } + } + + // TODO: what's difference to closeLRA? @Test - private String startLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#startLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + public void startLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); lraClient.closeLRA(lra); - - return lra.toExternalForm(); } + /** + *

+ * Starting LRA and canceling it. + *

+ * It's expected the LRA won't be listed in active LRAs when canceled. + */ @Test - private String cancelLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null,"SpecTest#cancelLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + public void cancelLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null,lraClientId(), lraTimeout(), ChronoUnit.MILLIS); lraClient.cancelLRA(lra); - List lras = lraSPI.getLRAs(null); - - assertNull(getLra(lras, lra.toExternalForm()), "cancelLRA via client: lra still active", null); + List allClientLRAs = lraManagement.getLRAs(null); + boolean isLraIdInList = containsLraId(allClientLRAs, lra); - return lra.toExternalForm(); + assertFalse("LRA '" + lra + "' should not be active anymore but was found in the list of all lras " + + allClientLRAs, isLraIdInList); } + /** + *

+ * Starting LRA and closing it. + *

+ * It's expected the LRA won't be listed in active LRAs when closed. + */ @Test - private String closeLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#closelLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + public void closeLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); lraClient.closeLRA(lra); - List lras = lraSPI.getLRAs(null); - - assertNull(getLra(lras, lra.toExternalForm()), "closeLRA via client: lra still active", null); + List allClientLRAs = lraManagement.getLRAs(null); + boolean isLraIdInList = containsLraId(allClientLRAs, lra); - return lra.toExternalForm(); + assertFalse("LRA '" + lra + "' should not be active anymore but was found in the list of all lras " + + allClientLRAs, isLraIdInList); } + /** + * Started LRA should be listed amongst active LRAs. + */ @Test - private String getActiveLRAs() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#getActiveLRAs", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - List lras = lraSPI.getLRAs(null); + public void getActiveLRAs() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); - assertNotNull(getLra(lras, lra.toExternalForm()), "getActiveLRAs: getLra returned null", null); + List allActiveLRAs = lraManagement.getLRAs(LRAStatus.Active); + boolean isLraIdInList = containsLraId(allActiveLRAs, lra); - lraClient.closeLRA(lra); + assertTrue("LRA '" + lra + "' should be listed in the list of the active LRAs, as it was not closed yet, but it isn't", + isLraIdInList); - return lra.toExternalForm(); + lraClient.closeLRA(lra); } + /** + * Started LRA should be listed amongst active LRAs. + */ @Test - private String getAllLRAs() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#getAllLRAs", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - List lras = lraSPI.getLRAs(null); + public void getAllLRAs() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); - assertNotNull(getLra(lras, lra.toExternalForm()), "getAllLRAs: getLra returned null", null); + List allClientLRAs = lraManagement.getLRAs(null); + boolean isLraIdInList = containsLraId(allClientLRAs, lra); - lraClient.closeLRA(lra); + assertTrue("LRA '" + lra + "' should be listed in the list of the all LRAs, as it was not closed yet, but it isn't", + isLraIdInList); - return PASSED_TEXT; + lraClient.closeLRA(lra); } - // @Test - private void getRecoveringLRAs() throws WebApplicationException { + @Test + @Ignore + public void getRecoveringLRAs() throws WebApplicationException { // TODO } + /** + * Started LRA should in state 'active'. + */ @Test - private String isActiveLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#isActiveLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + public void isActiveLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); - assertTrue(lraClient.getStatus(lra) == LRAStatus.Active, null, null, lra); + assertEquals("LRA '" + lra + "' is not denoted as active even it was started", + LRAStatus.Active, lraManagement.getStatus(lra)); lraClient.closeLRA(lra); - - return lra.toExternalForm(); } - // the coordinator cleans up when canceled + /** + * Canceled LRA should in state 'compensated'. + * NOTE: the coordinator cleans up when canceled + */ @Test - private String isCompensatedLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#isCompensatedLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + @Ignore + public void isCompensatedLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); lraClient.cancelLRA(lra); - assertTrue(lraClient.getStatus(lra) == LRAStatus.Cancelled, null, null, lra); - - return lra.toExternalForm(); + assertEquals("LRA '" + lra + "' is not denoted as compensated even it was canceled", + LRAStatus.Cancelled, lraManagement.getStatus(lra)); } - // the coordinator cleans up when completed + /** + * Closed LRA should in state 'completed'. + * NOTE: the coordinator cleans up when completed + */ @Test - private String isCompletedLRA() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#isCompletedLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + @Ignore + public void isCompletedLRA() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); lraClient.closeLRA(lra); - assertTrue(lraClient.getStatus(lra) == LRAStatus.Closed, null, null, lra); - - return lra.toExternalForm(); + assertEquals("LRA '" + lra + "' is not denoted as compensated even it was canceled", + LRAStatus.Closed, lraManagement.getStatus(lra)); } + /** + * HTTP request to {@link LraController#activityWithLRA} + * which is a method annotated with {@link org.eclipse.microprofile.lra.annotation.LRA.Type#REQUIRED}. + */ @Test - private String joinLRAViaBody() throws WebApplicationException { + @Ignore + public void joinLRAViaBody() throws WebApplicationException { - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); Response response = resourcePath.request().put(Entity.text("")); - String lra = checkStatusAndClose(response, Response.Status.OK.getStatusCode(), true, resourcePath); + String lraId = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); // validate that the LRA coordinator no longer knows about lraId - List lras = lraSPI.getLRAs(LRAStatus.Active); + List activeLras = lraManagement.getLRAs(LRAStatus.Active); + boolean isLraIdInList = containsLraId(activeLras, lraId); // the resource /activities/work is annotated with Type.REQUIRED so the container should have ended it - assertNull(getLra(lras, lra), "joinLRAViaBody: lra is still active", resourcePath); - - return PASSED_TEXT; + assertFalse("LRA work was processed and the annotated method finished but the LRA id '" + lraId + "'" + + "is still part of known active LRAs " + activeLras, isLraIdInList); } @Test - private String nestedActivity() throws WebApplicationException { - URL lra = lraClient.startLRA(null, "SpecTest#nestedActivity", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget - .path(ACTIVITIES_PATH).path("nestedActivity"); - - Response response = resourcePath - .request() - .header(LRAClient.LRA_HTTP_HEADER, lra) - .put(Entity.text("")); + public void nestedActivity() throws WebApplicationException { + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); + WebTarget resourcePath = tckSuiteTarget + .path(LRA_CONTROLLER_PATH).path("nestedActivity"); - Object parentId = response.getHeaders().getFirst(LRAClient.LRA_HTTP_HEADER); - - assertNotNull(parentId, "nestedActivity: null parent LRA", resourcePath); - assertEquals(lra.toExternalForm(), parentId, "nestedActivity should have returned the parent LRA", resourcePath); - - String nestedLraId = checkStatusAndClose(response, Response.Status.OK.getStatusCode(), true, resourcePath); - - // close the LRA - lraClient.closeLRA(lra); - - // validate that the nested LRA was closed - List lras = lraSPI.getLRAs(LRAStatus.Active); - - // the resource /activities/work is annotated with Type.REQUIRED so the container should have ended it - assertNull(getLra(lras, nestedLraId), "nestedActivity: nested LRA should not be active", resourcePath); - - return lra.toExternalForm(); + Response response = null; + try { + response = resourcePath + .request() + .header(LRAClient.LRA_HTTP_HEADER, lra) + .put(Entity.text("")); + + assertEquals("Response status to ' " + resourcePath.getUri() + "' does not match.", + Response.Status.OK.getStatusCode(), response.getStatus()); + + Object parentId = response.getHeaders().getFirst(LRAClient.LRA_HTTP_HEADER); + + assertNotNull("Expecting to get parent LRA id as response from " + resourcePath.getUri(), parentId); + assertEquals("The nested activity should return the parent LRA id. The call to " + resourcePath.getUri(), + parentId, lra.toExternalForm()); + + String nestedLraId = response.readEntity(String.class); + + // close the LRA + lraClient.closeLRA(lra); + + // validate that the nested LRA was closed + List activeLras = lraManagement.getLRAs(LRAStatus.Active); + boolean isLraIdInList = containsLraId(activeLras, nestedLraId); + + // the resource /activities/work is annotated with Type.REQUIRED so the container should have ended it + assertFalse("Nested LRA id '" + lra + "' should be listed in the list of the active LRAs (from call to " + + resourcePath.getUri() + ")", isLraIdInList); + } finally { + if(response != null) { + response.close(); + } + } } @Test - private String completeMultiLevelNestedActivity() throws WebApplicationException { - return multiLevelNestedActivity(CompletionType.complete, 1); + public void completeMultiLevelNestedActivity() throws WebApplicationException { + multiLevelNestedActivity(CompletionType.complete, 1); } @Test - private String compensateMultiLevelNestedActivity() throws WebApplicationException { - return multiLevelNestedActivity(CompletionType.compensate, 1); + public void compensateMultiLevelNestedActivity() throws WebApplicationException { + multiLevelNestedActivity(CompletionType.compensate, 1); } @Test - private String mixedMultiLevelNestedActivity() throws WebApplicationException { - return multiLevelNestedActivity(CompletionType.mixed, 2); + public void mixedMultiLevelNestedActivity() throws WebApplicationException { + multiLevelNestedActivity(CompletionType.mixed, 2); } @Test - private String joinLRAViaHeader() throws WebApplicationException { - int cnt1 = completedCount(true); + public void joinLRAViaHeader() throws WebApplicationException { + int beforeCompletedCount = getCompletedCount(); - URL lra = lraClient.startLRA(null, "SpecTest#joinLRAViaBody", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); Response response = resourcePath .request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + + checkStatusAndClose(Response.Status.OK, response, resourcePath); // validate that the LRA coordinator still knows about lraId - List lras = lraSPI.getLRAs(LRAStatus.Active); - assertNotNull(getLra(lras, lra.toExternalForm()), "joinLRAViaHeader: missing lra", resourcePath); + List allActiveLRAs = lraManagement.getLRAs(LRAStatus.Active); + boolean isLraIdInList = containsLraId(allActiveLRAs, lra); + assertTrue("LRA '" + lra + "' should be active as not closed yet. But it was not found in the list of active LRAs " + + allActiveLRAs, isLraIdInList); // close the LRA lraClient.closeLRA(lra); // check that LRA coordinator no longer knows about lraId - lras = lraSPI.getLRAs(LRAStatus.Active); - assertNull(getLra(lras, lra.toExternalForm()), "joinLRAViaHeader: LRA should not be active", resourcePath); + allActiveLRAs = lraManagement.getLRAs(LRAStatus.Active); + assertFalse("LRA '" + lra + "' should not be active anymore as it was closed yet. But it was not found amongst active LRAs " + + allActiveLRAs, containsLraId(allActiveLRAs, lra)); // check that participant was told to complete - int cnt2 = completedCount(true); - assertEquals(cnt1 + 1, cnt2, "joinLRAViaHeader: wrong completion count", resourcePath); - - return PASSED_TEXT; + int completedCount = getCompletedCount(); + assertEquals("Wrong completion count for call " + resourcePath.getUri() + ". Expecting the method LRA was completed " + + "after joining the existing LRA " + lra, beforeCompletedCount + 1, completedCount); } @Test - private String join() throws WebApplicationException { - List lras = lraSPI.getLRAs(LRAStatus.Active); + public void join() throws WebApplicationException { + List lras = lraManagement.getLRAs(LRAStatus.Active); int count = lras.size(); - URL lra = lraClient.startLRA(null, "SpecTest#join", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); Response response = resourcePath .request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); lraClient.closeLRA(lra); - lras = lraSPI.getLRAs(LRAStatus.Active); - System.out.printf("join ok %d versus %d lras%n", count, lras.size()); - assertEquals(count, lras.size(), "join: wrong LRA count", resourcePath); - - return lra.toExternalForm(); + lras = lraManagement.getLRAs(LRAStatus.Active); + assertEquals("Number of LRA instances before the test does not match current number of active LRAs. The joint LRA should be closed already. " + + "The test call went to LRA controller at " + resourcePath.getUri(), count, lras.size()); } @Test - private String leaveLRA() throws WebApplicationException { - int cnt1 = completedCount(true); - URL lra = lraClient.startLRA(null, "SpecTest#leaveLRA", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + public void leaveLRA() throws WebApplicationException { + int beforeCompletedCount = getCompletedCount(); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); Response response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); // perform a second request to the same method in the same LRA context to validate that multiple participants are not registered - resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); // call a method annotated with @Leave (should remove the participant from the LRA) - resourcePath = msTarget.path(ACTIVITIES_PATH).path("leave"); + resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("leave"); response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); lraClient.closeLRA(lra); // check that participant was not told to complete - int cnt2 = completedCount(true); + int completedCount = getCompletedCount(); - assertEquals(cnt1, cnt2, "leaveLRA: wrong completion count", resourcePath); - - return lra.toExternalForm(); + assertEquals("Wrong completion count when participant left the LRA. " + + "Expecting the completed count hasn't change between start and end of the test. " + + "The test call went to LRA controller at " + resourcePath.getUri(), beforeCompletedCount, completedCount); } @Test - private String leaveLRAViaAPI() throws WebApplicationException { - int cnt1 = completedCount(true); - URL lra = lraClient.startLRA(null, "SpecTest#leaveLRAViaAPI", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + public void leaveLRAViaAPI() throws WebApplicationException { + int beforeCompletedCount = getCompletedCount(); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); Response response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); // perform a second request to the same method in the same LRA context to validate that multiple participants are not registered - resourcePath = msTarget.path(ACTIVITIES_PATH).path(WORK_TEXT); + resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(TRANSACTIONAL_WORK_PATH); response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); String recoveryUrl = response.getHeaderString(LRAClient.LRA_HTTP_RECOVERY_HEADER); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); // call a method annotated with @Leave (should remove the participant from the LRA) try { - resourcePath = msTarget.path(ACTIVITIES_PATH).path("leave"); + resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("leave"); response = resourcePath.path(URLEncoder.encode(lra.toString(), "UTF-8")) .request() .header(LRAClient.LRA_HTTP_HEADER, lra) @@ -447,120 +526,104 @@ private String leaveLRAViaAPI() throws WebApplicationException { Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( Entity.text(String.format("%s: %s", resourcePath.getUri().toString(), e.getMessage()))).build()); } - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); lraClient.closeLRA(lra); // check that participant was not told to complete - int cnt2 = completedCount(true); - - assertEquals(cnt1, cnt2, - String.format("leaveLRAViaAPI: wrong count %d versus %d", cnt1, cnt2), resourcePath); + int completedCount = getCompletedCount(); - return PASSED_TEXT; + assertEquals("Wrong completion count when participant left the LRA by calling @Leave method. " + + "Expecting the completed count hasn't change between start and end of the test. " + + "The test call went to LRA controller at " + resourcePath.getUri(), beforeCompletedCount, completedCount); } @Test - private String dependentLRA() throws WebApplicationException { + public void dependentLRA() throws WebApplicationException, MalformedURLException { // call a method annotated with NOT_SUPPORTED but one which programatically starts an LRA and returns it via a header - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path("startViaApi"); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("startViaApi"); Response response = resourcePath.request().put(Entity.text("")); // check that the method started an LRA Object lraHeader = response.getHeaders().getFirst(LRAClient.LRA_HTTP_HEADER); - String id = checkStatusAndClose(response, Response.Status.OK.getStatusCode(), true, resourcePath); + String lraId = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); // the value returned via the header and body should be equal - assertNotNull(lraHeader, String.format("JAX-RS response to PUT request should have returned the header %s", - LRAClient.LRA_HTTP_HEADER), resourcePath); - assertNotNull(id, "JAX-RS response to PUT request should have returned content", resourcePath); - assertEquals(id, lraHeader.toString(), "dependentLRA: resource returned wrong LRA", resourcePath); + assertNotNull("JAX-RS response to PUT request should have returned the header " + LRAClient.LRA_HTTP_HEADER + + ". The test call went to " + resourcePath.getUri(), lraHeader); + assertNotNull("JAX-RS response to PUT request should have returned content of LRA id. The test call went to " + + resourcePath.getUri(), lraId); + assertEquals("The dependent LRA has to belong to the same LRA id. The test call went to " + resourcePath.getUri(), + lraId, lraHeader.toString()); - try { - lraClient.closeLRA(new URL(lraHeader.toString())); - } catch (MalformedURLException e) { - throw new WebApplicationException(e); - } - - return PASSED_TEXT; + lraClient.closeLRA(new URL(lraHeader.toString())); } @Test - private String cancelOn() { + public void cancelOn() { cancelCheck("cancelOn"); - - return PASSED_TEXT; } @Test - private String cancelOnFamily() { + public void cancelOnFamily() { cancelCheck("cancelOnFamily"); - - return PASSED_TEXT; } @Test - private String timeLimit() { - int[] cnt1 = {completedCount(true), completedCount(false)}; - Response response = null; - - try { - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path("timeLimit"); - response = resourcePath - .request() - .get(); - - checkStatusAndClose(response, -1, true, resourcePath); - - // Note that the timeout firing will cause the coordinator to compensate - // the LRA so it may no longer exist - // (depends upon how long the coordinator keeps a record of finished LRAs + public void timeLimit() { + int beforeCompletedCount = getCompletedCount(); + int beforeCompensatedCount = getCompensatedCount(); + + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("timeLimit"); + Response response = resourcePath + .request() + .get(); - // check that participant was invoked - int[] cnt2 = {completedCount(true), completedCount(false)}; + response.close(); - /* - * The call to activities/timeLimit should have started an LRA which should have timed out - * (because the invoked resource method sleeps for longer than the timeLimit annotation - * attribute specifies). Therefore the participant should have compensated: - */ - assertEquals(cnt1[0], cnt2[0], - "timeLimit: complete was called instead of compensate", resourcePath); - assertEquals(cnt1[1] + 1, cnt2[1], - "timeLimit: compensate should have been called", resourcePath); - } finally { + // Note that the timeout firing will cause the coordinator to compensate + // the LRA so it may no longer exist + // (depends upon how long the coordinator keeps a record of finished LRAs - if (response != null) { - response.close(); - } - } + // check that participant was invoked + int completedCount = getCompletedCount(); + int compensatedCount = getCompensatedCount(); - return PASSED_TEXT; + /* + * The call to activities/timeLimit should have started an LRA which should have timed out + * (because the invoked resource method sleeps for longer than the timeLimit annotation + * attribute specifies). Therefore the participant should have compensated: + */ + assertEquals("The LRA should have timed out but complete was called instead of compensate. " + + "Expecting the number of complete call before test matches the ones after LRA timed out. " + + "The test call went to " + resourcePath.getUri(), beforeCompletedCount, completedCount); + assertEquals("The LRA should have timed out and compensate should be called. " + + "Expecting the number of compensate call before test is one less lower than the ones after LRA timed out. " + + "The test call went to " + resourcePath.getUri(), beforeCompensatedCount + 1, compensatedCount); } /* * Participants can pass data during enlistment and this data will be returned during * the complete/compensate callbacks */ + // TODO: is this test to be run? private void testUserData() { - List lras = lraSPI.getLRAs(LRAStatus.Active); - int count = lras.size(); + List beforeActiveLRAs = lraManagement.getLRAs(LRAStatus.Active); String testData = "test participant data"; - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path("testUserData"); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("testUserData"); Response response = resourcePath .request().put(Entity.text(testData)); - String activityId = response.readEntity(String.class); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + String activityId = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); - lras = lraSPI.getLRAs(LRAStatus.Active); + List activeLRAs = lraManagement.getLRAs(LRAStatus.Active); - assertEquals(count, lras.size(), "testUserData: testUserData produced the wrong LRA count", - resourcePath); + assertEquals("produced the wrong LRA count on call of method " + resourcePath.getUri(), + beforeActiveLRAs.size(), activeLRAs.size()); - response = msTarget.path(ACTIVITIES_PATH).path("getActivity") + response = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("getActivity") .queryParam("activityId", activityId) .request() .get(); @@ -568,27 +631,25 @@ private void testUserData() { String activity = response.readEntity(String.class); // validate that the service received the correct data during the complete call - assertTrue(activity.contains("userData='" + testData), null, null, null); - assertTrue(activity.contains("endData='" + testData), null, null, null); + assertTrue("service should get received userData field during complete call", activity.contains("userData='" + testData)); + assertTrue("service should get received endData field during complete call", activity.contains("endData='" + testData)); } @Test - private String acceptTest() throws WebApplicationException { - joinAndEnd(true, true, ACTIVITIES_PATH, ACCEPT_WORK); - return PASSED_TEXT; + public void acceptTest() throws WebApplicationException { + joinAndEnd(true, true, LRA_CONTROLLER_PATH, ACCEPT_WORK); } // TODO the spec does not specifiy recovery semantics - @Test private void joinAndEnd(boolean waitForRecovery, boolean close, String path, String path2) throws WebApplicationException { - int countBefore = lraSPI.getLRAs(LRAStatus.Active).size(); - URL lra = lraClient.startLRA(null, "SpecTest#join", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - WebTarget resourcePath = msTarget.path(path).path(path2); + int beforeActiveLRACount = lraManagement.getLRAs(LRAStatus.Active).size(); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); + WebTarget resourcePath = tckSuiteTarget.path(path).path(path2); Response response = resourcePath .request().header(LRAClient.LRA_HTTP_HEADER, lra).put(Entity.text("")); - checkStatusAndClose(response, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response, resourcePath); if (close) { lraClient.closeLRA(lra); @@ -599,137 +660,130 @@ private void joinAndEnd(boolean waitForRecovery, boolean close, String path, Str if (waitForRecovery) { // trigger a recovery scan which trigger a replay attempt on any participants // that have responded to complete/compensate requests with Response.Status.ACCEPTED - resourcePath = recoveryTarget.path(RECOVERY_PATH_TEXT); + resourcePath = recoveryTarget.path("recovery"); Response response2 = resourcePath .request().get(); - checkStatusAndClose(response2, Response.Status.OK.getStatusCode(), false, resourcePath); + checkStatusAndClose(Response.Status.OK, response2, resourcePath); } - int countAfter = lraSPI.getLRAs(LRAStatus.Active).size(); + int activeLRACount = lraManagement.getLRAs(LRAStatus.Active).size(); - assertEquals(countBefore, countAfter, "joinAndEnd: some LRAs were not recovered", resourcePath); + assertEquals("Expecting all LRAs were recovered and the number of active LRAs before test matches the number after. " + + "The test call went to " + resourcePath.getUri(), + beforeActiveLRACount, activeLRACount); } @Test - private String noLRATest() throws WebApplicationException { - WebTarget resourcePath = msTarget - .path(StandardController.ACTIVITIES_PATH3) - .path(StandardController.NON_TRANSACTIONAL_WORK); + public void noLRATest() throws WebApplicationException { + WebTarget resourcePath = tckSuiteTarget + .path(NoLRAController.NO_LRA_CONTROLLER_PATH) + .path(NoLRAController.NON_TRANSACTIONAL_WORK_PATH); - int[] cnt1 = {completedCount(true), completedCount(false)}; - URL lra = lraClient.startLRA(null, "SpecTest#noLRATest", - LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + int beforeCompletedCount = getCompletedCount(); + int beforeCompensatedCount = getCompensatedCount(); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); Response response = resourcePath.request().header(LRAClient.LRA_HTTP_HEADER, lra) .put(Entity.text("")); - String result = checkStatusAndClose(response, Response.Status.OK.getStatusCode(), - true, resourcePath); + String lraId = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); - assertEquals(result, lra.toExternalForm(), "service returned the wrong LRA", null); + assertEquals("While calling non-LRA method the controller returns not expected LRA id", + lraId, lra.toExternalForm()); lraClient.cancelLRA(lra); // check that second service (the LRA aware one), namely // {@link org.eclipse.microprofile.lra.tck.participant.api.ActivityController#activityWithMandatoryLRA(String, String)} // was told to compensate - int[] cnt2 = {completedCount(true), completedCount(false)}; - - assertEquals(cnt1[0], cnt2[0], "complete should not have been called", resourcePath); - assertEquals(cnt1[1] + 1, cnt2[1], "compensate should have been called", resourcePath); + int completedCount = getCompletedCount(); + int compensatedCount = getCompensatedCount(); - return PASSED_TEXT; + assertEquals("Complete should not be called on the LRA aware service. " + + "The number of completed count for before and after test does not match. " + + "The test call went to " + resourcePath.getUri(), beforeCompletedCount, completedCount); + assertEquals("Compensate service should be called on LRA aware service. The number of compensated count after test is bigger for one. " + + "The test call went to " + resourcePath.getUri(), beforeCompensatedCount + 1, compensatedCount); } + // TODO: is this test to be run? private void renewTimeLimit() { - int[] cnt1 = {completedCount(true), completedCount(false)}; - Response response = null; + int beforeCompletedCount = getCompletedCount(); + int beforeCompensatedCount = getCompensatedCount(); - try { - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH) - .path("renewTimeLimit"); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH) + .path("renewTimeLimit"); - response = resourcePath - .request() - .get(); + Response response = resourcePath + .request() + .get(); - checkStatusAndClose(response, -1, true, resourcePath); + response.close(); - // check that participant was invoked - int[] cnt2 = {completedCount(true), completedCount(false)}; + // check that participant was invoked + int completedCount = getCompletedCount(); + int compensatedCount = getCompensatedCount(); - /* - * The call to activities/timeLimit should have started an LRA whch should not have timed out - * (because the called resource method renews the timeLimit before sleeping for longer than - * the timeLimit annotation attribute specifies). - * Therefore the participant should not have compensated: - */ - assertEquals(cnt1[0] + 1, cnt2[0], - resourcePath.getUri().toString() + ": compensate was called instead of complete", resourcePath); - assertEquals(cnt1[1], cnt2[1], - resourcePath.getUri().toString() + ": compensate should not have been called", resourcePath); - } finally { - if (response != null) { - response.close(); - } - } + /* + * The call to activities/timeLimit should have started an LRA whch should not have timed out + * (because the called resource method renews the timeLimit before sleeping for longer than + * the timeLimit annotation attribute specifies). + * Therefore the participant should not have compensated: + */ + assertEquals("Compensate was called instead of complete. The test call went to " + resourcePath.getUri(), + beforeCompletedCount + 1, completedCount); + assertEquals("Compensate should not have been called. The test call went to " + resourcePath.getUri(), + beforeCompensatedCount, compensatedCount); } - private String checkStatusAndClose(Response response, int expected, boolean readEntity, WebTarget webTarget) { + private void checkStatusAndClose(Response.Status expectedStatus, Response response, WebTarget resourcePath) { try { - if (expected != -1 && response.getStatus() != expected) { - if (webTarget != null) { - throw new WebApplicationException(String.format("%s: expected status %d got %d", - webTarget.getUri().toString(), expected, response.getStatus()), response); - } - - throw new WebApplicationException(response); - } - - if (readEntity) { - return response.readEntity(String.class); - } + assertEquals("Not expected status at call '" + resourcePath.getUri() + "'", + expectedStatus.getStatusCode(), response.getStatus()); } finally { response.close(); } - - return null; } - private int completedCount(boolean completed) { - Response response = null; - String path = completed ? "completedactivitycount" : "compensatedactivitycount"; - + private String checkStatusReadAndClose(Response.Status expectedStatus, Response response, WebTarget resourcePath) { try { - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(path); + assertEquals("Response status on call to '" + resourcePath.getUri() + "' failed to match.", + expectedStatus.getStatusCode(), response.getStatus()); + return response.readEntity(String.class); + } finally { + response.close(); + } + } - response = resourcePath.request().get(); + private int getCompletedCount() { + return getActivityCount("completedactivitycount"); + } - assertEquals(Response.Status.OK.getStatusCode(), - response.getStatus(), - resourcePath.getUri().toString() + ": wrong status", - resourcePath); + private int getCompensatedCount() { + return getActivityCount("compensatedactivitycount"); + } - return Integer.parseInt(response.readEntity(String.class)); - } finally { - if (response != null) { - response.close(); - } - } + private int getActivityCount(String activityCountTargetPath) { + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH) + .path(activityCountTargetPath); + Response response = resourcePath.request().get(); + String count = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); + return Integer.parseInt(count); } - private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throws WebApplicationException { - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path("multiLevelNestedActivity"); + private void multiLevelNestedActivity(CompletionType how, int nestedCnt) throws WebApplicationException { + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path("multiLevelNestedActivity"); - int[] cnt1 = {completedCount(true), completedCount(false)}; + int beforeCompletedCount = getCompletedCount(); + int beforeCompensatedCount = getCompensatedCount(); if (how == CompletionType.mixed && nestedCnt <= 1) { how = CompletionType.complete; } - URL lra = lraClient.startLRA(null, "SpecTest#multiLevelNestedActivity", LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); + URL lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS); String lraId = lra.toString(); Response response = resourcePath @@ -738,10 +792,11 @@ private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throw .header(LRAClient.LRA_HTTP_HEADER, lra) .put(Entity.text("")); - String lraStr = checkStatusAndClose(response, Response.Status.OK.getStatusCode(), true, resourcePath); + String lraStr = checkStatusReadAndClose(Response.Status.OK, response, resourcePath); + assertNotNull("expecting a LRA string returned from " + resourcePath.getUri(), lraStr); assert lraStr != null; String[] lraArray = lraStr.split(","); - final List lras = lraSPI.getLRAs(LRAStatus.Active); + final List lras = lraManagement.getLRAs(LRAStatus.Active); URL[] urls = new URL[lraArray.length]; IntStream.range(0, urls.length).forEach(i -> { @@ -753,24 +808,30 @@ private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throw } }); // check that the multiLevelNestedActivity method returned the mandatory LRA followed by any nested LRAs - assertEquals(nestedCnt + 1, lraArray.length, "multiLevelNestedActivity: step 1", resourcePath); - assertEquals(lraId, lraArray[0], "multiLevelNestedActivity: step 2", resourcePath); // first element should be the mandatory LRA + assertEquals("multiLevelNestedActivity: step 1 (the test call went to " + resourcePath.getUri() + ")", + nestedCnt + 1, lraArray.length); + // first element should be the mandatory LRA + assertEquals("multiLevelNestedActivity: step 2 (the test call went to " + resourcePath.getUri() + ")", + lraId, lraArray[0]); // check that the coordinator knows about the two nested LRAs started by the multiLevelNestedActivity method // NB even though they should have completed they are held in memory pending the enclosing LRA finishing - IntStream.rangeClosed(1, nestedCnt).forEach(i -> assertNotNull(getLra(lras, lraArray[i]), - " missing nested LRA: step 2b", - resourcePath)); + IntStream.rangeClosed(1, nestedCnt).forEach(i -> assertTrue("missing nested LRA: step 2b (path called " + resourcePath + ")", + containsLraId(lras, lraArray[i]))); // and the mandatory lra seen by the multiLevelNestedActivity method - assertNotNull(getLra(lras, lraArray[0]), "lra should have been found", resourcePath); + assertTrue("lra should have been found (path called " + resourcePath.getUri() + ")", + containsLraId(lras, lraArray[0])); - int[] cnt2 = {completedCount(true), completedCount(false)}; + int inMiddleCompletedCount = getCompletedCount(); + int inMiddleCompensatedCount = getCompensatedCount(); // check that all nested activities were told to complete - assertEquals(cnt1[0] + nestedCnt, cnt2[0], "multiLevelNestedActivity: step 3", resourcePath); + assertEquals("multiLevelNestedActivity: step 3 (called test path " + resourcePath.getUri() + ")", + beforeCompletedCount + nestedCnt, inMiddleCompletedCount); // and that neither were told to compensate - assertEquals(cnt1[1], cnt2[1], "multiLevelNestedActivity: step 4", resourcePath); + assertEquals("multiLevelNestedActivity: step 4 (called test path " + resourcePath.getUri() + ")", + beforeCompensatedCount, inMiddleCompensatedCount); // close the LRA if (how == CompletionType.compensate) { @@ -797,18 +858,22 @@ private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throw } // validate that the top level and nested LRAs are gone - final List lras2 = lraSPI.getLRAs(LRAStatus.Active); + final List lras2 = lraManagement.getLRAs(LRAStatus.Active); - IntStream.rangeClosed(0, nestedCnt).forEach(i -> assertNull(getLra(lras2, lraArray[i]), - "multiLevelNestedActivity: top level or nested activity still active", resourcePath)); + IntStream.rangeClosed(0, nestedCnt).forEach(i -> assertFalse( + "multiLevelNestedActivity: top level or nested activity still active (called path " + resourcePath.getUri() + ")", + containsLraId(lras2, lraArray[i]))); - int[] cnt3 = {completedCount(true), completedCount(false)}; + int afterCompletedCount = getCompletedCount(); + int afterCompensatedCount = getCompensatedCount(); if (how == CompletionType.complete) { // make sure that all nested activities were not told to complete or cancel a second time - assertEquals(cnt2[0] + nestedCnt, cnt3[0], "multiLevelNestedActivity: step 5", resourcePath); + assertEquals("multiLevelNestedActivity: step 5 (called test path " + resourcePath.getUri() + ")", + inMiddleCompletedCount + nestedCnt, afterCompletedCount); // and that neither were still not told to compensate - assertEquals(cnt1[1], cnt3[1], "multiLevelNestedActivity: step 6", resourcePath); + assertEquals("multiLevelNestedActivity: step 6 (called test path " + resourcePath.getUri() + ")", + beforeCompensatedCount, afterCompensatedCount); } else if (how == CompletionType.compensate) { /* @@ -819,9 +884,11 @@ private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throw * which will then tell L2 to compenstate (ie the compensate count is incrememted again) */ // each nested participant should have completed (the +nestedCnt) - assertEquals(cnt1[0] + nestedCnt, cnt3[0], "multiLevelNestedActivity: step 7", resourcePath); + assertEquals("multiLevelNestedActivity: step 7 (called test path " + resourcePath.getUri() + ")", + beforeCompletedCount + nestedCnt, afterCompletedCount); // each nested participant should have compensated. The top level enlistement should have compensated (the +1) - assertEquals(cnt2[1] + 1 + nestedCnt, cnt3[1], "multiLevelNestedActivity: step 8", resourcePath); + assertEquals("multiLevelNestedActivity: step 8 (called test path " + resourcePath.getUri() + ")", + inMiddleCompensatedCount + 1 + nestedCnt, afterCompensatedCount); } else { /* * The test is calling for a mixed uutcome: @@ -829,99 +896,72 @@ private String multiLevelNestedActivity(CompletionType how, int nestedCnt) throw * - one of the nested LRAs was compensated the rest should have been completed */ // there should be just 1 compensation (the first nested LRA) - assertEquals(1, cnt3[1] - cnt1[1], "multiLevelNestedActivity: step 9", resourcePath); + assertEquals("multiLevelNestedActivity: step 9 (called test path " + resourcePath.getUri() + ")", + 1, afterCompensatedCount - beforeCompensatedCount); /* * Expect nestedCnt + 1 completions, 1 for the top level and one for each nested LRA * (NB the first nested LRA is completed and compensated) * Note that the top level complete should not call complete again on the nested LRA */ - assertEquals(nestedCnt + 1, cnt3[0] - cnt1[0], "multiLevelNestedActivity: step 10", resourcePath); + assertEquals("multiLevelNestedActivity: step 10 (called test path " + resourcePath.getUri() + ")", + nestedCnt + 1, afterCompletedCount - beforeCompletedCount); } - - return PASSED_TEXT; } private void cancelCheck(String path) { - int[] cnt1 = {completedCount(true), completedCount(false)}; - URL lra = lraClient.startLRA(null, "SpecTest#" + path, LRA_TIMEOUT_MILLIS, ChronoUnit.MILLIS); - Response response = null; + int beforeCompletedCount = getCompletedCount(); + int beforeCompensatedCount = getCompensatedCount(); - WebTarget resourcePath = msTarget.path(ACTIVITIES_PATH).path(path); + URL lra = lraClient.startLRA(null, "SpecTest#" + path, lraTimeout(), ChronoUnit.MILLIS); - try { - response = resourcePath - .request() - .header(LRAClient.LRA_HTTP_HEADER, lra) - .get(); + WebTarget resourcePath = tckSuiteTarget.path(LRA_CONTROLLER_PATH).path(path); - checkStatusAndClose(response, Response.Status.BAD_REQUEST.getStatusCode(), true, resourcePath); + Response response = resourcePath + .request() + .header(LRAClient.LRA_HTTP_HEADER, lra) + .get(); - // check that participant was invoked - int[] cnt2 = {completedCount(true), completedCount(false)}; + checkStatusReadAndClose(Response.Status.BAD_REQUEST, response, resourcePath); - // check that complete was not called and that compensate was - assertEquals(cnt1[0], cnt2[0], "complete was called instead of compensate", resourcePath); - assertEquals(cnt1[1] + 1, cnt2[1], "compensate should have been called", resourcePath); + // check that participant was invoked + int completedCount = getCompletedCount(); + int compensatedCount = getCompensatedCount(); - try { - assertTrue(lraClient.getStatus(lra) != LRAStatus.Active, "cancelCheck: LRA should have been cancelled", resourcePath, lra); - } catch (NotFoundException ignore) { - // means the LRA has gone - } - } finally { - if (response != null) { - response.close(); - } - } - } + // check that complete was not called and that compensate was + assertEquals("complete was called instead of compensate (called to " + resourcePath.getUri() + ")", + beforeCompletedCount, completedCount); + assertEquals("compensate should have been called (called to " + resourcePath.getUri() + ")", + beforeCompensatedCount + 1, compensatedCount); - private static LRAInfo getLra(List lras, String lraId) { - for (LRAInfo lraInfo : lras) { - if (lraInfo.getLraId().equals(lraId)) { - return lraInfo; - } + try { + assertNotEquals("LRA '" + lra + "' should have been cancelled (called to " + resourcePath.getUri() + ")", + LRAStatus.Active, lraManagement.getStatus(lra)); + } catch (NotFoundException ignore) { + // means the LRA has gone } - - return null; } - private static void assertTrue(boolean condition, String reason, WebTarget target, URL lra) { -// assert condition; - - if (!condition) { - throw new GenericLRAException(lra, 0, target.getUri().toString() + ": " + reason, null); - } + private boolean containsLraId(List lras, URL lraIdURL) { + String lraId = lraIdURL.toExternalForm(); + return containsLraId(lras, lraId); } - private static void assertEquals(T expected, T actual, String reason, WebTarget target) { -// assert expected.equals(actual); - - if (!expected.equals(actual)) { - throw new GenericLRAException(null, 0, target.getUri().toString() + ": " + reason, null); - } - } - private static void fail(String msg) { - System.out.printf("%s%n", msg); - assert false; + private boolean containsLraId(List lras, String lraId) { + return lras.stream().anyMatch(lrainfo -> lrainfo.getLraId().equals(lraId)); } - private static void assertNotNull(T value, String reason, WebTarget target) { - if (value == null) { - if (target == null) { - throw new GenericLRAException(null, 0, reason, null); - } else { - throw new GenericLRAException(null, 0, target.getUri().toString() + reason, null); - } - } + /** + * 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(); } - private static void assertNull(T value, String reason, WebTarget target) { - if (value != null) { - if (target == null) { - throw new GenericLRAException(null, 0, reason, null); - } else { - throw new GenericLRAException(null, 0, target.getUri().toString() + reason, null); - } - } + /** + * Adjusting the default timeout by the specified timeout factor + * which can be defined by user. + */ + private long lraTimeout() { + return Util.adjust(LRA_TIMEOUT_MILLIS, timeoutFactor); } } diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/model/Activity.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/Activity.java similarity index 67% rename from tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/model/Activity.java rename to tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/Activity.java index 26338ae1..69bcaa62 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/model/Activity.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/Activity.java @@ -17,16 +17,22 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package org.eclipse.microprofile.lra.tck.participant.model; +package org.eclipse.microprofile.lra.tck.participant.activity; import org.eclipse.microprofile.lra.annotation.ParticipantStatus; import java.io.Serializable; import java.util.concurrent.atomic.AtomicInteger; +/** + * A simple DTO that provides information about + * the work processed in the TCK suite controllers. + */ public class Activity implements Serializable { - private String id; - private String rcvUrl; + private static final long serialVersionUID = 1L; + + private String lraId; + private String recoveryUrl; private String statusUrl; private ParticipantStatus status; private String userData; @@ -34,47 +40,51 @@ public class Activity implements Serializable { private final AtomicInteger acceptedCount = new AtomicInteger(0); - public Activity(String txId) { - this.setId(txId); + public Activity(String lraId) { + this.setLraId(lraId); } - public String getId() { - return id; + public String getLraId() { + return lraId; } - public void setId(String id) { - this.id = id; + public Activity setLraId(String lraId) { + this.lraId = lraId; + return this; } public String getRcvUrl() { - return rcvUrl; + return recoveryUrl; } - public void setRcvUrl(String rcvUrl) { - this.rcvUrl = rcvUrl; + public Activity setRecoveryUrl(String recoveryUrl) { + this.recoveryUrl = recoveryUrl; + return this; } public String getStatusUrl() { return statusUrl; } - public void setStatusUrl(String statusUrl) { + public Activity setStatusUrl(String statusUrl) { this.statusUrl = statusUrl; + return this; } public ParticipantStatus getStatus() { return status; } - public void setStatus(ParticipantStatus status) { + public Activity setStatus(ParticipantStatus status) { this.status = status; + return this; } @Override public String toString() { return "Activity{" + - "id='" + getId() + '\'' + - ", rcvUrl='" + getRcvUrl() + '\'' + + "lraId='" + getLraId() + '\'' + + ", recoveryUrl='" + getRcvUrl() + '\'' + ", statusUrl='" + getStatusUrl() + '\'' + ", status=" + getStatus() + ", userData='" + getUserData() + '\'' + @@ -90,23 +100,26 @@ public String getUserData() { return userData; } - public void setUserData(String userData) { + public Activity setUserData(String userData) { this.userData = userData; + return this; } public String getEndData() { return endData; } - public void setEndData(String endData) { + public Activity setEndData(String endData) { this.endData = endData; + return this; } public AtomicInteger getAcceptedCount() { return acceptedCount; } - public void setAcceptedCount(int acceptedCount) { + public Activity setAcceptedCount(int acceptedCount) { this.acceptedCount.set(acceptedCount); + return this; } } diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/service/ActivityService.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/ActivityStorage.java similarity index 63% rename from tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/service/ActivityService.java rename to tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/ActivityStorage.java index f8aeb93a..c5e2643a 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/service/ActivityService.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/activity/ActivityStorage.java @@ -17,35 +17,42 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -package org.eclipse.microprofile.lra.tck.participant.service; +package org.eclipse.microprofile.lra.tck.participant.activity; -import org.eclipse.microprofile.lra.tck.participant.model.Activity; - -import javax.enterprise.context.ApplicationScoped; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * Storing activities processed by controllers during TCK suite run. + */ @ApplicationScoped -public class ActivityService { +public class ActivityStorage { private Map activities = new HashMap<>(); - public Activity getActivity(String txId) throws NotFoundException { - if (!activities.containsKey(txId)) - throw new NotFoundException(Response.status(404).entity("Invalid activity id: " + txId).build()); + public Activity getActivityAndAssertExistence(String lraId, UriInfo jaxrsContext) { + if(!activities.containsKey(lraId)) { + String errorMessage = String.format("Activity store does not contain LRA id '%s', " + + "invoked from endpoint '%s'", lraId, jaxrsContext.getPath()); + throw new NotFoundException(Response.status(404).entity(errorMessage).build()); + } - return activities.get(txId); + return activities.get(lraId); } public List findAll() { return new ArrayList<>(activities.values()); } - public void add(Activity activity) { - activities.putIfAbsent(activity.getId(), activity); + public Activity add(Activity activity) { + activities.putIfAbsent(activity.getLraId(), activity); + return activities.get(activity.getLraId()); } public void remove(String id) { diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ActivityController.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java similarity index 77% rename from tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ActivityController.java rename to tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java index 756bcf3d..7461eaae 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ActivityController.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java @@ -23,7 +23,8 @@ import org.eclipse.microprofile.lra.client.GenericLRAException; import org.eclipse.microprofile.lra.client.InvalidLRAIdException; import org.eclipse.microprofile.lra.client.LRAClient; -import org.eclipse.microprofile.lra.tck.participant.service.ActivityService; +import org.eclipse.microprofile.lra.tck.participant.activity.Activity; +import org.eclipse.microprofile.lra.tck.participant.activity.ActivityStorage; import org.eclipse.microprofile.lra.annotation.LRA; import org.eclipse.microprofile.lra.annotation.Compensate; import org.eclipse.microprofile.lra.annotation.Complete; @@ -31,7 +32,6 @@ import org.eclipse.microprofile.lra.annotation.NestedLRA; import org.eclipse.microprofile.lra.annotation.Status; import org.eclipse.microprofile.lra.client.IllegalLRAStateException; -import org.eclipse.microprofile.lra.tck.participant.model.Activity; import org.eclipse.microprofile.lra.annotation.ParticipantStatus; import javax.enterprise.context.ApplicationScoped; @@ -69,17 +69,17 @@ import static org.eclipse.microprofile.lra.client.LRAClient.LRA_HTTP_RECOVERY_HEADER; @ApplicationScoped -@Path(ActivityController.ACTIVITIES_PATH) +@Path(LraController.LRA_CONTROLLER_PATH) @LRA(value = LRA.Type.SUPPORTS, end = false) -public class ActivityController { - public static final String ACTIVITIES_PATH = "activities"; +public class LraController { + public static final String LRA_CONTROLLER_PATH = "lracontroller"; + public static final String TRANSACTIONAL_WORK_PATH = "work"; public static final String ACCEPT_WORK = "acceptWork"; - private static final Logger LOGGER = Logger.getLogger(ActivityController.class.getName()); + private static final Logger LOGGER = Logger.getLogger(LraController.class.getName()); - static final String WORK_RESOURCE_PATH = "/work"; static final String MANDATORY_LRA_RESOURCE_PATH = "/mandatory"; - private static final String MISSING_LRA_DATA = "Missing lra data"; + private static final String MISSING_LRA_DATA = "Missing LRA data"; @Inject private LRAClient lraClient; @@ -91,7 +91,7 @@ public class ActivityController { private UriInfo context; @Inject - private ActivityService activityService; + private ActivityStorage activityStore; /** * Performing a GET on the participant URL will return the current status of the @@ -107,7 +107,7 @@ public class ActivityController { @Status @LRA(value = LRA.Type.NOT_SUPPORTED) public Response status(@HeaderParam(LRA_HTTP_HEADER) String lraId) throws NotFoundException { - Activity activity = activityService.getActivity(lraId); + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); if (activity.getStatus() == null) { throw new IllegalLRAStateException(lraId, "getStatus", "LRA is not active"); @@ -127,6 +127,7 @@ public Response status(@HeaderParam(LRA_HTTP_HEADER) String lraId) throws NotFou /** * Test that participants can leave an LRA using the {@link LRAClient} programmatic API * @param lraUrl the id of the LRA + * @param recoveryUrl header param defines recovery url * @return the url of the LRA if it was successfully removed * @throws NotFoundException if the activity was not found * @throws MalformedURLException if the LRA is malformed @@ -141,9 +142,9 @@ public Response leaveWorkViaAPI(@PathParam("LraUrl")String lraUrl, if (lraUrl != null && recoveryUrl != null) { lraClient.leaveLRA(new URL(recoveryUrl)); - activityService.getActivity(lraUrl); + activityStore.getActivityAndAssertExistence(lraUrl, context); - activityService.remove(lraUrl); + activityStore.remove(lraUrl); return Response.ok(lraUrl).build(); } @@ -159,9 +160,9 @@ public Response leaveWork(@HeaderParam(LRA_HTTP_HEADER) String lraId) throws NotFoundException { if (lraId != null) { - activityService.getActivity(lraId); + activityStore.getActivityAndAssertExistence(lraId, context); - activityService.remove(lraId); + activityStore.remove(lraId); return Response.ok(lraId).build(); } @@ -179,14 +180,14 @@ public Response completeWork(@HeaderParam(LRA_HTTP_HEADER) String lraId, String assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Complete methods - Activity activity = activityService.getActivity(lraId); + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); activity.setEndData(userData); if (activity.getAndDecrementAcceptCount() > 0) { activity.setStatus(ParticipantStatus.Completing); activity.setStatusUrl(String.format("%s/%s/%s/status", context.getBaseUri(), - ACTIVITIES_PATH, lraId)); + LRA_CONTROLLER_PATH, lraId)); return Response.accepted().location(URI.create(activity.getStatusUrl())).build(); } @@ -194,7 +195,7 @@ public Response completeWork(@HeaderParam(LRA_HTTP_HEADER) String lraId, String activity.setStatus(ParticipantStatus.Completed); activity.setStatusUrl(String.format("%s/%s/activity/completed", context.getBaseUri(), lraId)); - System.out.printf("ActivityController completing %s%n", lraId); + LOGGER.info(String.format("LRA id '%s' was completed", lraId)); return Response.ok(activity.getStatusUrl()).build(); } @@ -209,14 +210,14 @@ public Response compensateWork(@HeaderParam(LRA_HTTP_HEADER) String lraId, Strin COMPENSATED_COUNT.incrementAndGet(); - Activity activity = activityService.getActivity(lraId); + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); activity.setEndData(userData); if (activity.getAndDecrementAcceptCount() > 0) { activity.setStatus(ParticipantStatus.Compensating); activity.setStatusUrl(String.format("%s/%s/%s/status", context.getBaseUri(), - ACTIVITIES_PATH, lraId)); + LRA_CONTROLLER_PATH, lraId)); return Response.accepted().location(URI.create(activity.getStatusUrl())).build(); } @@ -224,7 +225,7 @@ public Response compensateWork(@HeaderParam(LRA_HTTP_HEADER) String lraId, Strin activity.setStatus(ParticipantStatus.Compensated); activity.setStatusUrl(String.format("%s/%s/activity/compensated", context.getBaseUri(), lraId)); - System.out.printf("ActivityController compensating %s%n", lraId); + LOGGER.info(String.format("LRA id '%s' was compensated", lraId)); return Response.ok(activity.getStatusUrl()).build(); } @@ -237,32 +238,33 @@ public Response forgetWork(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Forget methods - Activity activity = activityService.getActivity(lraId); + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); - activityService.remove(activity.getId()); + if(activity == null) { + throw new IllegalStateException( + String.format("Activity store does not contain LRA id '%s' while it was invoked forget method at " + context.getPath())); + } + + activityStore.remove(activity.getLraId()); activity.setStatus(ParticipantStatus.Completed); activity.setStatusUrl(String.format("%s/%s/activity/completed", context.getBaseUri(), lraId)); - System.out.printf("ActivityController forgetting %s%n", lraId); + LOGGER.info(String.format("LRA id '%s' was forgotten", lraId)); return Response.ok(activity.getStatusUrl()).build(); } @PUT - @Path(ActivityController.ACCEPT_WORK) + @Path(LraController.ACCEPT_WORK) @LRA(value = LRA.Type.REQUIRED, end = false) public Response acceptWork( - @HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcvId, + @HeaderParam(LRA_HTTP_RECOVERY_HEADER) String recoveryId, @HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - Activity activity = addWork(lraId, rcvId); - - if (activity == null) { - return Response.status(Response.Status.EXPECTATION_FAILED).entity(MISSING_LRA_DATA).build(); - } + Activity activity = storeActivity(lraId, recoveryId); - activity.setAcceptedCount(1); // tests that it is possible to asynchronously complete + activity.setAcceptedCount(1); // later tests that it is possible to asynchronously complete return Response.ok(lraId).build(); } @@ -272,7 +274,7 @@ public Response acceptWork( public Response supportsLRACall(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - addWork(lraId, null); + storeActivity(lraId, null); return Response.ok(lraId).build(); } @@ -288,7 +290,7 @@ public Response subActivity(@HeaderParam(LRA_HTTP_HEADER) String lraId) { lraId = lra.toString(); - addWork(lraId, null); + storeActivity(lraId, null); // invoke a method that SUPPORTS LRAs. The filters should detect the LRA we just // started via the injected client @@ -305,26 +307,26 @@ public Response subActivity(@HeaderParam(LRA_HTTP_HEADER) String lraId) { } @PUT - @Path(WORK_RESOURCE_PATH) + @Path(TRANSACTIONAL_WORK_PATH) @LRA(value = LRA.Type.REQUIRED, end = false) - public Response activityWithLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcvId, + public Response activityWithLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String recoveryId, @HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - Activity activity = addWork(lraId, rcvId); + Activity activity = storeActivity(lraId, recoveryId); if (activity == null) { return Response.status(Response.Status.EXPECTATION_FAILED).entity(MISSING_LRA_DATA).build(); } - return Response.ok(lraId).header(LRA_HTTP_RECOVERY_HEADER, rcvId).build(); + return Response.ok(lraId).header(LRA_HTTP_RECOVERY_HEADER, recoveryId).build(); } private String restPutInvocation(URL lraURL, String path, String bodyText) { String id = null; Response response = ClientBuilder.newClient() .target(context.getBaseUri()) - .path("activities") + .path(LRA_CONTROLLER_PATH) .path(path) .request() .header(LRAClient.LRA_HTTP_HEADER, lraURL) @@ -334,7 +336,14 @@ private String restPutInvocation(URL lraURL, String path, String bodyText) { id = response.readEntity(String.class); } - checkStatusAndClose(response, Response.Status.OK.getStatusCode()); + try { + if (response.getStatus() != Response.Status.OK.getStatusCode()) { + throw new WebApplicationException("Error on REST PUT for LRA '" + lraURL + + "' at path '" + path + "' and body '" + bodyText + "'", response); + } + } finally { + response.close(); + } return id; } @@ -342,24 +351,20 @@ private String restPutInvocation(URL lraURL, String path, String bodyText) { @PUT @Path(MANDATORY_LRA_RESOURCE_PATH) @LRA(value = LRA.Type.MANDATORY, end = false) - public Response activityWithMandatoryLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcvId, + public Response activityWithMandatoryLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String recoveryId, @HeaderParam(LRA_HTTP_HEADER) String lraId) { - return activityWithLRA(rcvId, lraId); + return activityWithLRA(recoveryId, lraId); } @PUT @Path("/nestedActivity") @LRA(value = LRA.Type.MANDATORY, end = true) @NestedLRA - public Response nestedActivity(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcvId, + public Response nestedActivity(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String recoveryId, @HeaderParam(LRA_HTTP_HEADER) String nestedLRAId) { assertHeaderPresent(nestedLRAId); - Activity activity = addWork(nestedLRAId, rcvId); - - if (activity == null) { - return Response.status(Response.Status.EXPECTATION_FAILED).entity(MISSING_LRA_DATA).build(); - } + storeActivity(nestedLRAId, recoveryId); return Response.ok(nestedLRAId).build(); } @@ -368,16 +373,12 @@ public Response nestedActivity(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcv @Path("/multiLevelNestedActivity") @LRA(value = LRA.Type.MANDATORY, end = false) public Response multiLevelNestedActivity( - @HeaderParam(LRA_HTTP_RECOVERY_HEADER) String rcvId, + @HeaderParam(LRA_HTTP_RECOVERY_HEADER) String recoveryId, @HeaderParam(LRA_HTTP_HEADER) String nestedLRAId, @QueryParam("nestedCnt") @DefaultValue("1") Integer nestedCnt) { assertHeaderPresent(nestedLRAId); - Activity activity = addWork(nestedLRAId, rcvId); - - if (activity == null) { - return Response.status(Response.Status.EXPECTATION_FAILED).entity(MISSING_LRA_DATA).build(); - } + storeActivity(nestedLRAId, recoveryId); URL lraURL; @@ -395,28 +396,21 @@ public Response multiLevelNestedActivity( return Response.ok(String.join(",", lras)).build(); } - private Activity addWork(String lraId, String rcvId) { - System.out.printf("ActivityController: work id %s and rcvId %s %n", lraId, rcvId); + private Activity storeActivity(String lraId, String recoveryId) { + LOGGER.fine(String.format("Storing information about LRA id '%s' and recoveryId '%s'", lraId, recoveryId)); - try { - return activityService.getActivity(lraId); - } catch (NotFoundException e) { - Activity activity = new Activity(lraId); - - activity.setRcvUrl(rcvId); - activity.setStatus(null); - - activityService.add(activity); + Activity activity = new Activity(lraId) + .setRecoveryUrl(recoveryId) + .setStatus(null); - return activity; - } + return activityStore.add(activity); } @GET @Produces(MediaType.APPLICATION_JSON) @LRA(LRA.Type.NOT_SUPPORTED) public Response findAll() { - List results = activityService.findAll(); + List results = activityStore.findAll(); return Response.ok(results.size()).build(); } @@ -444,7 +438,7 @@ public Response getCompensatedCount() { public Response cancelOn(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - activityService.add(new Activity(lraId)); + activityStore.add(new Activity(lraId)); return Response.status(Response.Status.BAD_REQUEST).entity(Entity.text("Simulate buisiness logic failure")).build(); } @@ -456,7 +450,7 @@ public Response cancelOn(@HeaderParam(LRA_HTTP_HEADER) String lraId) { public Response cancelOnFamily(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - activityService.add(new Activity(lraId)); + activityStore.add(new Activity(lraId)); return Response.status(Response.Status.BAD_REQUEST).entity(Entity.text("Simulate buisiness logic failure")).build(); } @@ -468,12 +462,12 @@ public Response cancelOnFamily(@HeaderParam(LRA_HTTP_HEADER) String lraId) { public Response timeLimit(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - activityService.add(new Activity(lraId)); + activityStore.add(new Activity(lraId)); try { Thread.sleep(300); // sleep for longer than specified in the timeLimit annotation attribute } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.log(Level.FINE, "Interrupted becaused time limit elapsed", e); } return Response.status(Response.Status.OK).entity(Entity.text("Simulate buisiness logic timeoout")).build(); } @@ -485,11 +479,11 @@ public Response timeLimit(@HeaderParam(LRA_HTTP_HEADER) String lraId) { public Response extendTimeLimit(@HeaderParam(LRA_HTTP_HEADER) String lraId) { assertHeaderPresent(lraId); - activityService.add(new Activity(lraId)); + activityStore.add(new Activity(lraId)); try { /* - * the incomming LRA was created with a timeLimit of 100 ms via the timeLimit annotation + * the incoming LRA was created with a timeLimit of 100 ms via the timeLimit annotation * attribute update the timeLimit to 300 sleep for 200 return from the method so the LRA will * have been running for 200 ms so it should not be cancelled */ @@ -497,7 +491,7 @@ public Response extendTimeLimit(@HeaderParam(LRA_HTTP_HEADER) String lraId) { // sleep for 200000 micro seconds (should be longer than specified in the timeLimit annotation attribute) Thread.sleep(200); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.log(Level.FINE, "Interrupted because the renewed time limit elapsed", e); } return Response.status(Response.Status.OK).entity(Entity.text("Simulate buisiness logic timeoout")).build(); } @@ -513,18 +507,18 @@ public Response extendTimeLimit(@HeaderParam(LRA_HTTP_HEADER) String lraId) { * "URL"/cannot-compensate * "URL"/cannot-complete * - * @param txId the id of the LRA + * @param lraId the id of the LRA * @return the final status of the activity * @throws NotFoundException if the activity does not exist */ @PUT - @Path("/{TxId}/compensate") + @Path("/{LRAId}/compensate") @Produces(MediaType.APPLICATION_JSON) - public Response compensate(@PathParam("TxId")String txId) throws NotFoundException { - Activity activity = activityService.getActivity(txId); + public Response compensate(@PathParam("LRAId") String lraId) throws NotFoundException { + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); activity.setStatus(ParticipantStatus.Compensated); - activity.setStatusUrl(String.format("%s/%s/activity/compensated", context.getBaseUri(), txId)); + activity.setStatusUrl(String.format("%s/%s/activity/compensated", context.getBaseUri(), lraId)); return Response.ok(activity.getStatusUrl()).build(); } @@ -539,80 +533,65 @@ public Response compensate(@PathParam("TxId")String txId) throws NotFoundExcepti * "URL"/cannot-compensate * "URL"/cannot-complete * - * @param txId the id of the LRA + * @param lraId the id of the LRA * @return the final status of the activity * @throws NotFoundException if the activity does not exist */ @PUT - @Path("/{TxId}/complete") + @Path("/{LRAId}/complete") @Produces(MediaType.APPLICATION_JSON) - public Response complete(@PathParam("TxId")String txId) throws NotFoundException { - Activity activity = activityService.getActivity(txId); + public Response complete(@PathParam("LRAId") String lraId) throws NotFoundException { + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); activity.setStatus(ParticipantStatus.Completed); - activity.setStatusUrl(String.format("%s/%s/activity/completed", context.getBaseUri(), txId)); + activity.setStatusUrl(String.format("%s/%s/activity/completed", context.getBaseUri(), lraId)); return Response.ok(activity.getStatusUrl()).build(); } @PUT - @Path("/{TxId}/forget") - public void forget(@PathParam("TxId")String txId) throws NotFoundException { - Activity activity = activityService.getActivity(txId); + @Path("/{LRAId}/forget") + public void forget(@PathParam("LRAId")String lraId) throws NotFoundException { + Activity activity = activityStore.getActivityAndAssertExistence(lraId, context); - activityService.remove(activity.getId()); + activityStore.remove(activity.getLraId()); } @GET - @Path("/{TxId}/completed") + @Path("/{LRAId}/completed") @Produces(MediaType.APPLICATION_JSON) - public String completedStatus(@PathParam("TxId")String txId) { + public String completedStatus(@PathParam("LRAId") String lraId) { return ParticipantStatus.Completed.name(); } @GET - @Path("/{TxId}/compensated") + @Path("/{LRAId}/compensated") @Produces(MediaType.APPLICATION_JSON) - public String compensatedStatus(@PathParam("TxId")String txId) { + public String compensatedStatus(@PathParam("LRAId") String lraId) { return ParticipantStatus.Compensated.name(); } - private void checkStatusAndClose(Response response, int expected) { - try { - if (response.getStatus() != expected) { - throw new WebApplicationException(response); - } - } finally { - response.close(); - } - } - private static URL lraToURL(String lraId, String errorMessage) { try { return new URL(lraId); } catch (MalformedURLException e) { - LOGGER.log(Level.WARNING, "Can't construct URL from LRA id " + lraId, e); + LOGGER.log(Level.WARNING, "Can't construct URL from LRA id '" + lraId + "'", e); throw new GenericLRAException(null, Response.Status.BAD_REQUEST.getStatusCode(), - errorMessage + ": " + lraId, e); + errorMessage + ": LRA id '" + lraId + "'", e); } } private void assertHeaderPresent(String lraId) { - // assert (lraId != null) : context.getPath() + ": missing " + LRA_HTTP_HEADER + " header"; - if (lraId == null) { throw new InvalidLRAIdException(null, - String.format("%s: missing %s header", context.getPath(), LRA_HTTP_HEADER), null); + String.format("%s: missing '%s' header", context.getPath(), LRA_HTTP_HEADER)); } } - private void assertNotHeaderPresent(String lraId) { - // assert (lraId == null) : context.getPath() + ": unexpected " + LRA_HTTP_HEADER + " header"; - if (lraId != null) { throw new InvalidLRAIdException(null, - String.format("%s: unexpected %s header", context.getPath(), LRA_HTTP_HEADER), null); + String.format("%s: unexpected '%s' header", context.getPath(), LRA_HTTP_HEADER)); } } } diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/StandardController.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/NoLRAController.java similarity index 86% rename from tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/StandardController.java rename to tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/NoLRAController.java index f03a43f8..357b23d5 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/StandardController.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/NoLRAController.java @@ -33,19 +33,19 @@ import javax.ws.rs.core.UriInfo; import static org.eclipse.microprofile.lra.client.LRAClient.LRA_HTTP_HEADER; -import static org.eclipse.microprofile.lra.tck.participant.api.ActivityController.ACTIVITIES_PATH; +import static org.eclipse.microprofile.lra.tck.participant.api.LraController.LRA_CONTROLLER_PATH; @ApplicationScoped -@Path(StandardController.ACTIVITIES_PATH3) -public class StandardController { - public static final String ACTIVITIES_PATH3 = "activities3"; - public static final String NON_TRANSACTIONAL_WORK = "/work"; +@Path(NoLRAController.NO_LRA_CONTROLLER_PATH) +public class NoLRAController { + public static final String NO_LRA_CONTROLLER_PATH = "nolracontroller"; + public static final String NON_TRANSACTIONAL_WORK_PATH = "work"; @Context private UriInfo context; @PUT - @Path(NON_TRANSACTIONAL_WORK) + @Path(NON_TRANSACTIONAL_WORK_PATH) public Response work2(@HeaderParam(LRA_HTTP_HEADER) String lraId) throws NotFoundException { if (lraId != null) { @@ -53,8 +53,8 @@ public Response work2(@HeaderParam(LRA_HTTP_HEADER) String lraId) throws NotFoun } WebTarget resourcePath = ClientBuilder.newClient().target(context.getBaseUri()) - .path(ACTIVITIES_PATH) - .path(ActivityController.MANDATORY_LRA_RESOURCE_PATH); + .path(LRA_CONTROLLER_PATH) + .path(LraController.MANDATORY_LRA_RESOURCE_PATH); Response response = resourcePath.request().put(Entity.text("")); 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 91bc33ef..17206939 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 @@ -19,18 +19,6 @@ *******************************************************************************/ package org.eclipse.microprofile.lra.tck.participant.api; -import org.eclipse.microprofile.lra.annotation.Complete; -import org.eclipse.microprofile.lra.annotation.Compensate; -import org.eclipse.microprofile.lra.annotation.Leave; -import org.eclipse.microprofile.lra.annotation.Status; -import org.eclipse.microprofile.lra.annotation.Forget; -import org.eclipse.microprofile.lra.client.GenericLRAException; - -import javax.ws.rs.Path; -import javax.ws.rs.container.Suspended; -import javax.ws.rs.core.Link; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.URI; @@ -40,6 +28,19 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.ws.rs.Path; +import javax.ws.rs.container.Suspended; +import javax.ws.rs.core.Link; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.Complete; +import org.eclipse.microprofile.lra.annotation.Forget; +import org.eclipse.microprofile.lra.annotation.Leave; +import org.eclipse.microprofile.lra.annotation.Status; +import org.eclipse.microprofile.lra.client.GenericLRAException; + public class Util { private static final String COMPLETE = "complete"; @@ -72,14 +73,16 @@ public static Map getTerminationUris(Class compensatorClass, if (checkMethod(paths, COMPENSATE, (Path) pathAnnotation, method.getAnnotation(Compensate.class), uriPrefix) != 0) { - if (isAsyncCompletion(method)) + if (isAsyncCompletion(method)) { asyncTermination[0] = true; + } } if (checkMethod(paths, COMPLETE, (Path) pathAnnotation, method.getAnnotation(Complete.class), uriPrefix) != 0) { - if (isAsyncCompletion(method)) + if (isAsyncCompletion(method)) { asyncTermination[0] = true; + } } checkMethod(paths, STATUS, (Path) pathAnnotation, method.getAnnotation(Status.class), uriPrefix); @@ -109,14 +112,16 @@ public static Map getTerminationUris(Class compensatorClass, private static StringBuilder makeLink(StringBuilder b, String uriPrefix, String key, String value) { - if (value == null) + if (value == null) { return b; + } String terminationUri = uriPrefix == null ? value : String.format("%s%s", uriPrefix, value); Link link = Link.fromUri(terminationUri).title(key + " URI").rel(key).type(MediaType.TEXT_PLAIN).build(); - if (b.length() != 0) + if (b.length() != 0) { b.append(','); + } return b.append(link); } @@ -131,17 +136,41 @@ private static StringBuilder makeLink(StringBuilder b, String uriPrefix, String */ public static boolean isAsyncCompletion(Method method) { if (method.isAnnotationPresent(Complete.class) || method.isAnnotationPresent(Compensate.class)) { - for (Annotation[] ann : method.getParameterAnnotations()) - for (Annotation an : ann) + for (Annotation[] ann : method.getParameterAnnotations()) { + for (Annotation an : ann) { if (Suspended.class.getName().equals(an.annotationType().getName())) { LOGGER.log(Level.WARNING, "JAX-RS @Suspended annotation is untested"); return true; } + } + } } return false; } + /** + * Adjusting the value by factor. It means it multiplies the value with factor + * and returns the rounded (to up) number. + * + * @param value value to be adjusted + * @param factor factor for adjusting value + * @return adjusted value by factor + */ + public static int adjust(int value, double factor) { + return Math.toIntExact(adjust((long) value, factor)); + } + + /** + * see {@link Util#adjust(int, double)} + */ + public static long adjust(long value, double factor) { + if(value < 0){ + throw new IllegalArgumentException("value to adjust can't be negative"); + } + return (long) Math.ceil(value * factor); + } + private static int checkMethod(Map paths, String rel, Path pathAnnotation,