diff --git a/pom.xml b/pom.xml
index 745a1ff55f..c2f100bf13 100644
--- a/pom.xml
+++ b/pom.xml
@@ -497,6 +497,7 @@
1.1.21.0.101.0.0.Beta1
+ 1.31.5.0
diff --git a/rts/lra/client/src/main/java/io/narayana/lra/client/NarayanaLRAClient.java b/rts/lra/client/src/main/java/io/narayana/lra/client/NarayanaLRAClient.java
index 5564d054e5..342207360b 100644
--- a/rts/lra/client/src/main/java/io/narayana/lra/client/NarayanaLRAClient.java
+++ b/rts/lra/client/src/main/java/io/narayana/lra/client/NarayanaLRAClient.java
@@ -60,7 +60,6 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -744,9 +743,8 @@ private URI enlistCompensator(URI uri, Long timelimit, String linkHeader, String
String recoveryUrl = null;
try {
recoveryUrl = response.getHeaderString(LRA_HTTP_RECOVERY_HEADER);
- String url = URLDecoder.decode(recoveryUrl, StandardCharsets.UTF_8.name());
- return new URI(url);
- } catch (URISyntaxException | UnsupportedEncodingException e) {
+ return new URI(recoveryUrl);
+ } catch (URISyntaxException e) {
LRALogger.logger.infof(e,"join %s returned an invalid recovery URI '%s': %s", lraId, recoveryUrl, responseEntity);
throwGenericLRAException(null, Response.Status.SERVICE_UNAVAILABLE.getStatusCode(),
"join " + lraId + " returned an invalid recovery URI '" + recoveryUrl + "' : " + responseEntity, e);
diff --git a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/Coordinator.java b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/Coordinator.java
index dc088f57d0..d792621702 100644
--- a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/Coordinator.java
+++ b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/Coordinator.java
@@ -27,6 +27,7 @@
import io.narayana.lra.LRAData;
import io.narayana.lra.coordinator.domain.model.LongRunningAction;
import io.narayana.lra.coordinator.domain.service.LRAService;
+import io.narayana.lra.coordinator.internal.APIVersion;
import io.narayana.lra.logging.LRALogger;
import javax.enterprise.context.ApplicationScoped;
@@ -58,8 +59,8 @@
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
-import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -90,121 +91,143 @@
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
+import static javax.ws.rs.core.Response.Status.OK;
+import static io.narayana.lra.LRAConstants.LRA_API_VERSION_HEADER_NAME;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER;
@ApplicationScoped
@Path(COORDINATOR_PATH_NAME)
-@Tag(name = "LRA Coordinator")
+@Tag(name = "LRA Coordinator", description = "Operations to work with active LRAs (to start, to get a status, to finish etc.)")
public class Coordinator {
+ private static final APIVersion currentAPIVersion = APIVersion.instanceOf("1.0");
@Context
private UriInfo context;
@Inject // Will not work in an async scenario: CDI-452
- LRAService lraService;
+ private LRAService lraService;
- // Performing a GET on /lra-io.narayana.lra.coordinator returns a list of all LRAs.
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns all LRAs",
- description = "Gets both active and recovering LRAs")
- @APIResponse(description = "The LRA",
- content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = LRAData.class))
- )
- public Collection getAllLRAs(
+ @Operation(summary = "Returns all LRAs", description = "Gets both active and recovering LRAs")
+ @APIResponses({
+ @APIResponse(responseCode = "200", description = "The LRAData json array which is known to coordinator",
+ content = @Content(schema = @Schema(title = "LRAData array", type = SchemaType.ARRAY, implementation = LRAData.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)}),
+ @APIResponse(responseCode = "400", description = "",
+ content = @Content(schema = @Schema(title = "Error description", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)}),
+ })
+ public Response getAllLRAs(
@Parameter(name = STATUS_PARAM_NAME, description = "Filter the returned LRAs to only those in the give state (see CompensatorStatus)")
- @QueryParam(STATUS_PARAM_NAME) @DefaultValue("") String state) {
+ @QueryParam(STATUS_PARAM_NAME) @DefaultValue("") String state,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version) {
+ verifyVersion(version);
LRAStatus requestedLRAStatus = null;
if(!state.isEmpty()) {
- requestedLRAStatus = LRAStatus.valueOf(state);
+ try {
+ requestedLRAStatus = LRAStatus.valueOf(state);
+ } catch (NullPointerException | IllegalArgumentException e) {
+ String errorMsg = "Status " + state + " is not a valid LRAStatus value";
+ throw new WebApplicationException(errorMsg, e,
+ Response.status(BAD_REQUEST).header(LRA_API_VERSION_HEADER_NAME, version).entity(errorMsg).build());
+ }
}
- Collection lras = lraService.getAll(requestedLRAStatus);
+ List lras = lraService.getAll(requestedLRAStatus);
- if (lras == null) {
- LRALogger.i18NLogger.error_invalidQueryForGettingLraStatuses(state);
- String errMsg = String.format("Invalid query '%s' to get LRAs", state);
- throw new WebApplicationException(errMsg,
- Response.status(BAD_REQUEST).entity(errMsg).build());
- }
-
- return lras;
+ return Response.ok()
+ .entity(lras)
+ .header(LRA_API_VERSION_HEADER_NAME, version).build();
}
@GET
@Path("{LraId}/status")
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Obtain the status of an LRA as a string")
- @Schema(implementation = String.class)
@APIResponses({
- @APIResponse(responseCode = "404",
- description = "The coordinator has no knowledge of this LRA"),
- @APIResponse(responseCode = "204",
- description = "The LRA exists and has not yet been asked to close or cancel"),
- @APIResponse(responseCode = "200",
- description = "The LRA exists. The status is reported in the content body.")
+ @APIResponse(responseCode = "200", description = "The LRA exists. The status is reported in the content body.",
+ content = @Content(schema = @Schema(implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Unknown LRA error", implementation = String.class))),
})
public Response getLRAStatus(
- @Parameter(name = "LraId",
- description = "The unique identifier of the LRA", required = true)
- @PathParam("LraId")String lraId) throws NotFoundException {
- LongRunningAction transaction = lraService.getTransaction(toURI(lraId));
+ @Parameter(name = "LraId", description = "The unique identifier of the LRA." +
+ "Expecting to be a valid URL where the participant can be contacted at. If not in URL format it will be considered " +
+ "to be a id which will be declared to exist at URL where coordinator is deployed at.", required = true)
+ @PathParam("LraId")String lraId,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version)
+ throws NotFoundException {
+ verifyVersion(version);
+ LongRunningAction transaction = lraService.getTransaction(toURI(lraId)); // throws NotFoundException -> response 404
LRAStatus status = transaction.getLRAStatus();
if (status == null) {
status = LRAStatus.Active;
}
- return Response.ok(status.name()).build();
+ return Response.ok()
+ .entity(status.name())
+ .header(LRA_API_VERSION_HEADER_NAME, version).build();
}
@GET
@Path("{LraId}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Obtain the status of an LRA as a JSON structure")
+ @Operation(summary = "Obtain the information about an LRA as a JSON structure")
@APIResponses({
- @APIResponse(responseCode = "404",
- description = "The coordinator has no knowledge of this LRA",
- content = @Content(schema = @Schema(implementation = LRAData.class))),
- @APIResponse(responseCode = "204",
- description = "The LRA exists and has not yet been asked to close or cancel",
- content = @Content(schema = @Schema(implementation = LRAData.class))),
- @APIResponse(responseCode = "200",
- description = "The LRA exists. The status is reported in the content body.",
- content = @Content(schema = @Schema(implementation = LRAData.class)))
+ @APIResponse(responseCode = "200", description = "The LRA exists and the information is packed as JSON in the content body.",
+ content = @Content(schema = @Schema(title = "LRAData", implementation = LRAData.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Error description", implementation = String.class)))
})
- public LRAData getLRAInfo(
+ public Response getLRAInfo(
@Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
- @PathParam("LraId") String lraId) throws NotFoundException {
-
- return lraService.getLRA(toURI(lraId));
+ @PathParam("LraId") String lraId,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version) {
+ verifyVersion(version);
+ URI lraIdURI = toURI(lraId);
+ LRAData lraData = lraService.getLRA(lraIdURI);
+ return Response.status(OK).entity(lraData)
+ .header(LRA_API_VERSION_HEADER_NAME, version).build();
}
- // Performing a POST on /lra-io.narayana.lra.coordinator/start?ClientID= will start a new lra with a default timeout and
- // return a lra URL of the form /lra-io.narayana.lra.coordinator/.
- // Adding a query parameter, timeout=, will start a new lra with the specified timeout.
- // If the lra is terminated because of a timeout, the lra URL is deleted and all further invocations on the URL will return 404.
- // The invoker can assume this was equivalent to a compensate operation.
+ /**
+ * Performing a POST on {@value LRAConstants#COORDINATOR_PATH_NAME}/start?ClientID=
+ * will start a new lra with a default timeout and return a lra URL
+ * of the form /{@value LRAConstants#COORDINATOR_PATH_NAME}/.
+ * Adding a query parameter, {@value LRAConstants#TIMELIMIT_PARAM_NAME}=, will start a new lra with the specified timeout.
+ * If the lra is terminated because of a timeout, the lra URL is deleted and all further invocations on the URL will return 404.
+ * The invoker can assume this was equivalent to a compensate operation.
+ */
@POST
@Path("start")
@Produces(MediaType.TEXT_PLAIN)
@Bulkhead
@Operation(summary = "Start a new LRA",
- description = "The LRA model uses a presumed nothing protocol: the coordinator must communicate\n"
- + "with Compensators in order to inform them of the LRA activity. Every time a\n"
- + "Compensator is enrolled with a LRA, the coordinator must make information about\n"
- + "it durable so that the Compensator can be contacted when the LRA terminates,\n"
- + "even in the event of subsequent failures. Compensators, clients and coordinators\n"
- + "cannot make any presumption about the state of the global transaction without\n"
+ description = "The LRA model uses a presumed nothing protocol: the coordinator must communicate "
+ + "with Compensators in order to inform them of the LRA activity. Every time a "
+ + "Compensator is enrolled with a LRA, the coordinator must make information about "
+ + "it durable so that the Compensator can be contacted when the LRA terminates, "
+ + "even in the event of subsequent failures. Compensators, clients and coordinators "
+ + "cannot make any presumption about the state of the global transaction without "
+ "consulting the coordinator and all compensators, respectively.")
@APIResponses({
@APIResponse(responseCode = "201",
description = "The request was successful and the response body contains the id of the new LRA",
- content = @Content(schema = @Schema(title = "An LRA id", description = "An URI of the new LRA"))),
- @APIResponse(responseCode = "500",
- description = "A new LRA could not be started")
+ content = @Content(schema = @Schema(title = "An LRA id", description = "An URI of the new LRA", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "Parent LRA id cannot be joint to the started LRA",
+ content = @Content(schema = @Schema(title = "Failure description", description = "Message containing problematic LRA id", implementation = String.class))),
+ @APIResponse(responseCode = "500", description = "A new LRA could not be started. Coordinator internal error.",
+ content = @Content(schema = @Schema(title = "LRA cannot be started error", implementation = String.class)))
})
public Response startLRA(
@Parameter(name = CLIENT_ID_PARAM_NAME,
@@ -220,8 +243,9 @@ public Response startLRA(
@Parameter(name = PARENT_LRA_PARAM_NAME,
description = "The enclosing LRA if this new LRA is nested")
@QueryParam(PARENT_LRA_PARAM_NAME) @DefaultValue("") String parentLRA,
- @HeaderParam(LRA_HTTP_CONTEXT_HEADER) String parentId) throws WebApplicationException {
-
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version) throws WebApplicationException {
+ verifyVersion(version);
URI parentLRAUrl = null;
if (parentLRA != null && !parentLRA.isEmpty()) {
@@ -236,12 +260,13 @@ public Response startLRA(
String compensatorUrl = String.format("%s/nested/%s", coordinatorUrl, LRAConstants.getLRAUid(lraId));
if (lraService.hasTransaction(parentLRAUrl)) {
- Response response = joinLRAViaBody(parentLRAUrl.toASCIIString(), timelimit, null, compensatorUrl);
+ Response response = joinLRAViaBody(parentLRAUrl.toASCIIString(), timelimit, null, version, compensatorUrl);
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
- return Response.status(response.getStatus()).build();
+ return Response.status(response.getStatus()).header(LRA_API_VERSION_HEADER_NAME, version).build();
}
} else {
+ // TODO: investigate on reasons why starting the nested transaction goes to parent participant URL
Client client = null;
Response response = null;
@@ -254,13 +279,17 @@ public Response startLRA(
.get(PARTICIPANT_TIMEOUT, TimeUnit.SECONDS);
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
- String errMessage = String.format("The coordinator at %s returned an unexpected response: %d",
- parentLRAUrl, response.getStatus());
+ String errMessage = String.format("The coordinator at %s returned an unexpected response: %d"
+ + "when trying the LRA '%s' to join the parent LRA id '%s'", parentLRAUrl, response.getStatus(), lraId, parentLRA);
return Response.status(response.getStatus()).entity(errMessage).build();
}
} catch (Exception e) {
- LRALogger.logger.debugf("Cannot contact the LRA Coordinator at %s", parentLRA);
- throw new WebApplicationException(e);
+ String errorMsg = String.format("Cannot contact the LRA Coordinator at '%s' for LRA '%s' joining parent LRA '%s'",
+ parentLRAUrl, lraId, parentLRA);
+ LRALogger.logger.debugf(errorMsg);
+ throw new WebApplicationException(errorMsg, e,
+ Response.status(INTERNAL_SERVER_ERROR).header(LRA_API_VERSION_HEADER_NAME, version)
+ .entity(errorMsg).build());
} finally {
if (client != null) {
client.close();
@@ -274,27 +303,36 @@ public Response startLRA(
return Response.created(lraId)
.entity(lraId)
.header(LRA_HTTP_CONTEXT_HEADER, Current.getContexts())
+ .header(LRA_API_VERSION_HEADER_NAME, version)
.build();
}
@PUT
@Path("{LraId}/renew")
@Operation(summary = "Update the TimeLimit for an existing LRA",
- description = "LRAs can be automatically cancelled if they aren't closed or cancelled before the TimeLimit\n"
- + "specified at creation time is reached.\n"
- + "The time limit can be updated.\n")
+ description = "LRAs can be automatically cancelled if they aren't closed or cancelled before the TimeLimit "
+ + "specified at creation time is reached. The time limit can be updated.")
@APIResponses({
- @APIResponse(responseCode = "200", description = "If the LRA timelimit has been updated"),
- @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA"),
- @APIResponse(responseCode = "412",
- description = "The LRA is not longer active (ie the complete or compensate messages have been sent")
+ @APIResponse(responseCode = "200", description = "If the LRA time limit has been updated",
+ content = @Content(schema = @Schema(title = "Renewed LRA id", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA or " +
+ "the LRA is not longer active (ie the complete or compensate messages have been sent",
+ content = @Content(schema = @Schema(title = "Unknown LRA error", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
})
public Response renewTimeLimit(
+ @Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
+ @PathParam("LraId") String lraId,
@Parameter(name = TIMELIMIT_PARAM_NAME, description = "The new time limit for the LRA", required = true)
- @QueryParam(TIMELIMIT_PARAM_NAME) @DefaultValue("0") Long timelimit,
- @PathParam("LraId")String lraId) throws NotFoundException {
-
- return Response.status(lraService.renewTimeLimit(toURI(lraId), timelimit)).build();
+ @QueryParam(TIMELIMIT_PARAM_NAME) @DefaultValue("0") Long timeLimit,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version) {
+ verifyVersion(version);
+ return Response.status(lraService.renewTimeLimit(toURI(lraId), timeLimit))
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .entity(lraId)
+ .build();
}
@GET
@@ -352,11 +390,14 @@ public Response forgetNestedLRA(@PathParam("NestedLraId") String nestedLraId) {
return Response.ok().build();
}
- // Performing a PUT on lra-coordinator//close will trigger the successful completion of the lra and all
- // compensators will be dropped by the io.narayana.lra.coordinator.
- // The complete message will be sent to the compensators. Question: is this message best effort or at least once?
- // Upon termination, the URL is implicitly deleted. If it no longer exists, then 404 will be returned.
- // The invoker cannot know for sure whether the lra completed or compensated without enlisting a participant.
+ /**
+ * Performing a PUT on {@value LRAConstants#COORDINATOR_PATH_NAME}//close will trigger the successful completion
+ * of the LRA and all compensators will be dropped by the LRA Coordinator.
+ * The complete message will be sent to the compensators.
+ * Upon termination, the URL is implicitly deleted. If it no longer exists, then 404 will be returned.
+ * The invoker cannot know for sure whether the lra completed or compensated without enlisting a participant.
+ */
+ // TODO: Question: is this message best effort or at least once?
// TODO rework spec to allow an LRAStatus header everywhere
@PUT
@Path("{LraId}/close")
@@ -369,15 +410,22 @@ public Response forgetNestedLRA(@PathParam("NestedLraId") String nestedLraId) {
+ " The invoker cannot know for sure whether the lra completed"
+ " or compensated without enlisting a participant.")
@APIResponses({
- @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA"),
- @APIResponse(responseCode = "200", description = "The complete message was sent to all coordinators",
- content = @Content(
- schema = @Schema(title = "one of the LRAStatus enum values", implementation = String.class)))
+ @APIResponse(responseCode = "200", description = "The complete message was sent to all coordinators",
+ content = @Content(schema = @Schema(title = "LRAStatus enum value", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Unknown LRA error", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
})
public Response closeLRA(
@Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
- @PathParam("LraId")String txId) throws NotFoundException {
- return Response.ok(endLRA(toURI(txId), false, false).name()).build();
+ @PathParam("LraId") String txId,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version) {
+ verifyVersion(version);
+ return Response.ok(endLRA(toURI(txId), false, false).name())
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .build();
}
@PUT
@@ -389,15 +437,23 @@ public Response closeLRA(
+ " Upon termination, the URL is implicitly deleted."
+ " The invoker cannot know for sure whether the lra completed or compensated without enlisting a participant.")
@APIResponses({
- @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA"),
@APIResponse(responseCode = "200", description = "The compensate message was sent to all coordinators",
- content = @Content(
- schema = @Schema(title = "one of the LRAStatus enum values", implementation = String.class)))
+ content = @Content(schema = @Schema(title = "LRAStatus enum value", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Unknown LRA error", implementation = String.class)),
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
})
public Response cancelLRA(
- @Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
- @PathParam("LraId")String lraId) throws NotFoundException {
- return Response.ok(endLRA(toURI(lraId), true, false).name()).build();
+ @Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
+ @PathParam("LraId")String lraId,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version)
+ throws NotFoundException {
+ verifyVersion(version);
+ return Response.ok(endLRA(toURI(lraId), true, false).name())
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .build();
}
@@ -412,28 +468,33 @@ private LRAStatus endLRA(URI lraId, boolean compensate, boolean fromHierarchy) t
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "A Compensator can join with the LRA at any time prior to the completion of an activity")
@APIResponses({
- @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA"),
- @APIResponse(responseCode = "412",
- description = "The LRA is not longer active (ie in the complete or compensate messages have been sent"),
@APIResponse(responseCode = "200",
- description = "The participant was successfully registered with the LRA and"
- + " the response body contains a unique resource reference for that participant:\n"
- + " - HTTP GET on the reference returns the original participant URL;\n"
- + " - HTTP PUT on the reference will overwrite the old participant URL with the new one supplied.",
- headers = @Header(name = LRA_HTTP_RECOVERY_HEADER,
- description = "If the participant is successfully registered with the LRA then this header\n"
- + " will contain a unique resource reference for that participant:\n"
- + " - HTTP GET on the reference returns the original participant URL;\n"
- + " - HTTP PUT on the reference will overwrite the old participant URL with the new one supplied.",
- schema = @Schema(implementation = String.class)),
- content = @Content(schema = @Schema(title = "A new LRA recovery id", description = "An URI representing the " +
- "recovery id of this join request")))
+ description = "The participant was successfully registered with the LRA",
+ content = @Content(schema = @Schema(title = "A new LRA recovery id",
+ description = "An URI representing the recovery id of this join request",implementation = String.class)),
+ headers = {
+ @Header(name = LRA_HTTP_RECOVERY_HEADER, description = "It contains a unique resource reference for that participant:\n"
+ + " - HTTP GET on the reference returns the original participant URL;\n" // TODO: verify recovery coordinator works this way
+ + " - HTTP PUT on the reference will overwrite the old participant URL with the new one supplied.",
+ schema = @Schema(implementation = String.class)),
+ @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "400", description = "Link does not contain all required fields for joining the LRA. " +
+ "Probably no compensator or after 'rel' is available.",
+ content = @Content(schema = @Schema(title = "Error to enlist", implementation = String.class))),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Unknown LRA error", implementation = String.class))),
+ @APIResponse(responseCode = "412",
+ description = "The LRA is not longer active, or wrong format of compensator data",
+ content = @Content(schema = @Schema(title = "Wrong format LRA error", implementation = String.class)),
+ headers = {@Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)}),
+ @APIResponse(responseCode = "500", description = "Format of the compensator data (e.g. Link format) could not be processed",
+ content = @Content(schema = @Schema(title = "Internal failure", implementation = String.class))),
})
public Response joinLRAViaBody(
@Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
@PathParam("LraId")String lraId,
@Parameter(name = TIMELIMIT_PARAM_NAME,
- description = "The time limit (in seconds) that the Compensator can guarantee that it can compensate "
+ description = "The time limit in milliseconds that the Compensator can guarantee that it can compensate "
+ "the work performed by the service. After this time period has elapsed, it may no longer be "
+ "possible to undo the work within the scope of this (or any enclosing) LRA. It may therefore "
+ "be necessary for the application or service to start other activities to explicitly try to "
@@ -445,15 +506,17 @@ public Response joinLRAViaBody(
+ " the status of the participant. The link rel names are"
+ " complete, compensate and status.")
@HeaderParam("Link") @DefaultValue("") String compensatorLink,
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version,
@RequestBody(name = "Compensator data",
- description = "opaque data that will be stored with the coordinator and passed back to\n"
- + "the participant when the LRA is closed or cancelled.\n")
- String compensatorData) throws NotFoundException {
+ description = "opaque data that will be stored with the coordinator and passed back to "
+ + "the participant when the LRA is closed or cancelled.") String compensatorData) throws NotFoundException {
+ verifyVersion(version);
// test to see if the join request contains any participant specific data
boolean isLink = isLink(compensatorData);
if (compensatorLink != null && !compensatorLink.isEmpty()) {
- return joinLRA(toURI(lraId), timeLimit, null, compensatorLink, compensatorData);
+ return joinLRA(toURI(lraId), timeLimit, null, compensatorLink, compensatorData, version);
}
if (!isLink) { // interpret the content as a standard participant url
@@ -466,16 +529,19 @@ public Response joinLRAViaBody(
terminateURIs.put(COMPLETE, new URL(compensatorData + "complete").toExternalForm());
terminateURIs.put(STATUS, new URL(compensatorData + "status").toExternalForm());
} catch (MalformedURLException e) {
+ String errorMsg = String.format("Cannot join to LRA id '%s' with body as compensator url '%s' is invalid",
+ lraId, compensatorData);
if (LRALogger.logger.isTraceEnabled()) {
- LRALogger.logger.tracef(e, "Cannot join to LRA id '%s' with body as compensator url '%s' is invalid",
- lraId, compensatorData);
+ LRALogger.logger.trace(errorMsg, e);
}
- return Response.status(PRECONDITION_FAILED).build();
+ return Response.status(PRECONDITION_FAILED)
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .entity(errorMsg)
+ .build();
}
- // register with the coordinator
- // put the lra id in an http header
+ // register with the coordinator, put the lra id in an http header
StringBuilder linkHeaderValue = new StringBuilder();
terminateURIs.forEach((k, v) -> makeLink(linkHeaderValue, "", k, v)); // or use Collectors.joining(",")
@@ -483,7 +549,7 @@ public Response joinLRAViaBody(
compensatorData = linkHeaderValue.toString();
}
- return joinLRA(toURI(lraId), timeLimit, null, compensatorData, null);
+ return joinLRA(toURI(lraId), timeLimit, null, compensatorData, null, version);
}
@@ -513,7 +579,7 @@ private boolean isLink(String linkString) {
}
}
- private Response joinLRA(URI lraId, long timeLimit, String compensatorUrl, String linkHeader, String userData)
+ private Response joinLRA(URI lraId, long timeLimit, String compensatorUrl, String linkHeader, String userData, String version)
throws NotFoundException {
final String recoveryUrlBase = String.format("%s%s/%s",
context.getBaseUri().toASCIIString(), COORDINATOR_PATH_NAME, RECOVERY_COORDINATOR_PATH_NAME);
@@ -527,40 +593,65 @@ private Response joinLRA(URI lraId, long timeLimit, String compensatorUrl, Strin
.entity(recoveryUrl.toString())
.location(new URI(recoveryUrl.toString()))
.header(LRA_HTTP_RECOVERY_HEADER, recoveryUrl)
+ .header(LRA_API_VERSION_HEADER_NAME, version)
.build();
} catch (URISyntaxException e) {
LRALogger.i18NLogger.error_invalidRecoveryUrlToJoinLRAURI(recoveryUrl.toString(), lraId);
String errorMsg = lraId + ": Invalid recovery URL " + recoveryUrl.toString();
throw new WebApplicationException(errorMsg, e ,
- Response.status(INTERNAL_SERVER_ERROR).entity(errorMsg).build());
+ Response.status(INTERNAL_SERVER_ERROR).entity(errorMsg)
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .build());
}
}
- // A participant can resign from a lra at any time prior to the completion of an activity by performing a
- // PUT on lra-coordinator//remove with the URL of the participant.
+ /**
+ * A participant can resign from a lra at any time prior to the completion of an activity by performing a PUT
+ * PUT on {@value LRAConstants#COORDINATOR_PATH_NAME}//remove with the URL of the participant.
+ */
@PUT
@Path("{LraId}/remove")
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "A Compensator can resign from the LRA at any time prior to the completion of an activity")
@APIResponses({
- @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA"),
+ @APIResponse(responseCode = "200", description = "If the participant was successfully removed from the LRA",
+ headers = { @Header(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME) }),
+ @APIResponse(responseCode = "400", description = "The coordinator has no knowledge of this participant compensator URL",
+ content = @Content(schema = @Schema(title = "Unknown participant error", implementation = String.class))),
+ @APIResponse(responseCode = "404", description = "The coordinator has no knowledge of this LRA",
+ content = @Content(schema = @Schema(title = "Unknown LRA id error", implementation = String.class))),
@APIResponse(responseCode = "412",
description = "The LRA is not longer active (ie in the complete or compensate messages have been sent"),
- @APIResponse(responseCode = "200", description = "If the participant was successfully removed from the LRA")
})
public Response leaveLRA(
@Parameter(name = "LraId", description = "The unique identifier of the LRA", required = true)
@PathParam("LraId") String lraId,
- String compensatorUrl) throws NotFoundException, URISyntaxException {
- String reqUri = context.getRequestUri().toString();
-
- reqUri = reqUri.substring(0, reqUri.lastIndexOf('/'));
-
- int status = 0;
-
- status = lraService.leave(new URI(reqUri), compensatorUrl);
+ @Parameter(ref = LRAConstants.LRA_API_VERSION_HEADER_NAME)
+ @HeaderParam(LRAConstants.LRA_API_VERSION_HEADER_NAME) @DefaultValue(JaxRsActivator.LRA_API_VERSION_STRING) String version,
+ String participantCompensatorUrl) throws NotFoundException, URISyntaxException {
+ verifyVersion(version);
+ int status = lraService.leave(toURI(lraId), participantCompensatorUrl);
+
+ return Response.status(status)
+ .header(LRA_API_VERSION_HEADER_NAME, version)
+ .build();
+ }
- return Response.status(status).build();
+ private void verifyVersion(String versionString) {
+ APIVersion apiVersion = null;
+ try {
+ apiVersion = APIVersion.instanceOf(versionString);
+ if (apiVersion.compareTo(currentAPIVersion) > 0) {
+ String errorMsg = "Demanded API version " + versionString
+ + " is bigger than the supported one " + currentAPIVersion;
+ throw new WebApplicationException(errorMsg,
+ Response.status(PRECONDITION_FAILED).entity(errorMsg).build());
+ }
+ } catch (Exception iae) {
+ String errorMsg = "Wrong format of the provided version " + versionString + ": " + iae.getMessage();
+ throw new WebApplicationException(errorMsg, iae,
+ Response.status(PRECONDITION_FAILED).entity(errorMsg).build());
+ }
}
private URI toDecodedURI(String lraId) {
diff --git a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/JaxRsActivator.java b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/JaxRsActivator.java
index 2a0963a327..38e73cb7ed 100644
--- a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/JaxRsActivator.java
+++ b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/api/JaxRsActivator.java
@@ -1,8 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
package io.narayana.lra.coordinator.api;
+import io.narayana.lra.LRAConstants;
+import io.narayana.lra.coordinator.internal.APIVersion;
+import org.eclipse.microprofile.openapi.annotations.Components;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
+import org.eclipse.microprofile.openapi.annotations.enums.ParameterIn;
+import org.eclipse.microprofile.openapi.annotations.headers.Header;
+import org.eclipse.microprofile.openapi.annotations.info.Contact;
import org.eclipse.microprofile.openapi.annotations.info.Info;
-import org.eclipse.microprofile.openapi.annotations.tags.Tag;
+import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@@ -10,9 +38,24 @@
// mark the war as a JAX-RS archive
@ApplicationPath("/")
@OpenAPIDefinition(
- info = @Info(title = "LRA Coordinator", version = JaxRsActivator.LRA_API_VERSION),
- tags = @Tag(name = "LRA Coordinator")
+ info = @Info(title = "LRA Coordinator", version = JaxRsActivator.LRA_API_VERSION_STRING,
+ contact = @Contact(name = "Narayana", url = "https://narayana.io")),
+ components = @Components(
+ parameters = {
+ @Parameter(name = LRAConstants.LRA_API_VERSION_HEADER_NAME, in = ParameterIn.HEADER,
+ description = "API version string in format [major].[minor]-[prerelease]. Major and minor are required and to be numbers, prerelease part is optional.")
+ },
+ headers = {
+ @Header(name = LRAConstants.LRA_API_VERSION_HEADER_NAME, description = "Narayana LRA API version that processed the request")
+ }
+ )
)
public class JaxRsActivator extends Application {
- static final String LRA_API_VERSION = "1.0-RC1";
+ /**
+ * The LRA API version supported for the release.
+ * Any bigger version is considered as unimplemented and unknown.
+ * Any lower version is considered as older, implemented but deprecated and in case not supported.
+ */
+ public static final String LRA_API_VERSION_STRING = "1.0-RC1";
+ public static final APIVersion LRA_API_VERSION = APIVersion.instanceOf(LRA_API_VERSION_STRING);
}
diff --git a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/model/LRARecord.java b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/model/LRARecord.java
index 7d499d747c..fc1e18a0b8 100644
--- a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/model/LRARecord.java
+++ b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/model/LRARecord.java
@@ -112,11 +112,11 @@ public LRARecord() {
});
if (parseException[0] != null) {
- String errorMsg = lraId + ": Invalid link URI: " + parseException[0];
+ String errorMsg = lra.getId() + ": Invalid link URI: " + parseException[0];
throw new WebApplicationException(errorMsg, parseException[0],
Response.status(BAD_REQUEST).entity(errorMsg).build());
} else if (compensateURI == null && afterURI == null) {
- String errorMsg = lraId + ": Invalid link URI: missing compensator";
+ String errorMsg = lra.getId() + ": Invalid link URI: missing compensator or after LRA callback";
throw new WebApplicationException(errorMsg, Response.status(BAD_REQUEST).entity(errorMsg).build());
}
} else {
@@ -155,7 +155,7 @@ String getParticipantPath() {
static String cannonicalForm(String linkStr) throws URISyntaxException {
if (!linkStr.contains(">;")) {
- return cannonicalURI(new URI(linkStr)).toASCIIString();
+ return new URI(linkStr).toASCIIString();
}
SortedMap lm = new TreeMap<>();
diff --git a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/service/LRAService.java b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/service/LRAService.java
index f42cfba42c..a4afdddd78 100644
--- a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/service/LRAService.java
+++ b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/domain/service/LRAService.java
@@ -276,8 +276,9 @@ public LRAData endLRA(URI lraId, boolean compensate, boolean fromHierarchy) {
LongRunningAction transaction = getTransaction(lraId);
if (transaction.getLRAStatus() != LRAStatus.Active && !transaction.isRecovering() && transaction.isTopLevel()) {
- throw new WebApplicationException(Response.status(Response.Status.PRECONDITION_FAILED)
- .entity(String.format("%s: LRA is closing or closed: endLRA", lraId)).build());
+ String errorMsg = String.format("%s: LRA is closing or closed: endLRA", lraId);
+ throw new WebApplicationException(errorMsg, Response.status(Response.Status.PRECONDITION_FAILED)
+ .entity(errorMsg).build());
}
transaction.finishLRA(compensate);
@@ -305,14 +306,16 @@ public int leave(URI lraId, String compensatorUrl) {
try {
if (!transaction.forgetParticipant(compensatorUrl)) {
- if (LRALogger.logger.isInfoEnabled()) {
- LRALogger.logger.infof("LRAServicve.forget %s failed%n", lraId);
- }
+ String errorMsg = String.format("LRAService.forget %s failed on participant compensator url '%s'",
+ lraId, compensatorUrl);
+ throw new WebApplicationException(errorMsg, Response.status(Response.Status.BAD_REQUEST)
+ .entity(errorMsg).build());
}
-
return Response.Status.OK.getStatusCode();
} catch (Exception e) {
- return Response.Status.BAD_REQUEST.getStatusCode();
+ String errorMsg = String.format("LRAService.forget %s failed on finding participant '%s'", lraId, compensatorUrl);
+ throw new WebApplicationException(errorMsg, e, Response.status(Response.Status.BAD_REQUEST)
+ .entity(errorMsg).build());
}
}
@@ -408,7 +411,7 @@ public int renewTimeLimit(URI lraId, Long timelimit) {
LongRunningAction lra = lras.get(lraId);
if (lra == null) {
- return Response.Status.PRECONDITION_FAILED.getStatusCode();
+ return NOT_FOUND.getStatusCode();
}
return lra.setTimeLimit(timelimit);
diff --git a/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/internal/APIVersion.java b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/internal/APIVersion.java
new file mode 100644
index 0000000000..3031e6117d
--- /dev/null
+++ b/rts/lra/coordinator/src/main/java/io/narayana/lra/coordinator/internal/APIVersion.java
@@ -0,0 +1,125 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package io.narayana.lra.coordinator.internal;
+
+import io.narayana.lra.coordinator.api.JaxRsActivator;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * The LRA API version. The most probably provided in header {@code io.narayana.lra.LRAConstants#LRA_API_VERSION_HEADER_NAME}.
+ * The supported format is {@code #expectedFormat}.
+ *
+ *
+ * The major and minor parts are numbers, the preRelease part is an arbitrary string.
+ * Two instances of the {@link APIVersion} may be compared.
+ * There is taken into account only the major and minor parts,
+ * the preRelease is ignored.
+ * But two {@link APIVersion} instances are {@link Object#equals(Object)} only if all three parts are the same.
+ *
+ */
+public class APIVersion implements Comparable {
+ private static final String expectedFormat = "major.minor-preRelease";
+ private static final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(?:-(.+))?");
+
+ private final int major, minor;
+ private final String preRelease;
+
+ /**
+ * Parsing the version string and returns a {@link APIVersion} instance.
+ * The expected version format is {@code #expectedFormat}.
+ * If null is provided as String to parse then the most up-to-date
+ * LRA API version is taken from {@link JaxRsActivator#LRA_API_VERSION}.
+ *
+ * @param versionString version string to be parsed; when null or empty the most up-to-date
+ * {@link JaxRsActivator#LRA_API_VERSION} is returned
+ * @return instance of the {@link APIVersion} class based on the parsed String
+ * @throws IllegalArgumentException thrown when version string has a wrong format
+ */
+ public static APIVersion instanceOf(String versionString) {
+ Matcher versionMatcher = versionPattern.matcher(versionString);
+ if(versionString == null || versionString.isEmpty()) {
+ return JaxRsActivator.LRA_API_VERSION;
+ }
+ if(!versionMatcher.matches()) {
+ throw new IllegalArgumentException("Cannot parse provided version string " + versionString
+ + " as it does not match the expected format '" + expectedFormat + "'");
+ }
+ try {
+ int major = Integer.valueOf(versionMatcher.group(1));
+ int minor = Integer.valueOf(versionMatcher.group(2));
+ String preRelease = versionMatcher.group(3);
+ return new APIVersion(major, minor, preRelease);
+ } catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("The version string " + versionString + " matches the expected format " + expectedFormat
+ + " but the major.minor cannot be converted to numbers", nfe);
+ }
+ }
+
+ public APIVersion(int major, int minor, String preRelease) {
+ this.major = major;
+ this.minor = minor;
+ this.preRelease = preRelease;
+ }
+
+ @Override
+ public int compareTo(APIVersion anotherVersion) {
+ int result = Integer.compare(major, anotherVersion.major);
+ if (result == 0) {
+ result = Integer.compare(minor, anotherVersion.minor);
+ }
+ return result;
+ }
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder()
+ .append(major).append(".").append(minor);
+ if (preRelease != null) {
+ sb.append("-").append(preRelease);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ APIVersion that = (APIVersion) o;
+ return major == that.major &&
+ minor == that.minor &&
+ ((preRelease == null && that.preRelease == null)
+ || (preRelease != null && preRelease.equals(that.preRelease)));
+ }
+
+ @Override
+ public int hashCode() {
+ if (preRelease == null) {
+ return Objects.hash(major, minor);
+ } else {
+ return Objects.hash(major, minor, preRelease);
+ }
+ }
+}
diff --git a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery1TestCase.java b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery1TestCase.java
index 785f04d610..09514c15fa 100644
--- a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery1TestCase.java
+++ b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery1TestCase.java
@@ -21,15 +21,7 @@
*/
package io.narayana.lra.coordinator;
-import com.arjuna.ats.arjuna.recovery.RecoveryModule;
-import io.narayana.lra.Current;
-import io.narayana.lra.client.NarayanaLRAClient;
import io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry;
-import io.narayana.lra.coordinator.api.Coordinator;
-import io.narayana.lra.LRAData;
-import io.narayana.lra.coordinator.domain.model.LongRunningAction;
-import io.narayana.lra.coordinator.domain.service.LRAService;
-import io.narayana.lra.coordinator.internal.LRARecoveryModule;
import io.narayana.lra.filter.ServerLRAFilter;
import io.narayana.lra.logging.LRALogger;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
@@ -72,20 +64,8 @@
@RunWith(Arquillian.class)
@RunAsClient
public class LRACoordinatorRecovery1TestCase extends TestBase {
- private static final Package[] coordinatorPackages = {
- RecoveryModule.class.getPackage(),
- Coordinator.class.getPackage(),
- LRAData.class.getPackage(),
- LRAStatus.class.getPackage(),
- LRALogger.class.getPackage(),
- NarayanaLRAClient.class.getPackage(),
- Current.class.getPackage(),
- LRAService.class.getPackage(),
- LRARecoveryModule.class.getPackage(),
- LongRunningAction.class.getPackage()
- };
- private static final Package[] participantPackages = {
+ static final Package[] participantPackages = {
LRAListener.class.getPackage(),
LRA.class.getPackage(),
ServerLRAFilter.class.getPackage(),
diff --git a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery2TestCase.java b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery2TestCase.java
index 9e3e596e0f..8ae5f179b4 100644
--- a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery2TestCase.java
+++ b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/LRACoordinatorRecovery2TestCase.java
@@ -21,15 +21,7 @@
*/
package io.narayana.lra.coordinator;
-import com.arjuna.ats.arjuna.recovery.RecoveryModule;
-import io.narayana.lra.Current;
-import io.narayana.lra.client.NarayanaLRAClient;
import io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipantRegistry;
-import io.narayana.lra.coordinator.api.Coordinator;
-import io.narayana.lra.LRAData;
-import io.narayana.lra.coordinator.domain.model.LongRunningAction;
-import io.narayana.lra.coordinator.domain.service.LRAService;
-import io.narayana.lra.coordinator.internal.LRARecoveryModule;
import io.narayana.lra.filter.ServerLRAFilter;
import io.narayana.lra.logging.LRALogger;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
@@ -76,19 +68,6 @@ public class LRACoordinatorRecovery2TestCase extends TestBase {
private static final Long LONG_TIMEOUT = TimeoutValueAdjuster.adjustTimeout(600000L); // 10 minutes
private static final Long SHORT_TIMEOUT = 10000L; // 10 seconds
- private static final Package[] coordinatorPackages = {
- RecoveryModule.class.getPackage(),
- Coordinator.class.getPackage(),
- LRAData.class.getPackage(),
- LRAStatus.class.getPackage(),
- LRALogger.class.getPackage(),
- NarayanaLRAClient.class.getPackage(),
- Current.class.getPackage(),
- LRAService.class.getPackage(),
- LRARecoveryModule.class.getPackage(),
- LongRunningAction.class.getPackage()
- };
-
private static final Package[] participantPackages = {
LRAListener.class.getPackage(),
LRA.class.getPackage(),
diff --git a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/TestBase.java b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/TestBase.java
index f67e0a65c8..d493255a09 100644
--- a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/TestBase.java
+++ b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/TestBase.java
@@ -21,8 +21,14 @@
*/
package io.narayana.lra.coordinator;
+import com.arjuna.ats.arjuna.recovery.RecoveryModule;
+import io.narayana.lra.Current;
+import io.narayana.lra.LRAData;
import io.narayana.lra.client.NarayanaLRAClient;
+import io.narayana.lra.coordinator.api.Coordinator;
import io.narayana.lra.coordinator.domain.model.LongRunningAction;
+import io.narayana.lra.coordinator.domain.service.LRAService;
+import io.narayana.lra.coordinator.internal.LRARecoveryModule;
import io.narayana.lra.logging.LRALogger;
import org.eclipse.microprofile.lra.annotation.LRAStatus;
import org.jboss.arquillian.container.test.api.Config;
@@ -57,9 +63,8 @@ public abstract class TestBase {
@Rule
public TestName testName = new TestName();
- private static final String COORDINATOR_CONTAINER = "lra-coordinator";
-
- static final String COORDINATOR_DEPLOYMENT = COORDINATOR_CONTAINER;
+ public static final String COORDINATOR_CONTAINER = "lra-coordinator";
+ public static final String COORDINATOR_DEPLOYMENT = COORDINATOR_CONTAINER;
private static Path storeDir;
@@ -76,6 +81,19 @@ public static void beforeClass() {
storeDir = Paths.get(String.format("%s/standalone/data/tx-object-store", System.getProperty("env.JBOSS_HOME", "null")));
}
+ public static final Package[] coordinatorPackages = {
+ RecoveryModule.class.getPackage(),
+ Coordinator.class.getPackage(),
+ LRAData.class.getPackage(),
+ LRAStatus.class.getPackage(),
+ LRALogger.class.getPackage(),
+ NarayanaLRAClient.class.getPackage(),
+ Current.class.getPackage(),
+ LRAService.class.getPackage(),
+ LRARecoveryModule.class.getPackage(),
+ LongRunningAction.class.getPackage()
+ };
+
@Before
public void before() throws URISyntaxException, MalformedURLException {
LRALogger.logger.debugf("Starting test %s", testName);
@@ -88,7 +106,7 @@ public void after() {
lraClient.close();
}
- void startContainer(String bytemanScript) {
+ public void startContainer(String bytemanScript) {
Config config = new Config();
String javaVmArguments = System.getProperty("server.jvm.args");
@@ -106,7 +124,7 @@ void startContainer(String bytemanScript) {
deployer.deploy(COORDINATOR_DEPLOYMENT);
}
- void restartContainer() {
+ public void restartContainer() {
try {
// ensure that the controller is not running
containerController.kill(COORDINATOR_CONTAINER);
@@ -121,7 +139,7 @@ void restartContainer() {
containerController.start(COORDINATOR_CONTAINER);
}
- void stopContainer() {
+ public void stopContainer() {
if (containerController.isStarted(COORDINATOR_CONTAINER)) {
LRALogger.logger.debug("Stopping container");
diff --git a/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/internal/APIVersionTest.java b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/internal/APIVersionTest.java
new file mode 100644
index 0000000000..8c1968edf4
--- /dev/null
+++ b/rts/lra/coordinator/src/test/java/io/narayana/lra/coordinator/internal/APIVersionTest.java
@@ -0,0 +1,89 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2020, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package io.narayana.lra.coordinator.internal;
+
+import io.narayana.lra.coordinator.api.JaxRsActivator;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import org.hamcrest.MatcherAssert;
+import static org.hamcrest.Matchers.comparesEqualTo;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+
+/**
+ * Unit test for version class.
+ */
+public class APIVersionTest {
+ private static final APIVersion testVersion = APIVersion.instanceOf("1.1-Alpha");
+
+ @Test
+ public void upToDateVersionIsBiggerOrEqual() {
+ APIVersion oneZeroVersion = APIVersion.instanceOf("1.0");
+ MatcherAssert.assertThat(JaxRsActivator.LRA_API_VERSION, greaterThanOrEqualTo(oneZeroVersion));
+ }
+
+ // Object.equal does not match version with pre-release part
+ @Test
+ public void preReleaseIsNotEqualToFinal() {
+ APIVersion version = APIVersion.instanceOf("1.1");
+ MatcherAssert.assertThat(version, CoreMatchers.not(testVersion));
+ }
+
+ // Comparable.compareTo matches version with and without pre-release part
+ @Test
+ public void preReleaseIsCompareEqualToFinal() {
+ APIVersion version = APIVersion.instanceOf("1.1");
+ MatcherAssert.assertThat(version, comparesEqualTo(testVersion));
+ }
+
+ @Test
+ public void lowerMajorVersion() {
+ APIVersion version = APIVersion.instanceOf("0.1");
+ MatcherAssert.assertThat(version, lessThan(testVersion));
+ }
+
+ @Test
+ public void biggerMinorVersion() {
+ APIVersion version = APIVersion.instanceOf("2.0");
+ MatcherAssert.assertThat(version, greaterThan(testVersion));
+ }
+
+ @Test
+ public void lowerMinorVersion() {
+ APIVersion version = APIVersion.instanceOf("1.0");
+ MatcherAssert.assertThat(version, lessThan(testVersion));
+ }
+
+ @Test
+ public void biggerMajorVersion() {
+ APIVersion version = APIVersion.instanceOf("1.2");
+ MatcherAssert.assertThat(version, greaterThan(testVersion));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void incorrectVersion() {
+ APIVersion.instanceOf("1,3");
+ }
+}
\ No newline at end of file
diff --git a/rts/lra/coordinator/src/test/resources/arquillian.xml b/rts/lra/coordinator/src/test/resources/arquillian.xml
index 7ca0fe8967..22bc106baf 100644
--- a/rts/lra/coordinator/src/test/resources/arquillian.xml
+++ b/rts/lra/coordinator/src/test/resources/arquillian.xml
@@ -28,6 +28,7 @@
${lra.coordinator.host}${server.startup.timeout:120}
+ true
diff --git a/rts/lra/lra-service-base/src/main/java/io/narayana/lra/LRAConstants.java b/rts/lra/lra-service-base/src/main/java/io/narayana/lra/LRAConstants.java
new file mode 100644
index 0000000000..13f652a20e
--- /dev/null
+++ b/rts/lra/lra-service-base/src/main/java/io/narayana/lra/LRAConstants.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2019, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package io.narayana.lra;
+
+public abstract class LRAConstants {
+ public static final String COORDINATOR_PATH_NAME = "lra-coordinator";
+ public static final String RECOVERY_COORDINATOR_PATH_NAME = "lra-recovery-coordinator";
+ public static final String RECOVERY_COORDINATOR_SUB_RESOURCE_NAME = "recovery";
+
+ public static final String COMPLETE = "complete";
+ public static final String COMPENSATE = "compensate";
+ public static final String STATUS = "status";
+ public static final String LEAVE = "leave";
+ public static final String AFTER = "after";
+ public static final String FORGET = "forget";
+
+ public static final String STATUS_PARAM_NAME = "Status";
+ public static final String CLIENT_ID_PARAM_NAME = "ClientID";
+ public static final String TIMELIMIT_PARAM_NAME = "TimeLimit";
+ public static final String PARENT_LRA_PARAM_NAME = "ParentLRA";
+ public static final String RECOVERY_PARAM = "recoveryCount";
+ public static final String HTTP_METHOD_NAME = "method"; // the name of the HTTP method used to invoke participants
+
+ public static final String LRA_API_VERSION_HEADER_NAME = "Narayana-LRA-API-version";
+}
diff --git a/rts/lra/pom.xml b/rts/lra/pom.xml
index dcf33a5413..9e24ebe0de 100644
--- a/rts/lra/pom.xml
+++ b/rts/lra/pom.xml
@@ -1,8 +1,10 @@
-
+ -->
+4.0.0org.jboss.narayana.rts
@@ -32,6 +34,7 @@
3.1.1.Final1.2.6
+ 2.2UTF-8
@@ -80,6 +83,18 @@
+
+ org.hamcrest
+ hamcrest
+ ${version.hamcrest}
+ test
+
+
+ org.hamcrest
+ hamcrest-library
+ ${version.hamcrest}
+ test
+ junitjunit
diff --git a/rts/lra/service-base/src/main/java/io/narayana/lra/LRAConstants.java b/rts/lra/service-base/src/main/java/io/narayana/lra/LRAConstants.java
index c92de639c0..bc37bb1499 100644
--- a/rts/lra/service-base/src/main/java/io/narayana/lra/LRAConstants.java
+++ b/rts/lra/service-base/src/main/java/io/narayana/lra/LRAConstants.java
@@ -45,6 +45,8 @@ public final class LRAConstants {
public static final String RECOVERY_PARAM = "recoveryCount";
public static final String HTTP_METHOD_NAME = "method"; // the name of the HTTP method used to invoke participants
+ public static final String LRA_API_VERSION_HEADER_NAME = "Narayana-LRA-API-version";
+
/**
* Number of seconds to wait for requests to participant.
* The timeout is hardcoded as the protocol expects retry in case of failure and timeout.
diff --git a/rts/lra/service-base/src/main/java/io/narayana/lra/LRAData.java b/rts/lra/service-base/src/main/java/io/narayana/lra/LRAData.java
index 7a0f28cc3e..f54d373d47 100644
--- a/rts/lra/service-base/src/main/java/io/narayana/lra/LRAData.java
+++ b/rts/lra/service-base/src/main/java/io/narayana/lra/LRAData.java
@@ -60,8 +60,9 @@ public URI getLraId() {
return this.lraId;
}
- public void setLraId(URI lraId) {
+ public LRAData setLraId(URI lraId) {
this.lraId = lraId;
+ return this;
}
@Transient
@@ -73,56 +74,63 @@ public String getClientId() {
return this.clientId;
}
- public void setClientId(String clientId) {
+ public LRAData setClientId(String clientId) {
this.clientId = clientId;
+ return this;
}
public LRAStatus getStatus() {
return this.status;
}
- public void setStatus(LRAStatus status) {
+ public LRAData setStatus(LRAStatus status) {
this.status = status;
+ return this;
}
public boolean isTopLevel() {
return this.isTopLevel;
}
- public void setTopLevel(boolean topLevel) {
+ public LRAData setTopLevel(boolean topLevel) {
isTopLevel = topLevel;
+ return this;
}
public boolean isRecovering() {
return this.isRecovering;
}
- public void setRecovering(boolean recovering) {
+ public LRAData setRecovering(boolean recovering) {
isRecovering = recovering;
+ return this;
}
public long getStartTime() {
return startTime;
}
- public void setStartTime(long startTime) {
+ public LRAData setStartTime(long startTime) {
this.startTime = startTime;
+ return this;
}
public long getFinishTime() {
return finishTime;
}
- public void setFinishTime(long finishTime) {
+ public LRAData setFinishTime(long finishTime) {
this.finishTime = finishTime;
+ return this;
}
public int getHttpStatus() {
return this.httpStatus;
}
- public void setHttpStatus(int httpStatus) {
+ public LRAData setHttpStatus(int httpStatus) {
this.httpStatus = httpStatus;
+ return this;
}
public boolean equals(Object o) {
diff --git a/rts/lra/test/basic/pom.xml b/rts/lra/test/basic/pom.xml
index 0b8c93c9b5..9c65d7971a 100644
--- a/rts/lra/test/basic/pom.xml
+++ b/rts/lra/test/basic/pom.xml
@@ -51,6 +51,21 @@
${version.org.codehaus.jettison}test
+
+
+ org.jboss.resteasy
+ resteasy-client
+ ${version.org.jboss.resteasy}
+ test
+
+
+
+ org.jboss.resteasy
+ resteasy-json-binding-provider
+ ${version.org.jboss.resteasy}
+ test
+
+
diff --git a/rts/lra/test/basic/src/test/java/io/narayana/lra/arquillian/api/CoordinatorApi_1_0_IT.java b/rts/lra/test/basic/src/test/java/io/narayana/lra/arquillian/api/CoordinatorApi_1_0_IT.java
new file mode 100644
index 0000000000..9323173465
--- /dev/null
+++ b/rts/lra/test/basic/src/test/java/io/narayana/lra/arquillian/api/CoordinatorApi_1_0_IT.java
@@ -0,0 +1,830 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package io.narayana.lra.arquillian.api;
+
+import io.narayana.lra.LRAData;
+import io.narayana.lra.arquillian.Deployer;
+import io.narayana.lra.client.NarayanaLRAClient;
+import io.narayana.lra.coordinator.api.JaxRsActivator;
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.hamcrest.MatcherAssert;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Link;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.core.IsCollectionContaining.hasItem;
+import static org.hamcrest.core.IsCollectionContaining.hasItems;
+import static org.hamcrest.core.IsNot.not;
+
+/**
+ * REST API tests against the LRA coordinator in version for LRA 1.0.
+ */
+@RunWith(Arquillian.class)
+@RunAsClient
+public class CoordinatorApi_1_0_IT {
+ static final String API_VERSION_1_0 = "1.0";
+
+ private Client client;
+ private NarayanaLRAClient lraClient;
+ private String coordinatorUrl;
+ private List lrasToAfterFinish;
+
+ static final String NOT_SUPPORTED_FUTURE_LRA_VERSION = "42.1";
+
+ static final String LRA_API_VERSION_HEADER_NAME_V1_0 = "Narayana-LRA-API-version";
+ static final String RECOVERY_HEADER_NAME_V1_0 = "Long-Running-Action-Recovery";
+ static final String STATUS_PARAM_NAME_V1_0 = "Status";
+ static final String CLIENT_ID_PARAM_NAME_V1_0 = "ClientID";
+ static final String TIME_LIMIT_PARAM_NAME_V1_0 = "TimeLimit";
+ static final String PARENT_LRA_PARAM_NAME_V1_0 = "ParentLRA";
+
+ @Rule
+ public TestName testName = new TestName();
+
+ @Deployment
+ public static WebArchive deploy() {
+ return Deployer.deploy(CoordinatorApi_1_0_IT.class.getSimpleName());
+ }
+
+ @Before
+ public void before() {
+ client = ClientBuilder.newClient();
+ lraClient = new NarayanaLRAClient();
+ coordinatorUrl = lraClient.getCoordinatorUrl();
+ lrasToAfterFinish = new ArrayList<>();
+ }
+
+ @After
+ public void after() {
+ for (URI lraToFinish: lrasToAfterFinish) {
+ lraClient.cancelLRA(lraToFinish);
+ }
+ if (client != null) {
+ client.close();
+ }
+ }
+
+ @Test
+ public void getAllLRAs() {
+ Long timeBefore = System.currentTimeMillis();
+
+ String clientId1 = testName.getMethodName() + "_OK_1";
+ String clientId2 = testName.getMethodName() + "_OK_2";
+ URI lraId1 = lraClient.startLRA(clientId1);
+ URI lraId2 = lraClient.startLRA(lraId1, clientId2, 0L, null);
+ lrasToAfterFinish.add(lraId1); // lraId2 is nested and will be closed in regards to lraId1
+
+ List data;
+ try (Response response = client.target(coordinatorUrl)
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0).get()) {
+ Assert.assertEquals("Expected that the call succeeds, GET/200.", Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Provided API header, expected that one is returned",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ data = response.readEntity(new GenericType>() {});
+ }
+
+ Collection returnedLraIds = data.stream().map(LRAData::getLraId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator returns the first started LRA",
+ returnedLraIds, hasItem(lraId1));
+ MatcherAssert.assertThat("Expected the coordinator returns the second started LRA",
+ returnedLraIds, hasItem(lraId2));
+ Collection returnedClientIds = data.stream().map(LRAData::getClientId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator returns the first started LRA client id",
+ returnedClientIds, hasItem(clientId1));
+ MatcherAssert.assertThat("Expected the coordinator returns the second started LRA client id",
+ returnedClientIds, hasItem(clientId2));
+
+ Optional lraTopOptional = data.stream().filter(LRAData::isTopLevel).findFirst();
+ Assert.assertTrue("Expected to find one LRA from '" + data + "' to be top level", lraTopOptional.isPresent());
+ LRAData lraTop = lraTopOptional.get();
+ Optional lraNestedOptional = data.stream().filter(lraData -> !lraData.isTopLevel()).findFirst();
+ Assert.assertTrue("Expected to find one LRA from '" + data + "' to be nested", lraNestedOptional.isPresent());
+ LRAData lraNested = lraNestedOptional.get();
+
+ MatcherAssert.assertThat("Expected the start time of LRA '" + lraTop + "' is after the test start time",
+ timeBefore, lessThan(lraTop.getStartTime())); // expecting no time shift
+ Assert.assertEquals("Expected LRA '" + lraTop + "' being active",
+ LRAStatus.Active, lraTop.getStatus());
+ Assert.assertEquals("Expected top-level LRA '" + lraTop + "' being active, HTTP status 204.",
+ Status.NO_CONTENT.getStatusCode(), lraNested.getHttpStatus());
+ Assert.assertFalse("Expected LRA '" + lraTop + "' not being recovering", lraTop.isRecovering());
+ Assert.assertTrue("Expected LRA '" + lraTop + "' to be top level", lraTop.isTopLevel());
+
+ MatcherAssert.assertThat("Expected the start time of LRA '" + lraNested + "' is after the test start time",
+ timeBefore, lessThan(lraNested.getStartTime())); // expecting no time shift
+ Assert.assertEquals("Expected LRA '" + lraNested + "' being active",
+ LRAStatus.Active, lraNested.getStatus());
+ Assert.assertEquals("Expected nested LRA '" + lraNested + "' being active, HTTP status 204.",
+ Status.NO_CONTENT.getStatusCode(), lraNested.getHttpStatus());
+ Assert.assertFalse("Expected LRA '" + lraNested + "' not being recovering", lraNested.isRecovering());
+ Assert.assertFalse("Expected LRA '" + lraNested + "' to be nested", lraNested.isTopLevel());
+ }
+
+ @Test
+ public void getAllLRAsStatusFilter() {
+ String clientId1 = testName.getMethodName() + "_1";
+ String clientId2 = testName.getMethodName() + "_2";
+ URI lraId1 = lraClient.startLRA(clientId1);
+ URI lraId2 = lraClient.startLRA(lraId1, clientId2, 0L, null);
+ lrasToAfterFinish.add(lraId1);
+ lraClient.closeLRA(lraId2);
+
+ try (Response response = client.target(coordinatorUrl).request().get()) {
+ Assert.assertEquals("Expected that the call succeeds, GET/200.", Status.OK.getStatusCode(), response.getStatus());
+ List data = response.readEntity(new GenericType>() {});
+ Collection returnedLraIds = data.stream().map(LRAData::getLraId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator returns the first started and second closed LRA",
+ returnedLraIds, hasItems(lraId1, lraId2));
+ }
+ try (Response response = client.target(coordinatorUrl)
+ .queryParam(STATUS_PARAM_NAME_V1_0, "Active").request().get()) {
+ Assert.assertEquals("Expected that the call succeeds, GET/200.", Status.OK.getStatusCode(), response.getStatus());
+ List data = response.readEntity(new GenericType>() {});
+ Collection returnedLraIds = data.stream().map(LRAData::getLraId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator returns the first started LRA",
+ returnedLraIds, hasItem(lraId1));
+ MatcherAssert.assertThat("Expected the coordinator filtered out the non-active nested LRA",
+ returnedLraIds, not(hasItem(lraId2)));
+ }
+ }
+
+ @Test
+ public void getAllLRAsFailedStatus() {
+ String nonExistingStatusValue = "NotExistingStatusValue";
+ try (Response response = client.target(coordinatorUrl)
+ .queryParam(STATUS_PARAM_NAME_V1_0, nonExistingStatusValue).request().get()) {
+ Assert.assertEquals("Expected that the call fails on wrong status, GET/500.",
+ Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure to contain the wrong status value",
+ response.readEntity(String.class), containsString(nonExistingStatusValue));
+ }
+ }
+
+ @Test
+ public void getLRAStatus() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl).path(encodedLraId).path("status")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0).get()) {
+ Assert.assertEquals("Expected that the get status call succeeds, GET/200.", Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expected API header, the latest one version to be returned",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ Assert.assertEquals("Expected the returned LRA status is Active",
+ "Active", response.readEntity(String.class));
+ }
+ }
+
+ @Test
+ public void getLRAStatusFailed() {
+ String nonExistingLRAUrl = "http://localhost:1234/Non-Existing-LRA-id";
+ try (Response response = client.target(coordinatorUrl).path(nonExistingLRAUrl).path("status").request().get()) {
+ Assert.assertEquals("Expected that the call finds not found of " + nonExistingLRAUrl + ", GET/404.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the wrong LRA id",
+ response.readEntity(String.class), containsString(nonExistingLRAUrl));
+ }
+
+ String nonExistingLRAWrongUrlFormat = "Non-Existing-LRA-id";
+ try (Response response = client.target(coordinatorUrl).path(nonExistingLRAWrongUrlFormat).path("status").request().get()) {
+ Assert.assertEquals("Expected that the call fails on LRA not found of " + nonExistingLRAWrongUrlFormat + " , GET/404.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the wrong LRA id",
+ response.readEntity(String.class), containsString(lraClient.getCoordinatorUrl() + "/" + nonExistingLRAWrongUrlFormat));
+ }
+ }
+
+ @Test
+ public void getLRAInfo() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl).path(encodedLraId)
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0).get()) {
+ Assert.assertEquals("Expected that the get status call succeeds, GET/200.", Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expected API header, the latest one version to be returned",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ LRAData data = response.readEntity(new GenericType() {});
+ Assert.assertEquals("Expected returned LRA is started one", lraId, data.getLraId());
+ Assert.assertEquals("Expected the returned LRA being Active", LRAStatus.Active, data.getStatus());
+ Assert.assertTrue("Expected the returned LRA is top-level", data.isTopLevel());
+ Assert.assertEquals("Expected the returned LRA get HTTP status as active, HTTP status 204.",
+ Status.NO_CONTENT.getStatusCode(), data.getHttpStatus());
+ }
+ }
+
+ @Test
+ public void getLRAInfoNotExisting() {
+ String nonExistingLRA = "Non-Existing-LRA-id";
+ try (Response response = client.target(coordinatorUrl).path(nonExistingLRA).request().get()) {
+ Assert.assertEquals("Expected that the call fails on LRA not found, GET/404.", Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the wrong LRA id",
+ response.readEntity(String.class), containsString(nonExistingLRA));
+ }
+ }
+
+ @Test // TODO: delete me
+ public void startCloseLRA() throws UnsupportedEncodingException {
+ URI lraId1, lraId2;
+
+ try (Response response = client.target(coordinatorUrl)
+ .path("start")
+ .queryParam(CLIENT_ID_PARAM_NAME_V1_0, testName.getMethodName() + "_1")
+ .queryParam(TIME_LIMIT_PARAM_NAME_V1_0, "-42") // negative time limit is permitted by spec
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .post(null)) {
+ Assert.assertEquals("Creating top-level LRA should be successful, POST/201 is expected.",
+ Status.CREATED.getStatusCode(), response.getStatus());
+ lraId1 = response.readEntity(URI.class);
+ Assert.assertNotNull("Expected non null LRA id from entity of response '" + response + "'", lraId1);
+ lrasToAfterFinish.add(lraId1);
+
+ URI lraIdFromLocationHeader = URI.create(response.getHeaderString(HttpHeaders.LOCATION));
+ Assert.assertEquals("Expecting the LOCATION header configures the same LRA id as entity content on starting top-level LRA",
+ lraId1, lraIdFromLocationHeader);
+ // context header is returned strangely to client, some investigation will be needed
+ // URI lraIdFromLRAContextHeader = URI.create(response.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER));
+ // Assert.assertEquals("Expecting the LRA context header configures the same LRA id as entity content on starting top-level LRA",
+ // lraId1, lraIdFromLRAContextHeader);
+ Assert.assertEquals("Expecting to get the same API version as used for the request on top-level LRA start",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ }
+
+ String encodedLraId1 = URLEncoder.encode(lraId1.toString(), StandardCharsets.UTF_8.name());
+ try(Response response = client.target(coordinatorUrl)
+ .path("start")
+ .queryParam(CLIENT_ID_PARAM_NAME_V1_0, testName.getMethodName() + "_2")
+ .queryParam(PARENT_LRA_PARAM_NAME_V1_0, encodedLraId1)
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .post(null)) {
+ Assert.assertEquals("Creating nested LRA should be successful, POST/201 is expected.",
+ Status.CREATED.getStatusCode(), response.getStatus());
+ lraId2 = response.readEntity(URI.class);
+ Assert.assertNotNull("Expected non null nested LRA id from entity of response '" + response + "'", lraId2);
+
+ // the nested LRA id is in format ?ParentLRA=
+ URI lraIdFromLocationHeader = URI.create(response.getHeaderString(HttpHeaders.LOCATION));
+ Assert.assertEquals("Expecting the LOCATION header configures the same LRA id as entity content on starting nested LRA",
+ lraId2, lraIdFromLocationHeader);
+ // context header is returned strangely to client, some investigation will be needed
+ // String lraContextHeader = response.getHeaderString(LRA.LRA_HTTP_CONTEXT_HEADER);
+ // the context header is in format ,?ParentLRA=
+ // MatcherAssert.assertThat("Expected the nested LRA context header gives the parent LRA id at first",
+ // lraContextHeader, startsWith(lraId1.toASCIIString()));
+ // MatcherAssert.assertThat("Expected the nested LRA context header provides LRA id of started nested LRA",
+ // lraContextHeader, containsString("," + lraId2.toASCIIString()));
+ Assert.assertEquals("Expecting to get the same API version as used for the request on nested LRA start",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ }
+
+ Collection returnedLraIds = lraClient.getAllLRAs().stream().map(LRAData::getLraId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator knows about the top-level LRA", returnedLraIds, hasItem(lraId1));
+ MatcherAssert.assertThat("Expected the coordinator knows about the nested LRA", returnedLraIds, hasItem(lraId2));
+
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId1 + "/close")
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .put(null)) {
+ lrasToAfterFinish.clear(); // we've closed the LRA manually here, skipping the @After
+ Assert.assertEquals("Closing top-level LRA should be successful, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Closing top-level LRA should return the right status.",
+ LRAStatus.Closed.name(), response.readEntity(String.class));
+ Assert.assertEquals("Expecting to get the same API version as used for the request to close top-level LRA",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ }
+
+ Collection activeLRAsAfterClosing = lraClient.getAllLRAs().stream()
+ .filter(data -> data.getLraId().equals(lraId1) || data.getLraId().equals(lraId2)).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expecting the started LRAs are not more active after closing the top-level one",
+ activeLRAsAfterClosing, emptyCollectionOf(LRAData.class));
+ }
+
+ @Test // TODO: delete me
+ public void startCancelLRA() throws UnsupportedEncodingException {
+ URI lraId;
+ try (Response response = client.target(coordinatorUrl)
+ .path("start")
+ .queryParam(CLIENT_ID_PARAM_NAME_V1_0, testName.getMethodName())
+ .request()
+ .post(null)) {
+ Assert.assertEquals("Creating top-level LRA should be successful, POST/201 is expected.",
+ Status.CREATED.getStatusCode(), response.getStatus());
+ lraId = response.readEntity(URI.class);
+ Assert.assertNotNull("Expected non null LRA id from entity of response '" + response + "'", lraId);
+ lrasToAfterFinish.add(lraId);
+ Assert.assertEquals("Expecting to get the most up-to-date API version when passed no one on POST query",
+ JaxRsActivator.LRA_API_VERSION_STRING, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ }
+
+ Collection returnedLraIds = lraClient.getAllLRAs().stream().map(LRAData::getLraId).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expected the coordinator knows about the LRA", returnedLraIds, hasItem(lraId));
+ try (Response response = client.target(coordinatorUrl)
+ .path(URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name()) + "/cancel")
+ .request()
+ .put(null)) {
+ lrasToAfterFinish.clear(); // we've closed the LRA manually just now, skipping the @After
+ Assert.assertEquals("Closing LRA should be successful, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Canceling top-level LRA should return the right status.",
+ LRAStatus.Cancelled.name(), response.readEntity(String.class));
+ Assert.assertEquals("Expecting to get the most up-to-date API version when passed no one on POST query",
+ JaxRsActivator.LRA_API_VERSION_STRING, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ }
+
+ Collection activeLRAsAfterClosing = lraClient.getAllLRAs().stream()
+ .filter(data -> data.getLraId().equals(lraId)).collect(Collectors.toList());
+ MatcherAssert.assertThat("Expecting the started LRA is no more active after closing it",
+ activeLRAsAfterClosing, emptyCollectionOf(LRAData.class));
+ }
+
+ @Test
+ public void startLRANotExistingParentLRA() {
+ String notExistingParentLRA = "not-existing-parent-lra-id";
+ try (Response response = client.target(coordinatorUrl)
+ .path("start")
+ .queryParam(CLIENT_ID_PARAM_NAME_V1_0, testName.getMethodName())
+ .queryParam(PARENT_LRA_PARAM_NAME_V1_0, notExistingParentLRA)
+ .request()
+ .post(null)) {
+ Assert.assertEquals("Expected failure on non-existing parent LRA, POST/404 is expected.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String errorMsg = response.readEntity(String.class);
+ MatcherAssert.assertThat("Expected error message to contain the not found parent LRA id",
+ errorMsg, containsString(notExistingParentLRA));
+ }
+ }
+
+ @Test
+ public void closeNotExistingLRA() {
+ String notExistingLRAid = "not-existing-lra-id";
+ try (Response response = client.target(coordinatorUrl)
+ .path(notExistingLRAid)
+ .path("close")
+ .request()
+ .put(null)) {
+ Assert.assertEquals("Expected failure on non-existing LRA id, PUT/404 is expected.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String errorMsg = response.readEntity(String.class);
+ MatcherAssert.assertThat("Expected error message to contain the not found LRA id",
+ errorMsg, containsString(notExistingLRAid));
+ }
+ }
+
+ @Test
+ public void cancelNotExistingLRA() {
+ String notExistingLRAid = "not-existing-lra-id";
+ try (Response response = client.target(coordinatorUrl)
+ .path(notExistingLRAid)
+ .path("cancel")
+ .request()
+ .put(null)) {
+ Assert.assertEquals("Expected failure on non-existing LRA id, PUT/404 is expected.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String errorMsg = response.readEntity(String.class);
+ MatcherAssert.assertThat("Expected error message to contain the not found LRA id",
+ errorMsg, containsString(notExistingLRAid));
+ }
+ }
+
+ @Test
+ public void renewTimeLimit() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ Optional data = lraClient.getAllLRAs().stream().filter(l -> l.getLraId().equals(lraId)).findFirst();
+ Assert.assertTrue("Expected the started LRA will retrieved by LRA client get", data.isPresent());
+ Assert.assertEquals("Expected not defined finish time", 0L, data.get().getFinishTime());
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .path("renew")
+ .queryParam(TIME_LIMIT_PARAM_NAME_V1_0, Integer.MAX_VALUE)
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .put(null)) {
+ Assert.assertEquals("Expected time limit request to succeed, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expecting to get the most up-to-date API version when passed no one on POST query",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ MatcherAssert.assertThat("Expected the found LRA id is returned",
+ response.readEntity(String.class), containsString(lraId.toString()));
+ }
+
+ data = lraClient.getAllLRAs().stream().filter(l -> l.getLraId().equals(lraId)).findFirst();
+ Assert.assertTrue("Expected the started LRA will retrieved by LRA client get", data.isPresent());
+ MatcherAssert.assertThat("Expected finish time to not be 0 as time limit was defined",
+ data.get().getFinishTime(), greaterThan(0L));
+ }
+
+ @Test
+ public void renewTimeLimitNotExistingLRA() {
+ String notExistingLRAid = "not-existing-lra-id";
+ try (Response response = client.target(coordinatorUrl)
+ .path(notExistingLRAid)
+ .path("renew")
+ .queryParam(TIME_LIMIT_PARAM_NAME_V1_0, Integer.MAX_VALUE)
+ .request()
+ .put(null)) {
+ Assert.assertEquals("Expected time limit request to succeed, PUT/404 is expected.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ String errorMsg = response.readEntity(String.class);
+ MatcherAssert.assertThat("Expected error message to contain the not found LRA id",
+ errorMsg, containsString(notExistingLRAid));
+ }
+ }
+
+ @Test
+ public void joinLRAWithBody() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .put(Entity.text("http://compensator.url:8080"))) {
+ Assert.assertEquals("Expected joining LRA succeeded, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expecting API version header",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ String recoveryHeaderUrlMessage = response.getHeaderString(RECOVERY_HEADER_NAME_V1_0);
+ String recoveryUrlBody = response.readEntity(String.class);
+ URI recoveryUrlLocation = response.getLocation();
+ Assert.assertEquals("Expecting returned body and recovery header has the same content",
+ recoveryUrlBody, recoveryHeaderUrlMessage);
+ Assert.assertEquals("Expecting returned body and location has the same content",
+ recoveryUrlBody, recoveryUrlLocation.toString());
+ MatcherAssert.assertThat("Expected returned message contains the subpath of LRA recovery URL",
+ recoveryUrlBody, containsString("lra-coordinator/recovery"));
+ MatcherAssert.assertThat("Expected returned message contains the LRA id",
+ recoveryUrlBody, containsString(encodedLraId));
+ }
+ }
+
+ @Test
+ public void joinLRAWithLinkSimple() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .header("Link", "http://compensator.url:8080")
+ .put(null)) {
+ Assert.assertEquals("Expected joining LRA succeeded, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expecting API version header",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ String recoveryHeaderUrlMessage = response.getHeaderString(RECOVERY_HEADER_NAME_V1_0);
+ String recoveryUrlBody = response.readEntity(String.class);
+ URI recoveryUrlLocation = response.getLocation();
+ Assert.assertEquals("Expecting returned body and recovery header has the same content",
+ recoveryUrlBody, recoveryHeaderUrlMessage);
+ Assert.assertEquals("Expecting returned body and location has the same content",
+ recoveryUrlBody, recoveryUrlLocation.toString());
+ MatcherAssert.assertThat("Expected returned message contains the subpath of LRA recovery URL",
+ recoveryUrlBody, containsString("lra-coordinator/recovery"));
+ MatcherAssert.assertThat("Expected returned message contains the LRA id",
+ recoveryUrlBody, containsString(encodedLraId));
+ }
+ }
+
+
+ @Test
+ public void joinLRAWithLinkCompensate() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ Link link = Link.fromUri("http://compensate.url:8080").rel("compensate").build();
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header("Link", link.toString())
+ .put(null)) {
+ Assert.assertEquals("Expected joining LRA succeeded, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expecting the most up-to-date API version header",
+ JaxRsActivator.LRA_API_VERSION_STRING, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ String recoveryHeaderUrlMessage = response.getHeaderString(RECOVERY_HEADER_NAME_V1_0);
+ String recoveryUrlBody = response.readEntity(String.class);
+ Assert.assertEquals("Expecting returned body and recovery header has the same content",
+ recoveryUrlBody, recoveryHeaderUrlMessage);
+ MatcherAssert.assertThat("Expected returned message contains the subpath of LRA recovery URL",
+ recoveryUrlBody, containsString("lra-coordinator/recovery"));
+ }
+ }
+
+ @Test
+ public void joinLRAWithLinkAfter() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ Link afterLink = Link.fromUri("http://after.url:8080").rel("after").build();
+ Link unknownLink = Link.fromUri("http://unknow.url:8080").rel("uknown").build();
+ String linkList = afterLink.toString() + "," + unknownLink.toString();
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header("Link", linkList)
+ .put(null)) {
+ Assert.assertEquals("Expected joining LRA succeeded, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ String recoveryHeaderUrlMessage = response.getHeaderString(RECOVERY_HEADER_NAME_V1_0);
+ String recoveryUrlBody = response.readEntity(String.class);
+ Assert.assertEquals("Expecting returned body and recovery header has the same content",
+ recoveryUrlBody, recoveryHeaderUrlMessage);
+ MatcherAssert.assertThat("Expected returned message contains the subpath of LRA recovery URL",
+ URLDecoder.decode(recoveryUrlBody, StandardCharsets.UTF_8.name()), containsString("lra-coordinator/recovery"));
+ }
+ }
+
+ @Test
+ public void joinLRAIncorrectLinkFormat() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header("Link", ";rel=myrel;")
+ .put(null)) {
+ Assert.assertEquals("Expected the join failing, PUT/500 is expected.",
+ Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus());
+ }
+ }
+
+ @Test
+ public void joinLRAUnknownLRA() {
+ String notExistingLRAid = "not-existing-lra-id";
+ try (Response response = client.target(coordinatorUrl)
+ .path(notExistingLRAid)
+ .request()
+ .put(Entity.text("http://localhost:8080"))) {
+ Assert.assertEquals("Expected the join failing on unknown LRA id, PUT/404 is expected.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected error message to contain the LRA id where enlist failed",
+ response.readEntity(String.class), containsString(notExistingLRAid));
+ }
+ }
+
+ @Test
+ public void joinLRAWrongCompensatorData() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .put(Entity.text("this-is-not-an-url::::"))) {
+ Assert.assertEquals("Expected the join failing on wrong compensator data format, PUT/412 is expected.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected error message to contain the LRA id where enlist failed",
+ response.readEntity(String.class), containsString(lraId.toString()));
+ }
+ }
+
+ @Test
+ public void joinLRAWithLinkNotEnoughData() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+
+ String encodedLraId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ Link link = Link.fromUri("http://complete.url:8080").rel("complete").build();
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLraId)
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .header("Link", link.toString())
+ .put(null)) {
+ Assert.assertEquals("Expected the joining fails as no compensate in link, PUT/400 is expected.",
+ Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ String errorMsg = response.readEntity(String.class);
+ MatcherAssert.assertThat("Expected error message to contain the LRA id where enlist failed",
+ errorMsg, containsString(lraId.toString()));
+ }
+ }
+
+ @Test
+ public void leaveLRA() throws UnsupportedEncodingException {
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+ URI recoveryUri = lraClient.joinLRA(lraId, 0L, URI.create("http://localhost:8080"), "");
+
+ String encodedLRAId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLRAId)
+ .path("remove")
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .put(Entity.text(recoveryUri.toString()))) {
+ Assert.assertEquals("Expected leaving of LRA to succeed, PUT/200 is expected.",
+ Status.OK.getStatusCode(), response.getStatus());
+ Assert.assertEquals("Expecting API version header",
+ API_VERSION_1_0, response.getHeaderString(LRA_API_VERSION_HEADER_NAME_V1_0));
+ Assert.assertFalse("Expecting 'remove' API call returns no entity body", response.hasEntity());
+ }
+
+ try (Response response = client.target(coordinatorUrl)
+ .path(encodedLRAId)
+ .path("remove")
+ .request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, API_VERSION_1_0)
+ .put(Entity.text(recoveryUri.toString()))) {
+ Assert.assertEquals("Expected leaving of LRA to fail as it was removed just before, PUT/400 is expected.",
+ Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the non existing participant id",
+ response.readEntity(String.class), containsString(recoveryUri.toASCIIString()));
+ }
+ }
+
+ @Test
+ public void leaveLRANonExistingFailure() throws UnsupportedEncodingException {
+ String nonExistingLRAId = "http://localhost:1234/Non-Existing-LRA-id";
+ String encodedNonExistingLRAId = URLEncoder.encode(nonExistingLRAId, StandardCharsets.UTF_8.name());
+ try (Response response = client.target(coordinatorUrl).path(encodedNonExistingLRAId).path("remove").request().put(Entity.text("nothing"))) {
+ Assert.assertEquals("Expected that the call finds not found of " + encodedNonExistingLRAId + ", PUT/404.",
+ Status.NOT_FOUND.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the wrong LRA id",
+ response.readEntity(String.class), containsString(nonExistingLRAId));
+ }
+
+ URI lraId = lraClient.startLRA(testName.getMethodName());
+ lrasToAfterFinish.add(lraId);
+ String encodedLRAId = URLEncoder.encode(lraId.toString(), StandardCharsets.UTF_8.name());
+ String nonExistingParticipantUrl = "http://localhost:1234/Non-Existing-participant-LRA";
+ try (Response response = client.target(coordinatorUrl).path(encodedLRAId).path("remove").request()
+ .put(Entity.text(nonExistingParticipantUrl))) {
+ Assert.assertEquals("Expected that the call fails on LRA participant " + nonExistingParticipantUrl + " not found , PUT/400.",
+ Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the failure message to contain the wrong participant id",
+ response.readEntity(String.class), containsString(nonExistingParticipantUrl));
+ }
+ }
+
+
+ // ----------------------------------------------------------------------------------
+ // ------------------------ VERSION HEADER VERIFICATION -----------------------------
+ // ----------------------------------------------------------------------------------
+
+ @Test
+ public void getAllLRAsWrongVersion() {
+ try (Response response = client.target(coordinatorUrl)
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).get()) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void getLRAStatusWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("status")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).get()) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void getLRAInfoWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).get()) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void startLRAWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("start")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).post(null)) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void renewTimeLimitWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id").path("renew")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).put(null)) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void closeLRAWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id").path("close")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).put(null)) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void cancelLRAWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id").path("cancel")
+ .request().header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).put(null)) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+ @Test
+ public void joinViaBodyWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id").request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).put(Entity.text("compensator-url"))) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+ @Test
+ public void leaveLRAWrongVersion() {
+ try (Response response = client.target(coordinatorUrl).path("lra-id").path("remove").request()
+ .header(LRA_API_VERSION_HEADER_NAME_V1_0, NOT_SUPPORTED_FUTURE_LRA_VERSION).put(Entity.text("participant-url"))) {
+ Assert.assertEquals("Expected version on method call is not supported, GET/412.",
+ Status.PRECONDITION_FAILED.getStatusCode(), response.getStatus());
+ MatcherAssert.assertThat("Expected the response to contain the wrong version",
+ response.readEntity(String.class), containsString(NOT_SUPPORTED_FUTURE_LRA_VERSION));
+ }
+ }
+
+}
diff --git a/rts/lra/test/basic/src/test/resources/META-INF/services/javax.ws.rs.client.ClientBuilder b/rts/lra/test/basic/src/test/resources/META-INF/services/javax.ws.rs.client.ClientBuilder
new file mode 100644
index 0000000000..114f3fc97a
--- /dev/null
+++ b/rts/lra/test/basic/src/test/resources/META-INF/services/javax.ws.rs.client.ClientBuilder
@@ -0,0 +1 @@
+org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder
\ No newline at end of file