diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java index 87d285b4..e83c05e6 100644 --- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java +++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Compensate.java @@ -35,14 +35,29 @@ * will be chosen). The spec makes no guarantees about when it will be invoked, * just that is will eventually be called. * - * The id of the currently running LRA can be obtained by inspecting the incoming - * JAX-RS headers. If this LRA is nested then the parent LRA MUST be present - * in the header with the name + * If the annotated method is a JAX-RS resource method the id of the currently + * running LRA can be obtained by inspecting the incoming JAX-RS headers. If + * this LRA is nested then the parent LRA MUST be present in the header with the name * {@link org.eclipse.microprofile.lra.annotation.ws.rs.LRA#LRA_HTTP_PARENT_CONTEXT_HEADER}. * + * If the annotated method is not a JAX-RS resource method the id of the currently + * running LRA can be obtained by adhering to a predefined method signature as + * defined in the LRA specification document. Similarly the method may determine + * whether or not it runs with a nested LRA by providing a parameter to hold the parent id. + * For example, + *
+ *     
+ *          @Compensate
+ *          public void compensate(URI lraId, URI parentId) { ...}
+ *     
+ * 
+ * would be a valid compensation method declaration. If an invalid signature is detected + * the {@link org.eclipse.microprofile.lra.participant.InvalidLRAParticipantDefinitionException} + * will be thrown during the application startup. + * * Note that, according to the state model {@link LRAStatus} once an LRA has been * asked to cancel it is no longer possible to join with it as a participant. - * Therefore combining this annotation with an `@LRA` annotation that does not + * Therefore in JAX-RS, combining this annotation with an `@LRA` annotation that does not * start a new LRA will result in a `412 PreCondition Failed` status code and is * not advised. On the other hand, combining it with an `@LRA` annotation that * begins a new LRA can in certain use case make sense, but in this case the LRA diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java index 9be2901a..46537093 100644 --- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java +++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Complete.java @@ -37,14 +37,29 @@ * If the annotation is present on more than one method then an arbitrary one * will be chosen. * - * The id of the currently running LRA can be obtained by inspecting the incoming - * JAX-RS headers. If this LRA is nested then the parent LRA MUST be present - * in the header with the name + * If the annotated method is a JAX-RS resource method the id of the currently + * running LRA can be obtained by inspecting the incoming JAX-RS headers. If + * this LRA is nested then the parent LRA MUST be present in the header with the name * {@link org.eclipse.microprofile.lra.annotation.ws.rs.LRA#LRA_HTTP_PARENT_CONTEXT_HEADER}. * + * If the annotated method is not a JAX-RS resource method the id of the currently + * running LRA can be obtained by adhering to a predefined method signature as + * defined in the LRA specification document. Similarly the method may determine + * whether or not it runs with a nested LRA by providing a parameter to hold the parent id. + * For example, + *
+ *     
+ *          @Complete
+ *          public void complete(URI lraId, URI parentId) { ...}
+ *     
+ * 
+ * would be a valid completion method declaration. If an invalid signature is detected + * the {@link org.eclipse.microprofile.lra.participant.InvalidLRAParticipantDefinitionException} + * will be thrown during the application startup. + * * Note that, according to the state model {@link LRAStatus} once an LRA has been * asked to close it is no longer possible to join with it as a participant. - * Therefore combining this annotation with an `@LRA` annotation that does not + * Therefore in JAX-RS, combining this annotation with an `@LRA` annotation that does not * start a new LRA will result in a `412 PreCondition Failed` status code and is * not advised. On the other hand, combining it with an `@LRA` annotation that * begins a new LRA can in certain use case make sense, but in this case the LRA diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Forget.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Forget.java index 1b26e16e..843d71ef 100644 --- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Forget.java +++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Forget.java @@ -33,9 +33,7 @@ * in progress) or because of a failure (ie will never be able to finish) * then it must remember the fact (by reporting it when asked for its' * {@link Status})) until explicitly told that it can clean - * up using this @Forget annotation. The annotated method - * must be a standard JAX-RS endpoint annotated with the JAX-RS - * @DELETE annotation. + * up using this @Forget annotation. * * A similar remark applies if the participant was enlisted in a * nested LRA {@link LRA#value()}. Actions performed in the context @@ -43,11 +41,27 @@ * is explicitly told it can clean up using this @Forget * annotation. * - * The id of the currently running LRA can be obtained by inspecting the - * incoming JAX-RS headers. If this LRA is nested then the parent LRA - * MUST be present in the header with the name + * If the annotated method is a JAX-RS resource method the id of the currently + * running LRA can be obtained by inspecting the incoming JAX-RS headers. If + * this LRA is nested then the parent LRA MUST be present in the header with the name * {@link org.eclipse.microprofile.lra.annotation.ws.rs.LRA#LRA_HTTP_PARENT_CONTEXT_HEADER}. * + * If the annotated method is not a JAX-RS resource method the id of the currently + * running LRA can be obtained by adhering to a predefined method signature as + * defined in the LRA specification document. Similarly the method may determine + * whether or not it runs with a nested LRA by providing a parameter to hold the parent id. + * For example, + *
+ *     
+ *          @Forget
+ *          public void forget(URI lraId, URI parentId) { ...}
+ *     
+ * 
+ * would be a valid forget method declaration. If an invalid signature is detected + * the {@link org.eclipse.microprofile.lra.participant.InvalidLRAParticipantDefinitionException} + * will be thrown during the application startup. + * + * * Since the participant generally needs to know the id of the LRA in order * to clean up there is generally no benefit to combining this annotation * with the `@LRA` annotation (though it is not prohibited). diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Status.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Status.java index a6160f3a..df18eb78 100644 --- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/Status.java +++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/Status.java @@ -36,18 +36,35 @@ * or compensation notification but if the participant (the class that * contains the Compensate and Complete annotations) does not * support idempotency then it must be able to report its' status by - * by annotating one of the methods with this @Status annotation - * together with the @GET JAX-RS annotation. + * by annotating one of the methods with this @Status. * The annotated method should report the status according to one of the * {@link ParticipantStatus} enum values. * + * If the annotated method is a JAX-RS resource method the id of the currently + * running LRA can be obtained by inspecting the incoming JAX-RS headers. If + * this LRA is nested then the parent LRA MUST be present in the header with the name + * {@link org.eclipse.microprofile.lra.annotation.ws.rs.LRA#LRA_HTTP_PARENT_CONTEXT_HEADER}. + * + * If the annotated method is not a JAX-RS resource method the id of the currently + * running LRA can be obtained by adhering to a predefined method signature as + * defined in the LRA specification document. Similarly the method may determine + * whether or not it runs with a nested LRA by providing a parameter to hold the parent id. + * For example, + *
+ *     
+ *          @Status
+ *          public void status(URI lraId, URI parentId) { ...}
+ *     
+ * 
+ * would be a valid status method declaration. If an invalid signature is detected + * the {@link org.eclipse.microprofile.lra.participant.InvalidLRAParticipantDefinitionException} + * will be thrown during the application startup. + * * If the participant has already responded successfully to an invocation * of the @Compensate or @Complete method then it may - * report 404 Not Found HTTP status code. This enables the - * participant to free up resources. - * - * The id of the currently running LRA can be obtained by inspecting the - * incoming JAX-RS headers. + * report 404 Not Found HTTP status code or in case of + * non-JAX-RS method returning {@link ParticipantStatus} to return null. + * This enables the participant to free up resources. * * Since the participant generally needs to know the id of the LRA in order * to report its status there is generally no benefit to combining this diff --git a/api/src/main/java/org/eclipse/microprofile/lra/participant/InvalidLRAParticipantDefinitionException.java b/api/src/main/java/org/eclipse/microprofile/lra/participant/InvalidLRAParticipantDefinitionException.java new file mode 100644 index 00000000..ca6f4d68 --- /dev/null +++ b/api/src/main/java/org/eclipse/microprofile/lra/participant/InvalidLRAParticipantDefinitionException.java @@ -0,0 +1,54 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.participant; + +/** + * Runtime exception thrown when invalid non-JAX-RS LRA signature definition is detected. + * + * The prohibited valid signatures are: + * Return type: + * - void: successfull execution is mapped to `Compensated` or `Completed` participant statuses, + * error execution is handled by exceptions thrown in the participant method + * - not applicable for `@Status` participant methods + * - {@link org.eclipse.microprofile.lra.annotation.ParticipantStatus} + * - {@link javax.ws.rs.core.Response}: handled similarly as for JAX-RS participant methods + * - java.util.concurrent.CompletionStage: with the parameter of any of the previously + * defined types + * + * Arguments: up to 2 arguments of types in this order: + * - {@link java.net.URI}: representing current LRA context identification + * - {@link java.net.URI}: representing potentional parent LRA context identification + * + * Any other signature will result in deployment / start time of this type. + */ +public class InvalidLRAParticipantDefinitionException extends RuntimeException { + + public InvalidLRAParticipantDefinitionException(String message) { + super(message); + } + + public InvalidLRAParticipantDefinitionException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidLRAParticipantDefinitionException(Throwable cause) { + super(cause); + } +} diff --git a/api/src/main/java/org/eclipse/microprofile/lra/participant/JoinLRAException.java b/api/src/main/java/org/eclipse/microprofile/lra/participant/JoinLRAException.java deleted file mode 100644 index a59161d4..00000000 --- a/api/src/main/java/org/eclipse/microprofile/lra/participant/JoinLRAException.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *******************************************************************************/ - -package org.eclipse.microprofile.lra.participant; - -import java.net.URI; - -/** - * An exception used to report failures during enlistment of a participant in an LRA - */ -public class JoinLRAException extends Exception { - private URI lraId; - private int statusCode; - - /** - * @return the specific reason for why the enlistment failed - */ - public int getStatusCode() { - return statusCode; - } - - /** - * @return the LRA that join request related to - */ - public URI getLraId() { - return lraId; - } - - public JoinLRAException(URI lraId, int statusCode, String message, Throwable cause) { - super(String.format("%s: %s", lraId, message), cause); - - this.lraId = lraId; - this.statusCode = statusCode; - } -} diff --git a/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAManagement.java b/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAManagement.java deleted file mode 100644 index f7b03b86..00000000 --- a/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAManagement.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *******************************************************************************/ - -package org.eclipse.microprofile.lra.participant; - -import javax.enterprise.context.ApplicationScoped; -import java.net.URI; -import java.time.temporal.ChronoUnit; - -@ApplicationScoped -public interface LRAManagement { - /** - * Join an existing LRA. - * - * @param participant an instance of a {@link LRAParticipant} that will be - * notified when the target LRA ends - * @param lraId the LRA that the join request pertains to - * @param timeLimit the time for which the participant should remain valid. When - * this time limit is exceeded the participant may longer be able - * to fulfil the protocol guarantees. - * @param unit the unit that the timeLimit parameter is expressed in - * - * @return a recovery URI for this enlistment - * - * @throws JoinLRAException if the request to the coordinator failed. - * {@link JoinLRAException#getCause()} and/or - * {@link JoinLRAException#getStatusCode()} may provide a more specific reason - */ - URI joinLRA(LRAParticipant participant, URI lraId, Long timeLimit, - ChronoUnit unit) - throws JoinLRAException; - - /** - * Join an existing LRA. In contrast to the other form of registration this - * method does not indicate a time limit for the participant meaning that the - * participant registration will remain valid until it terminates successfully - * or unsuccessfully (ie it will never be timed out externally). - * - * @param participant an instance of a {@link LRAParticipant} that will be - * notified when the target LRA ends - * @param lraId the LRA that the join request pertains to - * - * @return a recovery URI for this enlistment - * - * @throws JoinLRAException if the request to the coordinator failed. - * {@link JoinLRAException#getCause()} and/or - * {@link JoinLRAException#getStatusCode()} may provide a more specific reason - */ - URI joinLRA(LRAParticipant participant, URI lraId) throws JoinLRAException; - -} diff --git a/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAParticipant.java b/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAParticipant.java deleted file mode 100644 index 7de2c8ee..00000000 --- a/api/src/main/java/org/eclipse/microprofile/lra/participant/LRAParticipant.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *******************************************************************************/ - -package org.eclipse.microprofile.lra.participant; - -import javax.ws.rs.NotFoundException; -import java.io.Serializable; -import java.net.URI; -import java.util.concurrent.Future; - -/** - * The API for notifying participants that a LRA is completing or cancelling. - * A participant joins with an LRA via a call to - * {@link LRAManagement#joinLRA(LRAParticipant, URI)} - */ -public interface LRAParticipant extends Serializable { - /** - * Notifies the participant that the LRA is closing - * @param lraId the LRA that is closing - * @return null if the participant completed successfully. If the participant - * cannot complete immediately it should return a future that the caller - * can used to monitor progress. If the JVM crashes before the participant - * can finish it should expect this method to be called again. If the - * participant fails to complete it must cancel the future or throw a - * TerminationException. - * @throws NotFoundException the participant does not know about this LRA - * @throws TerminationException the participant was unable to complete and will - * never be able to do so - */ - Future completeWork(URI lraId) - throws NotFoundException, TerminationException; - - /** - * Notifies the participant that the LRA is cancelling - * @param lraId the LRA that is closing - * @return null if the participant completed successfully. If the participant - * cannot complete immediately it should return a future that the - * caller can use to monitor progress. If the JVM crashes before - * the participant can finish it should expect this method to be - * called again. If the participant fails to complete it must cancel - * the future or throw a TerminationException. - * @throws NotFoundException the participant does not know about this LRA - * @throws TerminationException the participant was unable to complete and - * will never be able to do so - */ - Future compensateWork(URI lraId) - throws NotFoundException, TerminationException; -} - diff --git a/api/src/main/java/org/eclipse/microprofile/lra/participant/TerminationException.java b/api/src/main/java/org/eclipse/microprofile/lra/participant/TerminationException.java deleted file mode 100644 index 98654e75..00000000 --- a/api/src/main/java/org/eclipse/microprofile/lra/participant/TerminationException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - ******************************************************************************* - * Copyright (c) 2018 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * You may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - *******************************************************************************/ - -package org.eclipse.microprofile.lra.participant; - -public class TerminationException extends Exception { - private static final long serialVersionUID = 1L; -} diff --git a/spec/src/main/asciidoc/microprofile-lra-spec.adoc b/spec/src/main/asciidoc/microprofile-lra-spec.adoc index 742af9e5..491a001f 100644 --- a/spec/src/main/asciidoc/microprofile-lra-spec.adoc +++ b/spec/src/main/asciidoc/microprofile-lra-spec.adoc @@ -236,14 +236,8 @@ when the right subset of work (LRAs) is arrived at by the application will that subset be confirmed; all other LRAs will be told to cancel (complete in a failure state). -In the rest of this proposal we specify two different APIs for -controlling the life cycle of and participation in LRAs and a third API -for writing participants: - -1. <> for controlling -the life cycle and membership of LRAs -2. <> -to support services that do not use JAX-RS +In the rest of this proposal we specify an API for +controlling the life cycle of and participation in LRAs: [[java-annotations-for-lras]] === Java Annotations for LRAs @@ -505,10 +499,10 @@ the method finishes and the `end = true` element on the confirmTrip method force [[compensating-activities]] ==== Compensating Activities -The application developer indicates which JAX-RS method to use for a compensating -activity by annotating it with both the `@Compensate` and the JAX-RS `@PUT` annotations. +The application developer indicates which method to use for a compensating +activity by annotating it with the `@Compensate` annotation. Whenever the associated resource is invoked in the context of -an LRA the endpoint corresponding to the `@Compensate` method MUST be enlisted with +an LRA the method corresponding to the `@Compensate` method MUST be enlisted with the LRA. If the LRA is subsequently cancelled then the compensation method MUST be invoked. The specification does not mandate when this method is invoked @@ -530,17 +524,17 @@ An example of how to use this annoation is: } ---- -The resource class MAY also contain a method marked with the `@Complete` and the -JAX-RS `@PUT` annotations. If such a method is present then the method MUST -be invoked when the associated LRA is closed. Again, the specification does -not MANDATE when the method is called, just that it will eventually be invoked. -Typically the resource would use this call to perform any clean up actions. The -method is optional since such clean up actions may not be necessary, -for example consider a system that just tracks hotel reservations and has operations -for booking a room or cancelling the reservation (`@Compensate`). Since this system -is passive, once a room is booked, it does not make any difference if the LRA is -completed or not: the room will be unavailable for others. If it receives a call -to `@Compensate` then it will free the room. But it won't do anything on `@complete`. +The resource class MAY also contain a method marked with the `@Complete` annotation. +If such a method is present then the method MUST be invoked when the associated LRA +is closed. Again, the specification does not MANDATE when the method is called, +just that it will eventually be invoked. Typically the resource would use this call +to perform any clean up actions. The method is optional since such clean up actions +may not be necessary, for example consider a system that just tracks hotel reservations +and has operations for booking a room or cancelling the reservation (`@Compensate`). +Since this system is passive, once a room is booked, it does not make any difference +if the LRA is completed or not: the room will be unavailable for others. If it receives +a call to `@Compensate` then it will free the room. But it won't do anything on +`@Complete`. If the participant successfully compensates or completes then it may forget about the LRA. Otherwise it should remember that it is still associated with the LRA and @@ -555,11 +549,144 @@ of these methods. If it knows it will never be able to compensate or complete th it MUST remember the fact until explicitly told that it can clean up by providing a method annotated with `@Forget` (the requirement is marked MUST because message delivery is not guaranteed in a distributed system). -The forget method MUST be annotated with the JAX-RS `@DELETE` annotation. If there is no `@Status` then the `@Compensate` or `@Complete` methods will continue to be invoked until the implementation knows it has the final status. +If the `@Compensate` or `@Complete` annotation is present on multiple methods +then an arbitrary one is chosen. + +The javadoc for the <> provides +more details about this annotation. + +Similarly, the javadoc for the <> +provides details about the `@Complete` annotation. + +==== Participant marker annotations method signatures + +The participant marker annotations are annotations that allow users to mark a method +for the execution by the LRA implementation according to the <>. These annotations are: + +* `@Compensate` -- a method to be executed when the LRA is cancelled +* `@Complete` -- a method to be executed when the LRA is closed +* `@Status` -- a method that allow user to state status of the participant with regards +to a paricular LRA +* `@Forget` -- a method to be executed when the LRA allows participant to +clear all associated information + +This specification differentiates two types of participant method definitions -- +methods associated with the JAX-RS resource method or the methods which are not +bound to JAX-RS. + +[[jaxrs-participant-methods]] +===== JAX-RS participant methods + +The following table presents expectations that are placed on individual participant +annotations when associated with JAX-RS resource methods: + +[[jaxrs-response-table]] +|=== +| Annotation | HTTP method | Expected status codes | Response + +| `@Compensate` +| PUT +| 200, 202, 404, 412 +| <> + +| `@Complete` +| PUT +| 200, 202, 404, 412 +| <> + +| `@Status` +| GET +| 200, 202, 404, 412 +| <> + +| `@Forget` +| DELETE +| 200, 404, 412 +| no expectations + +|=== + +If the participant annotation is not accompanied by an associated JAX-RS HTTP method +annotation according to the <>, the error SHOULD be reported using a JAX-RS +exception mapper that maps to a `412 Precondition Failed` HTTP status code. + +[[non-jaxrs-participant-methods]] +===== Non-JAX-RS participant methods + +When the participant annotations are applied to the non-JAX-RS resource methods they +MUST adhere to these predefined signatures: + +* *Return type*: +** `void`: successfull execution is mapped to `Compensated` or `Completed` participant statuses, + error execution is handled by <> thrown in the participant method +*** not applicable for `@Status` participant methods +** <> +** `javax.ws.rs.core.Response`: handled similarly as for +<> +** `java.util.concurrent.CompletionStage`: with the parameter of any of the previously +defined types +* *Arguments*: up to 2 arguments of types in this order: +** `java.net.URI`: representing current LRA context identification +** `java.net.URI`: representing potentional parent LRA context identification + +Declaring more than two arguments, different types of arguments or different return type +for any non-JAX-RS method annotatated with the participant marker annotation MUST result +in the deployment time `InvalidLRAParticipantDefinitionException`. +Please note that both arguments are optional but the order is required. This means that +if only one argument is provided this argument will contain the value of the current +active LRA context (not the parent LRA context in case of nested LRA). + +Examples of valid signatures: + +[source,java] +---- +@Compensate +public void compensate(URI lraId, URI parentId) + +@Complete +public Response complete(URI lraId) + +@Status +public CompletionStage status(URI lraId) +---- + +Examples of invalid signatures: + +[source,java] +---- +@Compensate +public void compensate(String lraId, String parentId) // invalid types of arguments + +@Compensate +public String compensate(URI lraId) // invalid return type + +@Forget +public void forget(URI lraId, URI parentId, String additional) // too many arguments +---- + +[[non-jax-rs-exceptions]] +If any of the described methods throws an exception, we distinguish two cases depending +on the exception type: + +* `WebApplicationException` -- the exception is mapped to the HTTP response it carries +and then handled as defined in the section +<> +* any other exception +** @Compensate and @Complete - results into `FailedToCompensate` or `FailedToComplete` +participant states +** @Status and @Forget - as the participant may have already compensated (or completed) +or may in the process of compensation (completion) the exception in these methods should +result into failure condition (in JAX-RS this condition is represented by +500 return HTTP status code) which individual interpretation is left further unspecified. + +[[eventual-compensations]] +==== Eventual compensations + [[async-compensators]] If the resource cannot perform a compensation activity immediately the `@Compensate` method SHOULD do one or more of the following: @@ -573,21 +700,6 @@ The `@Status` method, if present, MUST report the progress of the compensation. Similarly if it cannot perform a completion activity immediately. -If the `@Compensate` or `@Complete` annotation is present on multiple methods -then an arbitrary one is chosen. If the annotation is not accompanied by -a JAX-RS `@PUT` annotation the error SHOULD be reported using a JAX-RS -exception mapper that maps to a `412 Precondition Failed` HTTP status code. - -footnote:[issue #15 (Provide an annotation to supply opaque data during -participant registration) allows participants to register opaque data when -joining with an LRA. This data is made available to the completion and -compensation callbacks] - -The javadoc for the <> provides -more details about this annotation. - -Similarly, the javadoc for the <> -provides details about the `@Complete` annotation. [[nesting-lras]] ==== Nesting LRAs @@ -713,7 +825,9 @@ report the status according to the If the participant has already responded successfully to an `@Compensate` or `@Complete` method invocation then it MAY report `404 Not Found` -HTTP status code. This enables the participant to free up resources. +HTTP status code or in the case of non-JAX-RS method returning +<> `null`. +This enables the participant to free up resources. [[forgetting-an-lra]] ==== Forgetting an LRA @@ -723,8 +837,7 @@ If a participant is unable to complete or compensate immediately in progress) or because of a failure (i.e. will never be able to finish) then it must remember the fact (by reporting its' status via the `@Status` method) until explicitly told that it can clean up -using this `@Forget` annotation. The forget annotation must be a -standard JAX-RS endpoint annotated with the JAX-RS `@DELETE` annotation. +using this `@Forget` annotation. This requirement ensures that the implementation will be able to guarantee the expectations of the LRA protocol under various failure conditions. @@ -847,87 +960,6 @@ survive system failures. The specification is not prescriptive about how an implementation achieves resiliency provided that it obeys the requirements of the specification as laid out in this document. -[[java-based-lra-participant-registration-api]] -=== Java based LRA participant registration API - -If an application does not directly expose JAX-RS endpoints for -compensation activities then participants can join an LRA directly using an instance of -<> -(https://github.com/eclipse/microprofile-lra/tree/master/api/src/main/java/org/eclipse/microprofile/lra/participant[github link]). -This interface requires that a participant be a Java class conforming to the -<> -(https://github.com/eclipse/microprofile-lra/tree/master/api/src/main/java/org/eclipse/microprofile/lra/participant[github link]). - -When an LRA is closed or cancelled the implementation [of this specification] is responsible -for invoking LRAParticipant completion or compensation -callbacks on any participants that were registered via the `LRAManagement#joinLRA` method. - -How the application obtains an LRAManagement instance is unspecified -(but CDI injection could be an obvious choice). For example, the following code listing -shows how to inject instances of the API and how to perform work in the context of an LRA: - -[source,java] ----- -@Path("/") -public class UnannotatedParticpant implements LRAParticipant { - @Inject - private LRAManagement lraManagement; - - public void doInTransaction() throws JoinLRAException - { - // start a new LRA with an unlimited timeout - // by invoking a suitable JAX-RS resource method annotated - // with @LRA(value = LRA.Type.REQUIRES_NEW, end = false) - ... - - // Join the LRA as a participant. - lraManagement.joinLRA(this, lraId, 0L, ChronoUnit.SECONDS); - - // do something interesting - - // close the LRA by invoking a suitable JAX-RS resource method - // annotated with, for example, - // @LRA(value = LRA.Type.SUPPORTS, end = true) - // This will cause the completion call back to be invoked - ... - - // the completeWork method will have been invoked at this point - } - - // the callback that will be invoked if the LRA is closed - @Override - public Future completeWork(URI lraId) - throws NotFoundException, TerminationException - { - return null; - } - - // the callback that will be invoked if the LRA is cancelled - @Override - public Future compensateWork(URI lraId) - throws NotFoundException, TerminationException - { - return null; - } -} ----- - -The framework must guarantee that participants will still be triggered (the LRA protocol still provides the "all or -nothing" guarantees that traditional transactions give). -This means that an implementation for all JAX-RS resources which implement the _LRAParticipant_ interface directly -or indirectly, MUST create some proxy which can be called by the spec implementation. + -This is to ensure that after this class has joined an LRA (through the _lraManagement.joinLRA()_ ) the _complete_ or -_compensate_ methods can be called by the spec implementation in case the microservice crashed before this notification could -be delivered. - -Serializable participants need to know how to contact the original -business application in order to trigger compensation activities whereas -a JAX-RS based solution need only persist resource paths which are likely -to correspond to existing microservice endpoints. In other words, from -an administrative and manageability point of view, it may be simpler to -use one of the other APIs such as the <>. - [[appendix-1]] == Appendix 1: Selected Javadoc API Descriptions @@ -965,17 +997,3 @@ include::{sourcedir}/org/eclipse/microprofile/lra/annotation/Complete.java[Leave include::{sourcedir}/org/eclipse/microprofile/lra/annotation/ParticipantStatus.java/[ParticipantStatus] ---- <<< - -=== LRAManagement -[[source-LRAManagement]] ----- -include::{sourcedir}/org/eclipse/microprofile/lra/participant/LRAManagement.java[LRAManagement] ----- -<<< - -=== LRAParticipant -[[source-LRAParticipant]] ----- -include::{sourcedir}/org/eclipse/microprofile/lra/participant/LRAParticipant.java[LRAParticipant] ----- -<<< diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckInvalidParticipantSignaturesTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckInvalidParticipantSignaturesTests.java new file mode 100644 index 00000000..cf0ec019 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckInvalidParticipantSignaturesTests.java @@ -0,0 +1,132 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck; + +import org.eclipse.microprofile.lra.participant.InvalidLRAParticipantDefinitionException; +import org.eclipse.microprofile.lra.tck.participant.nonjaxrs.InvalidArgumentTypesParticipant; +import org.eclipse.microprofile.lra.tck.participant.nonjaxrs.InvalidReturnTypeParticipant; +import org.eclipse.microprofile.lra.tck.participant.nonjaxrs.TooManyArgsParticipant; +import org.jboss.arquillian.container.spi.client.container.DeploymentException; +import org.jboss.arquillian.container.test.api.Deployer; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +/** + *

+ * TCK that verifies that invalid non-JAX-RS participant method signatures are reported during deployment + *

+ * + *

+ * Each test deploys an archive containing single invalid participant containing an error in its participant + * method signature and expects that such deployment is aborted with {@link InvalidLRAParticipantDefinitionException} + * according to the specification. + *

+ */ +@RunWith(Arquillian.class) +public class TckInvalidParticipantSignaturesTests { + + private static final String INVALID_RETURN_TYPE_DEPLOYMENT = "nonjaxrs-return-type-deploy"; + private static final String TOO_MANY_ARGS_DEPLOYMENT = "too-many-args-deploy"; + private static final String INVALID_ARGUMENT_TYPE_DEPLOYMENT = "nonjaxrs-argument-type-deploy"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public DeploymentNameRule deploymentNameRule = new DeploymentNameRule(); + + @ArquillianResource + private Deployer deployer; + + @Deployment(name = INVALID_RETURN_TYPE_DEPLOYMENT, managed = false) + public static WebArchive deployInvalidReturnTypeParticipant() { + return createArchive(InvalidReturnTypeParticipant.class); + } + + @Deployment(name = TOO_MANY_ARGS_DEPLOYMENT, managed = false) + public static WebArchive deployTooManyArgsParticipant() { + return createArchive(TooManyArgsParticipant.class); + } + + @Deployment(name = INVALID_ARGUMENT_TYPE_DEPLOYMENT, managed = false) + public static WebArchive deployInvalidArgumentTypeParticipant() { + return createArchive(InvalidArgumentTypesParticipant.class); + } + + private static WebArchive createArchive(Class participantClass) { + String archiveName = participantClass.getSimpleName(); + return ShrinkWrap + .create(WebArchive.class, archiveName + ".war") + .addClasses(participantClass, JaxRsActivator.class) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); + } + + @After + public void after() { + deployer.undeploy(deploymentNameRule.deploymentName); + } + + /** + * Verify that invalid return type (String) in participant method is detected + */ + @Test + public void invalidReturnTypeInParticipantMethodTest() { + testInvalidDeployment(INVALID_RETURN_TYPE_DEPLOYMENT); + } + + /** + * Verify that too many arguments (more than 2) in partcipant method are detected + */ + @Test + public void tooManyArgsInParticipantMethodTest() { + testInvalidDeployment(TOO_MANY_ARGS_DEPLOYMENT); + } + + /** + * Verigy that invalid type of argument (int) in participant method is detected + */ + @Test + public void invalidArgumentTypeInParticipantMethodTest() { + testInvalidDeployment(INVALID_ARGUMENT_TYPE_DEPLOYMENT); + } + + private void testInvalidDeployment(String deploymentName) { + deploymentNameRule.deploymentName = deploymentName; + expectedException.expect(DeploymentException.class); + expectedException.expectMessage(InvalidLRAParticipantDefinitionException.class.getSimpleName()); + + deployer.deploy(deploymentName); + } + + private static final class DeploymentNameRule extends TestName { + + String deploymentName; + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java index 87f42537..efa6f238 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckLRATypeTests.java @@ -20,29 +20,16 @@ package org.eclipse.microprofile.lra.tck; import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; -import org.eclipse.microprofile.lra.tck.participant.api.Util; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestName; import org.junit.runner.RunWith; -import javax.inject.Inject; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; -import java.net.MalformedURLException; import java.net.URI; -import java.net.URL; import java.time.temporal.ChronoUnit; import java.util.logging.Logger; @@ -89,47 +76,12 @@ * */ @RunWith(Arquillian.class) -public class TckLRATypeTests { +public class TckLRATypeTests extends TckTestBase { private static final Logger LOGGER = Logger.getLogger(TckLRATypeTests.class.getName()); - - @Rule public TestName testName = new TestName(); - - @Inject - private LraTckConfigBean config; - - private LRAClientOps lraClient; - - private static Client tckSuiteClient; - - private WebTarget tckSuiteTarget; - + @Deployment(name = "lra-type-tck-tests") public static WebArchive deploy() { - String archiveName = TckLRATypeTests.class.getSimpleName().toLowerCase(); - return ShrinkWrap - .create(WebArchive.class, archiveName + ".war") - .addPackages(true, "org.eclipse.microprofile.lra.tck") - .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); - } - - @AfterClass - public static void afterClass() { - if(tckSuiteClient != null) { - tckSuiteClient.close(); - } - } - - @Before - public void before() { - LOGGER.info("Running test: " + testName.getMethodName()); - setUpTestCase(); - - try { - tckSuiteTarget = tckSuiteClient.target(URI.create(new URL(config.tckSuiteBaseUrl()).toExternalForm())); - lraClient = new LRAClientOps(tckSuiteTarget); - } catch (MalformedURLException mfe) { - throw new IllegalStateException("Cannot create URL for the LRA TCK suite base url " + config.tckSuiteBaseUrl(), mfe); - } + return deploy(TckLRATypeTests.class.getSimpleName().toLowerCase()); } @After @@ -137,10 +89,6 @@ public void after() { lraClient.cleanUp(LOGGER, testName.getMethodName()); } - private void setUpTestCase() { - tckSuiteClient = ClientBuilder.newClient(); - } - // enum to indicate which checks to perform on the expected and actual active LRA after running a resource method private enum MethodLRACheck { NONE, NOT_PRESENT, EQUALS, NOT_EQUALS @@ -339,19 +287,4 @@ private void resourceRequest(String path, boolean startLRA, int expectedStatus, response.close(); } } - - /** - * The started LRA will be named based on the class name and the running test name. - */ - private String lraClientId() { - return this.getClass().getSimpleName() + "#" + testName.getMethodName(); - } - - /** - * Adjusting the default timeout by the specified timeout factor - * which can be defined by user. - */ - private long lraTimeout() { - return Util.adjust(LraTckConfigBean.LRA_TIMEOUT_MILLIS, config.timeoutFactor()); - } } diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckParticipantTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckParticipantTests.java new file mode 100644 index 00000000..cb509c4c --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckParticipantTests.java @@ -0,0 +1,205 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck; + +import org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid.ValidLRACSParticipant; +import org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid.ValidLRAParticipant; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ws.rs.core.Response; + +import static org.junit.Assert.assertEquals; + +/** + * TCK to verify that valid non-JAX-RS participant method signatures are respected + */ +@RunWith(Arquillian.class) +public class TckParticipantTests extends TckTestBase { + + private static final String VALID_DEPLOYMENT = "valid-deploy"; + + private int beforeCompletedCount; + private int beforeCompensatedCount; + private int beforeStatusCount; + private int beforeForgetCount; + + @Deployment(name = VALID_DEPLOYMENT) + public static WebArchive deployValidParticipant() { + return TckTestBase.deploy(VALID_DEPLOYMENT) + .addPackage(ValidLRAParticipant.class.getPackage()); + } + + @Before + public void before() { + super.before(); + + beforeCompensatedCount = getCompensateCount(); + beforeCompletedCount = getCompleteCount(); + beforeStatusCount = getStatusCount(); + beforeForgetCount = getForgetCount(); + } + + /** + * Test verifies that non-JAX-RS @Complete method is invoked according to the + * LRA protocol and that if {@link javax.ws.rs.WebApplicationException} is + * thrown inside of a non-JAX-RS participant method than {@link Response} it + * is carrying is extracted and acted upon accoding to LRA response handling + */ + @Test + public void validWebApplicationExceptionReturnedTest() { + Response response = tckSuiteTarget.path(ValidLRAParticipant.RESOURCE_PATH) + .path(ValidLRAParticipant.ENLIST_WITH_COMPLETE) + .request() + .get(); + + assertEquals("The 200 status response is expected", + Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals("Non JAX-RS @Complete method throwing WebApplicationException shoud have been called", + beforeCompletedCount + 1, getCompleteCount()); + assertEquals("@Compensate method should not have been called as LRA completed succesfully", + beforeCompensatedCount, getCompensateCount()); + + } + + /** + * This test verifies chained call of non-JAX-RS participant methods. First + * it starts and cancels a new LRA. @Compensate non-JAX-RS method then returns + * {@link org.eclipse.microprofile.lra.annotation.ParticipantStatus#Compensating} + * (see {@link ValidLRAParticipant}) indicating that non-JAX-RS @Status method should + * be invoked. The test then waits for recovery and then verifies that @Status method is + * called. This method finishes compensation with return of the {@link Response} object + * indicating failure and so the test then verifies that non-JAX-RS @Forget method has + * also been called. + */ + @Test + public void validSignaturesChainTest() throws InterruptedException { + Response response = tckSuiteTarget.path(ValidLRAParticipant.RESOURCE_PATH) + .path(ValidLRAParticipant.ENLIST_WITH_COMPENSATE) + .request() + .get(); + + assertEquals("The 500 status response is expected", + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); + assertEquals("Non JAX-RS @Compensate method should have been called", + beforeCompensatedCount + 1, getCompensateCount()); + assertEquals("@Complete method should not have been called as LRA compensated", + beforeCompletedCount, getCompleteCount()); + + replayEndPhase(ValidLRAParticipant.RESOURCE_PATH); + + assertEquals("Non JAX-RS @Status method should have been called", + beforeStatusCount + 1, getStatusCount()); + assertEquals("Non JAX-RS @Forget method should have been called", + beforeForgetCount + 1, getForgetCount()); + + } + + /** + * Test verifies {@link java.util.concurrent.CompletionStage} parametrized with + * {@link Void} as valid non-JAX-RS participant method return type + */ + @Test + public void testNonJaxRsCompletionStageVoid() { + int beforeCompletedCount = getCompleteCount(ValidLRACSParticipant.ROOT_PATH); + int beforeCompensateCount = getCompensateCount(ValidLRACSParticipant.ROOT_PATH); + + Response response = tckSuiteTarget.path(ValidLRACSParticipant.ROOT_PATH) + .path(ValidLRACSParticipant.ENLIST_WITH_COMPENSATE) + .request() + .get(); + + assertEquals("The 500 status response is expected", + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); + assertEquals("Non JAX-RS @Compensate method with CompletionStage should have been called", + beforeCompensateCount + 1, getCompensateCount(ValidLRACSParticipant.ROOT_PATH)); + assertEquals("Non JAX-RS @Complete method should have not been called", + beforeCompletedCount, getCompleteCount(ValidLRACSParticipant.ROOT_PATH)); + } + + /** + * Test verifyies {@link java.util.concurrent.CompletionStage} parametrized by + * {@link Response} and {@link org.eclipse.microprofile.lra.annotation.ParticipantStatus} as valid + * non-JAX-RS participant methods return types + */ + @Test + public void testNonJaxRsCompletionStageResponseAndParticipantStatus() throws InterruptedException { + int beforeCompletedCount = getCompleteCount(ValidLRACSParticipant.ROOT_PATH); + int beforeCompensateCount = getCompensateCount(ValidLRACSParticipant.ROOT_PATH); + int beforeStatusCount = getStatusCount(ValidLRACSParticipant.ROOT_PATH); + + Response response = tckSuiteTarget.path(ValidLRACSParticipant.ROOT_PATH) + .path(ValidLRACSParticipant.ENLIST_WITH_COMPLETE) + .request() + .get(); + + assertEquals("The 200 status response is expected", + Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals("Non JAX-RS @Complete method with CompletionStage should have been called", + beforeCompletedCount + 1, getCompleteCount(ValidLRACSParticipant.ROOT_PATH)); + assertEquals("Non JAX-RS @Compensate method should have not been called", + beforeCompensateCount, getCompensateCount(ValidLRACSParticipant.ROOT_PATH)); + + replayEndPhase(ValidLRACSParticipant.ROOT_PATH); + + assertEquals("Non JAX-RS @Status method with CompletionStage should have been called", + beforeStatusCount + 1, getStatusCount(ValidLRACSParticipant.ROOT_PATH)); + } + + private int getCompleteCount() { + return getCompleteCount(ValidLRAParticipant.RESOURCE_PATH); + } + + private int getCompensateCount() { + return getCompensateCount(ValidLRAParticipant.RESOURCE_PATH); + } + + private int getStatusCount() { + return getStatusCount(ValidLRAParticipant.RESOURCE_PATH); + } + + private int getForgetCount() { + Response response = tckSuiteTarget.path(ValidLRAParticipant.RESOURCE_PATH) + .path(ValidLRAParticipant.FORGET_COUNT_PATH).request().get(); + return response.readEntity(Integer.class); + } + + private int getCompleteCount(String path) { + Response response = tckSuiteTarget.path(path) + .path(ValidLRAParticipant.COMPLETED_COUNT_PATH).request().get(); + return response.readEntity(Integer.class); + } + + private int getCompensateCount(String path) { + Response response = tckSuiteTarget.path(path) + .path(ValidLRAParticipant.COMPENSATED_COUNT_PATH).request().get(); + return response.readEntity(Integer.class); + } + + private int getStatusCount(String path) { + Response response = tckSuiteTarget.path(path) + .path(ValidLRAParticipant.STATUS_COUNT_PATH).request().get(); + return response.readEntity(Integer.class); + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java index 8195a290..00c061b1 100644 --- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTestBase.java @@ -20,6 +20,8 @@ package org.eclipse.microprofile.lra.tck; import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; +import org.eclipse.microprofile.lra.tck.participant.activity.Activity; +import org.eclipse.microprofile.lra.tck.participant.api.LraController; import org.eclipse.microprofile.lra.tck.participant.api.Util; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; @@ -64,7 +66,9 @@ public class TckTestBase { static WebArchive deploy(String archiveName) { return ShrinkWrap .create(WebArchive.class, archiveName + ".war") - .addPackages(true, "org.eclipse.microprofile.lra.tck") + .addPackages(false, TckTestBase.class.getPackage(), + Activity.class.getPackage(), + LraController.class.getPackage()) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); } diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidArgumentTypesParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidArgumentTypesParticipant.java new file mode 100644 index 00000000..af01f485 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidArgumentTypesParticipant.java @@ -0,0 +1,56 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck.participant.nonjaxrs; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.Forget; +import org.eclipse.microprofile.lra.annotation.ParticipantStatus; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import java.net.URI; + +/** + * TCK invalid LRA participant containing invalid type of arguments in the participant method signature + * used for verification of deployment time invalid signature detection and error report in + * {@link org.eclipse.microprofile.lra.tck.TckInvalidParticipantSignaturesTests} + */ +@Path("nonjaxrs-argument-type-nonjaxrs") +public class InvalidArgumentTypesParticipant { + + @GET + @Path("enlist") + @LRA(LRA.Type.REQUIRED) + public Response doInLRA() { + return Response.ok().build(); + } + + @Compensate + public ParticipantStatus compensate(URI lraId) { + return ParticipantStatus.Compensated; + } + + @Forget + public void forget(int lraId, URI parentId) { + // intentionally empty + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidReturnTypeParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidReturnTypeParticipant.java new file mode 100644 index 00000000..dc1608d7 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/InvalidReturnTypeParticipant.java @@ -0,0 +1,48 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck.participant.nonjaxrs; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +/** + * TCK invalid LRA participant containing invalid return type in the participant method signature + * used for verification of deployment time invalid signature detection and error report in + * {@link org.eclipse.microprofile.lra.tck.TckInvalidParticipantSignaturesTests} + */ +@Path("nonjaxrs-return-type") +public class InvalidReturnTypeParticipant { + + @GET + @Path("enlist") + @LRA(LRA.Type.REQUIRED) + public Response doInLRA() { + return Response.ok().build(); + } + + @Compensate + public String compensate() { + return "compensated"; + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/TooManyArgsParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/TooManyArgsParticipant.java new file mode 100644 index 00000000..9759d743 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/TooManyArgsParticipant.java @@ -0,0 +1,56 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck.participant.nonjaxrs; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.Complete; +import org.eclipse.microprofile.lra.annotation.ParticipantStatus; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import java.net.URI; + +/** + * TCK invalid LRA participant containing too many arguments in the participant method signature + * used for verification of deployment time invalid signature detection and error report in + * {@link org.eclipse.microprofile.lra.tck.TckInvalidParticipantSignaturesTests} + */ +@Path("too-many-args-nonjaxrs") +public class TooManyArgsParticipant { + + @GET + @Path("enlist") + @LRA(LRA.Type.REQUIRED) + public Response doInLRA() { + return Response.ok().build(); + } + + @Compensate + public ParticipantStatus compensate(URI lraId) { + return ParticipantStatus.Compensated; + } + + @Complete + public ParticipantStatus complete(URI lraId, URI parentId, Object additional) { + return ParticipantStatus.Completed; + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRACSParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRACSParticipant.java new file mode 100644 index 00000000..3b5209d5 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRACSParticipant.java @@ -0,0 +1,160 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.Complete; +import org.eclipse.microprofile.lra.annotation.ParticipantStatus; +import org.eclipse.microprofile.lra.annotation.Status; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.ACCEPT_PATH; +import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.RECOVERY_PARAM; +import static org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid.ValidLRAParticipant.COMPENSATED_COUNT_PATH; +import static org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid.ValidLRAParticipant.COMPLETED_COUNT_PATH; +import static org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid.ValidLRAParticipant.STATUS_COUNT_PATH; + +/** + * Valid participant resource containing async non-JAX-RS participant methods with + * {@link CompletionStage} return types + */ +@ApplicationScoped +@Path(ValidLRACSParticipant.ROOT_PATH) +public class ValidLRACSParticipant { + + public static final String ROOT_PATH = "valid-cs-participant1"; + public static final String ENLIST_WITH_COMPLETE = "enlist-complete"; + public static final String ENLIST_WITH_COMPENSATE = "enlist-compensate"; + + private final AtomicInteger completedCount = new AtomicInteger(0); + private final AtomicInteger compensatedCount = new AtomicInteger(0); + private final AtomicInteger statusCount = new AtomicInteger(0); + + private int recoveryPasses; + + @GET + @Path(ENLIST_WITH_COMPLETE) + @LRA(value = LRA.Type.REQUIRED) + public Response enlistWithComplete(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) { + return Response.ok().build(); + } + + @GET + @Path(ENLIST_WITH_COMPENSATE) + @LRA(value = LRA.Type.REQUIRED, cancelOn = Response.Status.INTERNAL_SERVER_ERROR) + public Response enlistWithCompensate(@HeaderParam(LRA.LRA_HTTP_CONTEXT_HEADER) String lraId) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + @Compensate + public CompletionStage compensate(URI lraId) { + assert lraId != null; + + return CompletableFuture.runAsync(() -> { + compensatedCount.incrementAndGet(); + + simulateLongRunningCompensation(); + }); + } + + @Complete + public CompletionStage complete(URI lraId) { + assert lraId != null; + + return CompletableFuture.supplyAsync(() -> { + completedCount.incrementAndGet(); + + simulateLongRunningCompensation(); + return Response.accepted().build(); // Completing + }); + } + + @Status + public CompletionStage status(URI lraId) { + assert lraId != null; + + return CompletableFuture.supplyAsync(() -> { + statusCount.incrementAndGet(); + + simulateLongRunningCompensation(); + return ParticipantStatus.Completed; + }); + } + + private void simulateLongRunningCompensation() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @GET + @Path(COMPLETED_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response completed() { + return Response.ok(completedCount.toString()).build(); + } + + @GET + @Path(COMPENSATED_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response compensated() { + return Response.ok(compensatedCount.toString()).build(); + } + + @GET + @Path(STATUS_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response statusCount() { + return Response.ok(statusCount.toString()).build(); + } + + @PUT + @Path(ACCEPT_PATH) + @LRA(value = LRA.Type.REQUIRES_NEW) + public Response acceptLRA(@QueryParam(RECOVERY_PARAM) @DefaultValue("0") Integer recoveryPasses) { + this.recoveryPasses = recoveryPasses; + + return Response.ok().build(); + } + + @GET + @Path(ACCEPT_PATH) + public Response getAcceptLRA() { + return Response.ok(this.recoveryPasses).build(); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRAParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRAParticipant.java new file mode 100644 index 00000000..fd3a1f8c --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/nonjaxrs/valid/ValidLRAParticipant.java @@ -0,0 +1,173 @@ +/* + ******************************************************************************* + * Copyright (c) 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.eclipse.microprofile.lra.tck.participant.nonjaxrs.valid; + +import org.eclipse.microprofile.lra.annotation.Compensate; +import org.eclipse.microprofile.lra.annotation.Complete; +import org.eclipse.microprofile.lra.annotation.Forget; +import org.eclipse.microprofile.lra.annotation.ParticipantStatus; +import org.eclipse.microprofile.lra.annotation.Status; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA.Type; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.ACCEPT_PATH; +import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.RECOVERY_PARAM; + +/** + * TCK valid LRA participant containing a combination of valid participant method signatures + */ +@ApplicationScoped +@Path(ValidLRAParticipant.RESOURCE_PATH) +public class ValidLRAParticipant { + private static final Logger LOGGER = Logger.getLogger(ValidLRAParticipant.class.getName()); + + public static final String RESOURCE_PATH = "valid-nonjaxrs"; + public static final String COMPLETED_COUNT_PATH = "completed"; + public static final String COMPENSATED_COUNT_PATH = "compensated"; + public static final String STATUS_COUNT_PATH = "status"; + public static final String FORGET_COUNT_PATH = "forget"; + + private final AtomicInteger completedCount = new AtomicInteger(0); + private final AtomicInteger compensatedCount = new AtomicInteger(0); + private final AtomicInteger statusCount = new AtomicInteger(0); + private final AtomicInteger forgetCount = new AtomicInteger(0); + + public static final String ENLIST_WITH_COMPLETE = "nonjaxrs-enlist-complete"; + private int recoveryPasses; + + @GET + @Path(ENLIST_WITH_COMPLETE) + @LRA(value = Type.REQUIRED) + public Response enlistWithComplete() { + return Response.ok().build(); + } + + public static final String ENLIST_WITH_COMPENSATE = "nonjaxrs-enlist-compensate"; + + @GET + @Path(ENLIST_WITH_COMPENSATE) + @LRA(value = Type.REQUIRED, cancelOn = Response.Status.INTERNAL_SERVER_ERROR) + public Response enlistWithCompensate() { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + + @Complete + public void completeWithException(URI lraId, URI parentId) { + verifyLRAId(lraId); + + completedCount.incrementAndGet(); + + LOGGER.fine(String.format("LRA id '%s' was completed", lraId)); + throw new WebApplicationException(Response.ok().build()); + } + + @Compensate + public ParticipantStatus compensate(URI lraId) { + verifyLRAId(lraId); + + compensatedCount.incrementAndGet(); + + LOGGER.fine(String.format("LRA id '%s' was compensated", lraId)); + return ParticipantStatus.Compensating; + } + + @Status + public Response status(URI lraId) { + verifyLRAId(lraId); + + statusCount.incrementAndGet(); + + LOGGER.fine(String.format("LRA id '%s' status called, return FailedToCompensate to get @Forget called", lraId)); + return Response.ok(ParticipantStatus.FailedToCompensate).build(); + } + + @Forget + public void forget(URI lraId) { + verifyLRAId(lraId); + + forgetCount.incrementAndGet(); + + LOGGER.fine(String.format("LRA id '%s' forget called", lraId)); + } + + private void verifyLRAId(URI lraId) { + if (lraId == null) { + throw new NullPointerException("lraId cannot be null"); + } + } + + @GET + @Path(COMPLETED_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response completed() { + return Response.ok(completedCount.toString()).build(); + } + + @GET + @Path(COMPENSATED_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response compensated() { + return Response.ok(compensatedCount.toString()).build(); + } + + @GET + @Path(STATUS_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response statusCount() { + return Response.ok(statusCount.toString()).build(); + } + + @GET + @Path(FORGET_COUNT_PATH) + @Produces(MediaType.TEXT_PLAIN) + public Response forgetCount() { + return Response.ok(forgetCount.toString()).build(); + } + + @PUT + @Path(ACCEPT_PATH) + @LRA(value = LRA.Type.REQUIRES_NEW) + public Response acceptLRA(@QueryParam(RECOVERY_PARAM) @DefaultValue("0") Integer recoveryPasses) { + this.recoveryPasses = recoveryPasses; + + return Response.ok().build(); + } + + @GET + @Path(ACCEPT_PATH) + public Response getAcceptLRA() { + return Response.ok(this.recoveryPasses).build(); + } +}