Repository: eclipse/microprofile-lra
Pull request #174 (2019-06-24 19:18:52)

issue164 Support notifications that an LRA has finished
#164 

If a resource is invoked in the context of an LRA and it contains a JAX-RS resource method annotated with `@AfterEnd` then it will be notified when the associated LRA is finished (in any of the terminal states).

mmusgrov: I deliberately excluded non JAX-RS methods. This feature is orthogonal to participants. I have just added an extra test to make that explicit.
rdebusscher: The @AfterEnd is to 'send' the final state of the LRA to the Participant, correct? Otherwise, I'm obvious not seeing the use case/scenario then. 

xstefank: > I deliberately excluded non JAX-RS methods. This feature is orthogonal to participants. I have just added an extra test to make that explicit.

Can you elaborate on this please? I would think of this functionality as of again something that is called by coordination (like complete, compensate, forget, status) not like something to be called by users (like LRA, Leave)
mmusgrov: > The @AfterEnd is to 'send' the final state of the LRA to the Participant, correct? Otherwise, I'm obvious not seeing the use case/scenario then.

The specification was motivated by the desire to enable applications to organise work in some kind of business process where the completion of some activities triggers the subsequent ones, to quote our specification:

> Different usage patterns for LRAs are possible, for example LRAs may be used sequentially and/or
concurrently, where the termination of one LRA signals the start of some other unit of work within
an application. ...

And an actual, real world, use case is our own TCK.
tomjenkinson: Also, I think we should add adoc changes in this PR too, it would help with my earlier question
xstefank: @tomjenkinson maybe this helps https://github.com/eclipse/microprofile-lra/pull/174/files#diff-c196906fd09ae1e04e3c9d20d737ac86R67 ?

@mmusgrov I just noticed if endStatus (or the String response body) is supposed to be LRAStatus how does the participant know whether LRA is in FailedToCompensate because of the timeout or because some other participant failed. Wasn't this the original intent?
tomjenkinson: @xstefank it does help - thanks!
tomjenkinson: @xstefank that is a good catch on the timeout side. Would it be another state in LRAStatus? I am not sure where the idea for needing to know the timeout came from though so I will add this on the issue #164 
mmusgrov: > Also, I think we should add adoc changes in this PR too, it would help with my earlier question

If the general feeling is that this PR is a useful contribution then I will go ahead and update the asciidoc 
mmusgrov: > @tomjenkinson maybe this helps https://github.com/eclipse/microprofile-lra/pull/174/files#diff-c196906fd09ae1e04e3c9d20d737ac86R67 ?
> 
> @mmusgrov I just noticed, if endStatus (or the String response body) is supposed to be LRAStatus how does the participant know whether LRA is in FailedToCompensate because of the timeout or because some other participant failed. Wasn't this the original intent?

Personally, I think just knowing the end state is sufficient but if people want me to report a more complex data structure in the `EndLRA` notification then I can add/propose one. Is that the general consensus?
rdebusscher: @xstefank  How is the time-out of the LRA linked to the `FailedToCancel` (as `FailedToCompensate` is a participant status) of the LRA? 

A timed-out resource can also end with `Compensated`.  

We have no idea why we have the LRA status `FailedToCancel` since we just have the return `FailedToCompensate` but no reason why the participant returned this.




xstefank: > @xstefank How is the time-out of the LRA linked to the `FailedToCancel` (as `FailedToCompensate` is a participant status) of the LRA?

My bad, typing too fast. I am talking about LRA of course, sorry.

I was talking about this conversation - https://groups.google.com/forum/#!searchin/microprofile/LRA%7Csort:date/microprofile/fdrcpKPYn0w/WONAPNDsAgAJ

`Is there a way for the compensation method to find out, if it was called because of a timeout or any other error?`


mmusgrov: > Additional remarks on top of the missing support for non-JAX-RS.

@rdebusscher I already responded to your question about the non-JAX-RS method:

> The @AfterEnd is to 'send' the final state of the LRA to the Participant, correct? Otherwise, I'm obvious not seeing the use case/scenario then.

The specification was motivated by the desire to enable applications to organise work in some kind of business process where the completion of some activities triggers the subsequent ones, to quote our specification:

> Different usage patterns for LRAs are possible, for example LRAs may be used sequentially and/or
concurrently, where the termination of one LRA signals the start of some other unit of work within
an application. ...

And an actual, real world, use case is our own TCK.

So a typical use case would be:

A target use case is to trigger  new actions when a previous one has finished. Therefore we should support something like:

```java
    @PUT
    @Path(AFTER_LRA)
    @AfterLRA // this method will be called when the LRA associated with the method activityWithLRA finishes
    @LRA(value = LRA.Type.REQUIRES_NEW)
    public Response afterEnd(
        @HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI endedContext,
        @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI newContext,
        String endStatus) {
```
rdebusscher: @mmusgrov I agree that it can be important to get feedback that the LRA is finished to get something else started.

But by limiting it to JAX-RS you are excluding implementations which do not use JAX-RS as communication to participants. And makes it essentially a layer on top of Narayana.
rdebusscher: All we need to do, is define in the spec that the following format is allowed for the method since we have already the proxy participant which handles the callbacks for @Compensate and @Complete and can handle it for this call also.

    @AfterLRA
    public void onLRAEnd(URI lraId, LRAStatus status) {
xstefank: I agree with Rudy. I asked in this PR already why it should be excluded from non-JAX-RS participants - https://github.com/eclipse/microprofile-lra/pull/174#issuecomment-505297539
mmusgrov: > @mmusgrov I agree that it can be important to get feedback that the LRA is finished to get something else started.
> 
> But by limiting it to JAX-RS you are excluding implementations which do not use JAX-RS as communication to participants. And makes it essentially a layer on top of Narayana.

If you have a use case I will update the PR to include the non JAX-RS case.
tomjenkinson: Re:JAX-RS, personally I think this is not the same same level as participant API (like `@Compensate` and `@Complete`) it is closer to the `@LRA` annotation as it relates to notifications for the business logic
xstefank: > If you have a use case I will update the PR to include the non JAX-RS case and use it in the spec doc as justification.

Any JAX-RS use case with JAX-RS annotations removed on all methods except for the `@LRA` method.

> Re:JAX-RS, personally I think this is not the same same level as participant API (like `@Compensate` 
and `@Complete`) it is closer to the `@LRA` annotation as it relates to notifications for the business logic

The `@AfterLRA` method is called in the same way as other participant methods (from coordinator) so I cannot really see why it should not be present in non JAX-RS case. Cannot we say that even Compensate, Complete are also notifications for the business logic? After all, they are saying that participant should do something with performed business action -- either perform "undo" business action or clean up performed business action which itself can be also business action again (e.g. order confirmation).
rdebusscher: @mmusgrov Same case as for the non-JAX-RS case

- LRA ends (with whatever final state - completed, compensated, failedtoCompensate, ...)
- System calls the method annotated with @AfterLRA
- Developer/Business can launch any task/statement/call in response to the end of the LRA. (which can be a call to some endpoints which starts again another LRA)

For example

When Completed/cancelled, Call another service which needs to be informed of the success/failure of the Long Running Action

When FailedToCompensate, perform any required cleanup/consolidation/... (since some participants have completed successful but others didn't)


ochaloup: I agree here with the Rudy's and Martin's point of view. The `@AfterLRA` is about to be called at the participant. Based on the call the participant decides what is should be done as the next steps in its business processing. And it's the participant that has to expose the endpoint for being called by the coordinator. It should be the framework that simplify this exposure whenever it's possible.
tomjenkinson: Thanks for the feedback. It might help for me to explain the way I look at this.

I see this broadly similar to discussion about a synchronization and how it is distinct from a resource.  For instance, please compare [Synchronization](https://docs.oracle.com/javaee/7/api/javax/transaction/Synchronization.html) and [XAResource](https://docs.oracle.com/javase/8/docs/api/javax/transaction/xa/XAResource.html) They are different actors in the system. 

I realise my example draws from enterprise Java but please do bear with me and if I can further clarify please allow me to do so. Within a typical enterprise Java setting you would probably expect the participant (say the database or whatever) to be registered with the transaction differently to how an interested party (such as a JPA implementation or whatever).

Here is another way to look at this. I would expect the following use case to be applicable.

Service A is annoted with @LRA and and @AfterLRA but not expected to be a participant and calls Service B which is.

Note that in order for that to be valid, some of the wording here about suspend would probably need to be edited: https://github.com/eclipse/microprofile-lra/blob/master/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java#L73
xstefank: @tomjenkinson thank you, that was very helpful. I can understand your view on this now. 
 
> Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.

So you want to allow non-participant LRA resources (which do not have `@Compensate` method) to be still able to notified about LRA end. However, that would mean that LRA implemetation will have to somehow manage the link between the LRA and the class where the LRA was started/finished if this class had `AfterLRA` method which basically makes it a kind of participant right? It just enlists with `@AfterLRA` and not with `@Compensate`.

mmusgrov: > @tomjenkinson thank you, that was very helpful. I can understand your view on this now.
> 
> > Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.
> 
> So you want to allow non-participant LRA resources (which do not have `@Compensate` method) to be still able to notified about LRA end. However, that would mean that LRA implemetation will have to somehow manage the link between the LRA and the class where the LRA was started/finished if this class had `AfterLRA` method which basically makes it a kind of participant right? It just enlists with `@AfterLRA` and not with `@Compensate`.

There is an example in the PR 

> Thanks for the feedback. It might help for me to explain the way I look at this.
> 
> I see this broadly similar to discussion about a synchronization and how it is distinct from a resource. For instance, please compare [Synchronization](https://docs.oracle.com/javaee/7/api/javax/transaction/Synchronization.html) and [XAResource](https://docs.oracle.com/javase/8/docs/api/javax/transaction/xa/XAResource.html) They are different actors in the system.
> 
> I realise my example draws from enterprise Java but please do bear with me and if I can further clarify please allow me to do so. Within a typical enterprise Java setting you would probably expect the participant (say the database or whatever) to be registered with the transaction differently to how an interested party (such as a JPA implementation or whatever).
> 
> Here is another way to look at this. I would expect the following use case to be applicable.
> 
> Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.
> 
> Note that in order for that to be valid, some of the wording here about suspend would probably need to be edited: https://github.com/eclipse/microprofile-lra/blob/master/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java#L73

The PR does already include an example where there is no participant involved (look for the class AfterLRAListener):

```java
@ApplicationScoped
@Path(AfterLRAListener.AFTER_LRA_LISTENER_PATH)
public class AfterLRAListener {
    public static final String AFTER_LRA_LISTENER_PATH = "after-lra-listener";
    public static final String WORK = "work";

    private static final String AFTER_LRA = "/after";

    @Inject
    private LRAMetricService lraMetricService;

    @PUT
    @Path(WORK)
    @LRA(value = LRA.Type.REQUIRED, end = false)
    public Response activityWithLRA(
            @HeaderParam(LRA_HTTP_RECOVERY_HEADER) URI recoveryId,
            @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
        return Response.ok().build();
    }

    @PUT
    @Path(AFTER_LRA)
    @AfterLRA // this method will be called when the LRA associated with the method activityWithLRA finishes
    public Response afterEnd(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId, String endStatus) {
        LRAStatus status = LRAStatus.valueOf(endStatus);

        if (status.equals(LRAStatus.Closed)) {
            lraMetricService.incrementMetric(LRAMetricType.Closed, lraId,
                    AfterLRAListener.class.getName());
        } else if (status.equals(LRAStatus.Cancelled)) {
            lraMetricService.incrementMetric(LRAMetricType.Cancelled, lraId,
                    AfterLRAListener.class.getName());
        } else if (status.equals(LRAStatus.FailedToCancel)) {
            lraMetricService.incrementMetric(LRAMetricType.FailedToCancel,
                    lraId, AfterLRAListener.class.getName());
        } else if (status.equals(LRAStatus.FailedToClose)) {
            lraMetricService.incrementMetric(LRAMetricType.FailedToClose,
                    lraId, AfterLRAListener.class.getName());
        }

        return Response.ok().build();
  
```
tomjenkinson: > 
> 
> > Thanks for the feedback. It might help for me to explain the way I look at this.
> > I see this broadly similar to discussion about a synchronization and how it is distinct from a resource. For instance, please compare [Synchronization](https://docs.oracle.com/javaee/7/api/javax/transaction/Synchronization.html) and [XAResource](https://docs.oracle.com/javase/8/docs/api/javax/transaction/xa/XAResource.html) They are different actors in the system.
> > I realise my example draws from enterprise Java but please do bear with me and if I can further clarify please allow me to do so. Within a typical enterprise Java setting you would probably expect the participant (say the database or whatever) to be registered with the transaction differently to how an interested party (such as a JPA implementation or whatever).
> > Here is another way to look at this. I would expect the following use case to be applicable.
> > Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.
> > Note that in order for that to be valid, some of the wording here about suspend would probably need to be edited: https://github.com/eclipse/microprofile-lra/blob/master/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java#L73
> 
> The PR does already include an example where there is no participant involved (look for the class AfterLRAListener):
> 
> ```java
> @ApplicationScoped
> @Path(AfterLRAListener.AFTER_LRA_LISTENER_PATH)
> public class AfterLRAListener {
>     public static final String AFTER_LRA_LISTENER_PATH = "after-lra-listener";
>     public static final String WORK = "work";
> 
>     private static final String AFTER_LRA = "/after";
> 
>     @Inject
>     private LRAMetricService lraMetricService;
> 
>     @PUT
>     @Path(WORK)
>     @LRA(value = LRA.Type.REQUIRED, end = false)
>     public Response activityWithLRA(
>             @HeaderParam(LRA_HTTP_RECOVERY_HEADER) URI recoveryId,
>             @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
>         return Response.ok().build();
>     }
> 
>     @PUT
>     @Path(AFTER_LRA)
>     @AfterLRA // this method will be called when the LRA associated with the method activityWithLRA finishes
>     public Response afterEnd(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId, String endStatus) {
>         LRAStatus status = LRAStatus.valueOf(endStatus);
> 
>         if (status.equals(LRAStatus.Closed)) {
>             lraMetricService.incrementMetric(LRAMetricType.Closed, lraId,
>                     AfterLRAListener.class.getName());
>         } else if (status.equals(LRAStatus.Cancelled)) {
>             lraMetricService.incrementMetric(LRAMetricType.Cancelled, lraId,
>                     AfterLRAListener.class.getName());
>         } else if (status.equals(LRAStatus.FailedToCancel)) {
>             lraMetricService.incrementMetric(LRAMetricType.FailedToCancel,
>                     lraId, AfterLRAListener.class.getName());
>         } else if (status.equals(LRAStatus.FailedToClose)) {
>             lraMetricService.incrementMetric(LRAMetricType.FailedToClose,
>                     lraId, AfterLRAListener.class.getName());
>         }
> 
>         return Response.ok().build();
>   
> ```

I was expressing a suggestion that the wording around suspend in that `@LRA` annotation might want to be looked at in the context of this new functionality
tomjenkinson: > 
> 
> @tomjenkinson thank you, that was very helpful. I can understand your view on this now.
> 
> > Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.
> 
> So you want to allow non-participant LRA resources (which do not have `@Compensate` method) to be still able to notified about LRA end. However, that would mean that LRA implemetation will have to somehow manage the link between the LRA and the class where the LRA was started/finished if this class had `AfterLRA` method which basically makes it a kind of participant right? It just enlists with `@AfterLRA` and not with `@Compensate`.

Thanks for the response @xstefank, I am pleased I was able to clarify my intention. How we term them (a new type of participant or not) I was trying to disambiguate it from the current definition of a participant in the specification as they serve a different purpose, at least as how this PR is headed (which may of course not be the way we want to go with the underlying issue #164 
xstefank: If LRA is started in one non-participant resource but closed in another non-participant resource, both having `@AfterLRA` methods will both of them get called? 

@mmusgrov @tomjenkinson thank you, I got it now. But sorry I am still not convinced that this cannot be also in non JAX-RS variant. When `@LRA` method is hit, all of the resource methods needs to be checked for annotations -- and this will just mean check not only for Compensate but also for AfterLRA and if there is any hit in non JAX-RS method just expose proxy. In same sense we need to expose proxy now if there is only `@LRA` and non JAX-RS `@Compensate`. Would this be too complicated?
mmusgrov: > @tomjenkinson thank you, that was very helpful. I can understand your view on this now.
> 
> > Service A is annoted with `@LRA` and and `@AfterLRA` but not expected to be a participant and calls Service B which is.
> 
> So you want to allow non-participant LRA resources (which do not have `@Compensate` method) to be still able to notified about LRA end. However, that would mean that LRA implemetation will have to somehow manage the link between the LRA and the class where the LRA was started/finished if this class had `AfterLRA` method which basically makes it a kind of participant right? It just enlists with `@AfterLRA` and not with `@Compensate`.

Two remarks:

- the issue title deliberately uses the more general term `party` in favour of `participant`
- the PR contains an example resource that is not a participant
mmusgrov: > If LRA is started in one non-participant resource but closed in another non-participant resource, both having `@AfterLRA` methods will both of them get called?
> 

yes (the annotation applies whenever the same class has a method that is executed in the context of an LRA)

> @mmusgrov @tomjenkinson thank you, I got it now. But sorry I am still not convinced that this cannot be also in non JAX-RS variant. When `@LRA` method is hit, all of the resource methods needs to be checked for annotations -- and this will just mean check not only for Compensate but also for AfterLRA and if there is any hit in non JAX-RS method just expose proxy. In same sense we need to expose proxy now if there is only `@LRA` and non JAX-RS `@Compensate`. Would this be too complicated?

It is easy to implement, but that is not the point (which is use cases). I think the vote is 2-2 with arguments on both sides. @ochaloup would have swung the vote in your favour (3-2) but his argument is based on the fact that it only applies to participants - but I am sure he can rephrase his argument. @tomjenkinson unfortunately I think we may be outnumbered on this one.

rdebusscher: The method annotated with @AfterLRA doesn't need to be a participant, I can agree on that.  What we **also** need is the non-JAX-RS variant.

We even can support than the use case where we have a @AfterLRA annotated method in a plain CDI bean.
It can be used to collect **any** failed LRA for example.
ochaloup: The Tom's point about the distinction on the XAResource and Synchronization and comparing it with the LRA approach is great. I haven't realized this. It's good to have a chance to have `@AfterLRA` to be on non-participant class.

I still think of having both variants - JAX-RS and non-JAX-RS - make much sense. The LRA framework just makes the user to define the endpoint or manage creation of the endpoint in the background. The ease of the plain CDI usage sounds pretty good to me.
tomjenkinson: I think if there are questions about whether it should be JAX-RS or non-JAX-RS those should be on the issue, if people are happy with that let's move that discussion over to https://github.com/eclipse/microprofile-lra/issues/164?
mmusgrov: > The method annotated with @AfterLRA doesn't need to be within a participant, I can agree on that. What we **also** need is the non-JAX-RS variant.
> 
> We even can support than the use case where we have a @AfterLRA annotated method in a plain CDI bean.
> It can be used to collect **any** failed LRA for example.

The lifecycle of an LRA is controlled by invoking JAX-RS methods annotated with `@LRA` so the notification is only generated for the classes that contain such methods and only if they were invoked. 

Is that what you mean?

mmusgrov: There has been a lot of discussion on this issue but from my perspective and I have addressed everybody's issues. @tomjenkinson @rdebusscher @xstefank @ochaloup are there any further problems with this PR that you would like me to look into?
tomjenkinson: -1 to merging this until my comment above is resolved.

The text:
"If the
signature does not conform to this requirement then the behaviour of the implementation
is unspecified."

Is inconsistent with the approach in https://github.com/eclipse/microprofile-lra/pull/182/files

/cc @mmusgrov @rdebusscher @xstefank 
xstefank: @tomjenkinson if this gets merged before #182 I will update #182 accordingly :)

Patch:
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/AfterLRA.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/AfterLRA.java
new file mode 100644
index 0000000..e9f7ab2
--- /dev/null
+++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/AfterLRA.java
@@ -0,0 +1,82 @@
+/*
+ *******************************************************************************
+ * 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.annotation;
+
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * If a JAX-RS resource method is annotated with
+ * {@link LRA} and is invoked in the context of an LRA then
+ * the resource can ask to be notified when the LRA finishes
+ * by marking one of the other methods in the class with
+ * the {@link AfterLRA} annotation.
+ * </p>
+ *
+ * <p>
+ * If the <code>AfterLRA</code> method is also a JAX-RS resource method
+ * then the LRA context is made available to the annotated method
+ * via an HTTP header with the name
+ * {@link LRA#LRA_HTTP_ENDED_CONTEXT_HEADER} and the
+ * final status is passed to the method as plain text
+ * corresponding to one of the {@link LRAStatus} enum values.
+ * For example:
+ * <pre>
+ *     <code>
+ * @PUT
+ * @AfterLRA
+ * public Response afterEnd(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId,
+ *                          LRAStatus status)
+ *     </code>
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * The implementation will keep resending the notification
+ * until it receives a <code>200 OK</code> status code from the
+ * resource method (which means that the method SHOULD be
+ * idempotent).
+ * </p>
+ *
+ * <p>
+ * If the <code>AfterLRA</code> method is not a JAX-RS resource method
+ * then the id of the LRA and its final status can be obtained
+ * by ensuring that the annotated method conforms to the
+ * signature:
+ *
+ * <code>
+ *     public void onLRAEnd(URI lraId, LRAStatus status)
+ * </code>
+ *
+ * </p>
+ * <p>
+ * The return type is ignored and the method name is not
+ * significant.
+ * </p>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface AfterLRA {
+}
diff --git a/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java b/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
index e7c06b6..d070039 100644
--- a/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
+++ b/api/src/main/java/org/eclipse/microprofile/lra/annotation/ws/rs/LRA.java
@@ -23,6 +23,7 @@ package org.eclipse.microprofile.lra.annotation.ws.rs;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.core.Response;
 
+import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.annotation.Compensate;
 import org.eclipse.microprofile.lra.annotation.Complete;
 import org.eclipse.microprofile.lra.annotation.Forget;
@@ -101,6 +102,12 @@ public @interface LRA {
      */
     String LRA_HTTP_CONTEXT_HEADER = "Long-Running-Action";
 
+    /**
+     * Header name holding the LRA context of an LRA that has finished - to be
+     * used in conjunction with the {@link AfterLRA} annotation.
+     */
+    String LRA_HTTP_ENDED_CONTEXT_HEADER = "Long-Running-Action-Ended";
+
     /**
      * When a JAX-RS invocation is made with an active LRA which is nested,
      * the parent LRA is made available via an HTTP header field with the
@@ -135,6 +142,12 @@ public @interface LRA {
      * </p>
      *
      * <p>
+     *     If the method is to run in the context of an LRA and the annotated class
+     *     also contains a method annotated with {@link AfterLRA}
+     *     then the resource will be notified of the final state of the LRA.
+     * </p>
+     *
+     * <p>
      *     The element values {@link LRA.Type#REQUIRED} and
      *     {@link LRA.Type#REQUIRES_NEW} can start new LRAs.
      * </p>
diff --git a/spec/src/main/asciidoc/microprofile-lra-spec.adoc b/spec/src/main/asciidoc/microprofile-lra-spec.adoc
index ab524ba..4f85c70 100644
--- a/spec/src/main/asciidoc/microprofile-lra-spec.adoc
+++ b/spec/src/main/asciidoc/microprofile-lra-spec.adoc
@@ -501,6 +501,52 @@ The `end = false` element on the bookTrip method forces the LRA to continue runn
 the method finishes and the `end = true` element on the confirmTrip method forces the LRA
 (started by the bookTrip method) to close the LRA.
 
+[[after-lra]]
+===== Discovering the Outcome of an LRA
+
+As remarked in the previous section, a JAX-RS resource method runs with an active
+context depending upon the value specified in the `@LRA` annotation.
+The final state of this LRA can be discovered by marking one of the other methods
+in the class with the `@AfterLRA` annotation. When the LRA enters a terminal state
+the method will be passed the id of the LRA together with the `LRAStatus` (see the
+javadoc for the `@AfterLRA` annotation for more information). Note that the final
+states of an LRA is defined by <<lra-state-model,the LRA state model>> and it
+would be a specification violation if the implementation calls the method when the
+LRA is not in a final state. Further information about method signatures and
+JAX-RS response codes is given below in section <<jaxrs-participant-methods>>.
+
+Note that the resource does not need to be a participant in order to receive this
+notification. Therefore in the following resource definition, although no method
+is annotated with `@Compensate`, if the method called
+`activityWithLRA` is invoked then the method `notifyLRAFinished` will be called
+when the LRA finishes:
+
+[source,java]
+----
+public class BusinessResource {
+    @PUT
+    @Path("/work")
+    @LRA(value = LRA.Type.REQUIRES_NEW)
+    public Response activityWithLRA(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
+        // perform a business action in the context of lraId
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Path("/after")
+    @AfterLRA // method is called when the LRA associated with the method activityWithLRA finishes
+    public Response notifyLRAFinished(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId,
+                                      LRAStatus status) {
+
+        switch (status) {
+            case Closed:
+                // the LRA was successfully closed
+            ...    
+        }
+    }
+}
+----
+
 [[compensating-activities]]
 ==== Compensating Activities
 
@@ -545,7 +591,7 @@ If a compensating activity is brief then the synchronous model may be appropriat
 
 On the other hand, the compensating logic may involve concerted activities,
 perhaps even compensating in the context of another LRA. In this case the
-protocol accomodates a more decoupled mode of operation - the following example
+protocol accommodates a more decoupled mode of operation - the following example
 shows how a compensating activity can be started in the background:
 
 [source,java]
@@ -689,7 +735,7 @@ the participant state model>>. 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
+to a particular LRA
 * `@Forget` -- a method to be executed when the LRA allows participant to 
 clear all associated information
 
@@ -698,9 +744,9 @@ 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
+===== JAX-RS methods
 
-The following table presents expectations that are placed on individual participant 
+The following table presents expectations that are placed on individual
 annotations when associated with JAX-RS resource methods:
 
 [[jaxrs-response-table]]
@@ -727,22 +773,50 @@ annotations when associated with JAX-RS resource methods:
 | 200, 404, 412
 | no expectations
 
+| `@AfterLRA`
+| PUT
+| 200
+| no expectations
+
 |=== 
 
-The presented HTTP methods SHOULD be applied at individual JAX-RS participant methods 
+The presented HTTP methods SHOULD be applied at individual JAX-RS methods
 but they are not enforced by the specification. 
 
+If the method annotated with `@AfterLRA` returns an unexpected HTTP status
+or never reaches the caller then the implementation MUST invoke the same method again.
+
 If the annotated method returns an unexpected HTTP status code the implementation MAY
-invoke the same method again.
+invoke the same method again with the following caveat: if there is no `@Status` method
+and the implementation receives an unexpected response code from either of the
+`@Compensate` or `@Complete` invocations then it MUST reinvoke the method. [Note that
+this caveat applies to the situation where the response is lost since the caller will
+not see the correct code].
 
-Users are allowed to reuse existing JAX-RS endpoints for participant methods definitions. 
+Users are allowed to reuse existing JAX-RS endpoints for participant methods definitions.
 In this case, LRA implementation MUST ensure that invoking these methods outside of the 
 implementation of the LRA specification will not influence any running LRA.
 
+===== Non-JAX-RS afterLRA method
+
+A method annotated with `@AfterLRA` that is not a JAX-RS resource method MUST accept
+two arguments of type URI and LRAStatus, in that order. The first parameter holds
+an LRA context and the second parameter holds the final status of the LRA. If the
+signature does not conform to this requirement then the behaviour of the implementation
+is unspecified.
+
+An example of a valid signature is:
+
+[source,java]
+----
+@AfterLRA
+public void onLRAEnd(URI lraId, LRAStatus status)
+----
+
 [[non-jaxrs-participant-methods]]
 ===== Non-JAX-RS participant methods
 
-When the participant annotations are applied to the non-JAX-RS resource methods they 
+When the participant annotations are applied to the non-JAX-RS resource methods they
 MUST adhere to these predefined signatures:
 
 * *Return type*: 
@@ -1043,8 +1117,8 @@ participant compensation handlers to run:
 
         final CompletableFuture<Response> response = new CompletableFuture<>();
 
-        excecutorService.submit(() -> {
-            // excecute long running business activity finishing with a NOT_FOUND error
+        executorService.submit(() -> {
+            // execute long running business activity finishing with a NOT_FOUND error
             // which causes the LRA to cancel
             response.completeExceptionally(
                     new WebApplicationException(
@@ -1067,8 +1141,8 @@ the JAX-RS `@Suspended` annotation:
     @POST
     public void asyncResponseLRA(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                          final @Suspended AsyncResponse ar) {
-        excecutorService.submit(() -> {
-            // excecute long running business activity and resume when done
+        executorService.submit(() -> {
+            // execute long running business activity and resume when done
             ar.resume(Response.ok().entity(lraId).build());
         });
     }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
index 66e2f1f..b3f83bf 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/LRAClientOps.java
@@ -19,8 +19,11 @@
  *******************************************************************************/
 package org.eclipse.microprofile.lra.tck;
 
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
 import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
 import org.eclipse.microprofile.lra.tck.participant.api.GenericLRAException;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricType;
 
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.Invocation;
@@ -82,6 +85,42 @@ public class LRAClientOps {
         return status == Response.Status.NOT_FOUND.getStatusCode() || status == Response.Status.PRECONDITION_FAILED.getStatusCode();
     }
 
+    /**
+     * TODO if the current PR is acceptable then delete the old isLRAFinished method
+     * (which tests whether an LRA is active by making an attempt to enlist with it
+     * which some spec implementations report as a stack trace WARNING if the LRA is
+     * no longer active).
+     *
+     * @param lra the LRA to test
+     * @param lraMetricService metrics
+     * @param resourceName name of the resource that the metrics parameter applies to
+     * @return whether or not an LRA has finished
+     */
+    boolean isLRAFinished(URI lra, LRAMetricService lraMetricService, String resourceName) {
+        return getLRAEndStatus(lra, lraMetricService, resourceName) != null;
+    }
+
+    /**
+     *
+     * @param lra the LRA whose end status is being queried
+     * @param lraMetricService the metrics service
+     * @param resourceName the name of the resource whose metrics hold the end state of the LRA
+     * @return the end status of the LRA (or null if the LRA has not yet finished)
+     */
+    private LRAStatus getLRAEndStatus(URI lra, LRAMetricService lraMetricService, String resourceName) {
+        if (lraMetricService.getMetric(LRAMetricType.Closed, lra, resourceName) == 1) {
+            return LRAStatus.Closed;
+        } else if (lraMetricService.getMetric(LRAMetricType.FailedToClose, lra, resourceName) == 1) {
+            return LRAStatus.FailedToClose;
+        } else if (lraMetricService.getMetric(LRAMetricType.Cancelled, lra, resourceName) == 1) {
+            return LRAStatus.Cancelled;
+        } else if (lraMetricService.getMetric(LRAMetricType.FailedToCancel, lra, resourceName) == 1) {
+            return LRAStatus.FailedToCancel;
+        }
+
+        return null;
+    }
+
     // synchronize access to the connection since it is shared with the LRA background cancellation code
     private synchronized Response invokeRestEndpoint(URI lra, String basePath, String path, int coerceResponse) {
         WebTarget resourcePath = target.path(basePath).path(path).queryParam(STATUS_CODE_QUERY_NAME, coerceResponse);
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAListener.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAListener.java
new file mode 100644
index 0000000..5101516
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAListener.java
@@ -0,0 +1,85 @@
+/*
+ *******************************************************************************
+ * Copyright (c) 2019 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package org.eclipse.microprofile.lra.tck.participant.api;
+
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.AfterLRA;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricType;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+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_ENDED_CONTEXT_HEADER;
+import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER;
+
+/**
+ * resource for testing that methods annotated with
+ * {@link AfterLRA}
+ * are notified correctly when an LRA terminates
+ */
+@ApplicationScoped
+@Path(AfterLRAListener.AFTER_LRA_LISTENER_PATH)
+public class AfterLRAListener {
+    public static final String AFTER_LRA_LISTENER_PATH = "after-lra-listener";
+    public static final String AFTER_LRA_LISTENER_WORK = "work";
+
+    private static final String AFTER_LRA = "/after";
+
+    @Inject
+    private LRAMetricService lraMetricService;
+
+    @PUT
+    @Path(AFTER_LRA_LISTENER_WORK)
+    @LRA(value = LRA.Type.REQUIRED, end = false)
+    public Response activityWithLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) URI recoveryId,
+                                    @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Path(AFTER_LRA)
+    @AfterLRA // this method will be called when the LRA associated with the method activityWithLRA finishes
+    public Response afterEnd(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId, LRAStatus status) {
+        switch (status) {
+            case Closed:
+                // FALLTHRU
+            case Cancelled:
+                // FALLTHRU
+            case FailedToCancel:
+                // FALLTHRU
+            case FailedToClose:
+                lraMetricService.incrementMetric(
+                        LRAMetricType.valueOf(status.name()),
+                        lraId,
+                        AfterLRAListener.class.getName());
+                return Response.ok().build();
+            default:
+                return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java
new file mode 100644
index 0000000..bf35de5
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/AfterLRAParticipant.java
@@ -0,0 +1,104 @@
+/*
+ *******************************************************************************
+ * Copyright (c) 2019 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package org.eclipse.microprofile.lra.tck.participant.api;
+
+import org.eclipse.microprofile.lra.annotation.Compensate;
+import org.eclipse.microprofile.lra.annotation.Complete;
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.AfterLRA;
+import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
+import org.eclipse.microprofile.lra.tck.service.LRAMetricType;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+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_ENDED_CONTEXT_HEADER;
+import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER;
+
+/**
+ * resource for testing that methods annotated with
+ * {@link AfterLRA}
+ * are notified correctly when an LRA terminates
+ */
+@ApplicationScoped
+@Path(AfterLRAParticipant.AFTER_LRA_PARTICIPANT_PATH)
+public class AfterLRAParticipant {
+    public static final String AFTER_LRA_PARTICIPANT_PATH = "after-lra-participant";
+    public static final String AFTER_LRA_PARTICIPANT_WORK = "work";
+
+    private static final String AFTER_LRA = "/after";
+
+    @Inject
+    private LRAMetricService lraMetricService;
+
+    @PUT
+    @Path("/complete")
+    @Complete
+    public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId, String userData) {
+        lraMetricService.incrementMetric(LRAMetricType.Completed, lraId, AfterLRAParticipant.class.getName());
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Path("/compensate")
+    @Compensate
+    public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId, String userData) {
+        lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId, AfterLRAParticipant.class.getName());
+
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Path(AFTER_LRA_PARTICIPANT_WORK)
+    @LRA(value = LRA.Type.REQUIRED, end = false)
+    public Response activityWithLRA(@HeaderParam(LRA_HTTP_RECOVERY_HEADER) URI recoveryId,
+                                    @HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Path(AFTER_LRA)
+    @AfterLRA // this method will be called when the LRA associated with the method activityWithLRA finishes
+    public Response afterEnd(@HeaderParam(LRA_HTTP_ENDED_CONTEXT_HEADER) URI lraId, LRAStatus status) {
+        switch (status) {
+            case Closed:
+                // FALLTHRU
+            case Cancelled:
+                // FALLTHRU
+            case FailedToCancel:
+                // FALLTHRU
+            case FailedToClose:
+                lraMetricService.incrementMetric(
+                        LRAMetricType.valueOf(status.name()),
+                        lraId,
+                        AfterLRAParticipant.class.getName());
+                return Response.ok().build();
+            default:
+                return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+}
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
index cecb55e..623f6d1 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ContextTckResource.java
@@ -288,9 +288,9 @@ public class ContextTckResource {
     public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                                    @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent)
             throws NotFoundException {
-        lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId);
         if (parent != null) {
-            lraMetricService.incrementMetric(LRAMetricType.NESTED, parent);
+            lraMetricService.incrementMetric(LRAMetricType.Nested, parent);
         }
 
         return getEndPhaseResponse(false);
@@ -303,9 +303,9 @@ public class ContextTckResource {
     public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                                  @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent)
             throws NotFoundException {
-        lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Completed, lraId);
         if (parent != null) {
-            lraMetricService.incrementMetric(LRAMetricType.NESTED, parent);
+            lraMetricService.incrementMetric(LRAMetricType.Nested, parent);
         }
 
         return getEndPhaseResponse(true);
@@ -317,9 +317,9 @@ public class ContextTckResource {
     @Status
     public Response status(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                            @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent) {
-        lraMetricService.incrementMetric(LRAMetricType.STATUS, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Status, lraId);
         if (parent != null) {
-            lraMetricService.incrementMetric(LRAMetricType.NESTED, parent);
+            lraMetricService.incrementMetric(LRAMetricType.Nested, parent);
         }
 
         return Response.status(endPhaseStatus).entity(status.name()).build();
@@ -331,9 +331,9 @@ public class ContextTckResource {
     @Forget
     public Response forget(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId,
                            @HeaderParam(LRA_HTTP_PARENT_CONTEXT_HEADER) URI parent) {
-        lraMetricService.incrementMetric(LRAMetricType.FORGET, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Forget, lraId);
         if (parent != null) {
-            lraMetricService.incrementMetric(LRAMetricType.NESTED, parent);
+            lraMetricService.incrementMetric(LRAMetricType.Nested, parent);
         }
 
         return Response.status(endPhaseStatus).entity(status.name()).build();
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
index 06fbf46..0cd328c 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraCancelOnController.java
@@ -183,7 +183,7 @@ public class LraCancelOnController {
             throw new NullPointerException("lraId can't be null as it should be invoked with the context");
         }
 
-        lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Completed, lraId);
 
         LOGGER.info(String.format("LRA id '%s' was completed", lraId.toASCIIString()));
         return Response.ok().build();
@@ -198,7 +198,7 @@ public class LraCancelOnController {
             throw new NullPointerException("lraId can't be null as it should be invoked with the context");
         }
 
-        lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId);
 
         LOGGER.info(String.format("LRA id '%s' was compensated", lraId.toASCIIString()));
         return Response.ok().build();
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java
index 36a66b6..21bd9fe 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/LraController.java
@@ -140,9 +140,9 @@ public class LraController {
     @Complete
     public Response completeWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId, String userData)
         throws NotFoundException {
-        lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId, LraController.class.getName());
+        lraMetricService.incrementMetric(LRAMetricType.Completed, lraId, LraController.class.getName());
 
-        assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Complete methods
+        assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Completed methods
 
         Activity activity = activityStore.getActivityAndAssertExistence(lraId, context);
 
@@ -170,9 +170,9 @@ public class LraController {
     public Response compensateWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId, String userData)
         throws NotFoundException {
 
-        assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Compensate methods
+        assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Compensated methods
 
-        lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId, LraController.class.getName());
+        lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId, LraController.class.getName());
 
         Activity activity = activityStore.getActivityAndAssertExistence(lraId, context);
 
@@ -198,7 +198,7 @@ public class LraController {
     @Produces(MediaType.APPLICATION_JSON)
     @Forget
     public Response forgetWork(@HeaderParam(LRA_HTTP_CONTEXT_HEADER) URI lraId) {
-        lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId, LraController.class.getName());
+        lraMetricService.incrementMetric(LRAMetricType.Forget, lraId, LraController.class.getName());
 
         assertHeaderPresent(lraId); // the TCK expects the coordinator to invoke @Forget methods
 
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ParticipatingTckResource.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ParticipatingTckResource.java
index f19429f..23190bc 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ParticipatingTckResource.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/participant/api/ParticipatingTckResource.java
@@ -94,9 +94,9 @@ public class ParticipatingTckResource {
         }
 
         if (complete) {
-            lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId, ParticipatingTckResource.class.getName());
+            lraMetricService.incrementMetric(LRAMetricType.Completed, lraId, ParticipatingTckResource.class.getName());
         } else {
-            lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId, ParticipatingTckResource.class.getName());
+            lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId, ParticipatingTckResource.class.getName());
         }
 
         return Response.ok().build();
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
index aaf6bb8..bd34d43 100644
--- 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
@@ -79,7 +79,7 @@ public class ValidLRACSParticipant {
         assert lraId != null;
         
         return CompletableFuture.runAsync(() -> {
-            lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId);
+            lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId);
             
             simulateLongRunningCompensation();
         });
@@ -90,7 +90,7 @@ public class ValidLRACSParticipant {
         assert lraId != null;
         
         return CompletableFuture.supplyAsync(() -> {
-            lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId);
+            lraMetricService.incrementMetric(LRAMetricType.Completed, lraId);
             
             simulateLongRunningCompensation();
             return Response.accepted().build(); // Completing
@@ -102,7 +102,7 @@ public class ValidLRACSParticipant {
         assert lraId != null;
         
         return CompletableFuture.supplyAsync(() -> {
-            lraMetricService.incrementMetric(LRAMetricType.STATUS, lraId);
+            lraMetricService.incrementMetric(LRAMetricType.Status, lraId);
             
             simulateLongRunningCompensation();
             return ParticipantStatus.Completed;
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
index dec3f20..0fd46bf 100644
--- 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
@@ -22,8 +22,10 @@ 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.LRAStatus;
 import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
 import org.eclipse.microprofile.lra.annotation.Status;
+import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.annotation.ws.rs.LRA;
 import org.eclipse.microprofile.lra.annotation.ws.rs.LRA.Type;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
@@ -77,12 +79,21 @@ public class ValidLRAParticipant {
         return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(lraId).build();
     }
 
+    @AfterLRA
+    public void onLRAEnd(URI lraId, LRAStatus status) {
+        verifyLRAId(lraId);
+
+        lraMetricService.incrementMetric(LRAMetricType.AfterLRA, lraId);
+
+        LOGGER.fine(String.format("LRA id '%s' was finished with status %s",
+                lraId.toASCIIString(), status.name()));
+    }
 
     @Complete
     public void completeWithException(URI lraId, URI parentId) {
         verifyLRAId(lraId);
 
-        lraMetricService.incrementMetric(LRAMetricType.COMPLETE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Completed, lraId);
 
         LOGGER.fine(String.format("LRA id '%s' was completed", lraId.toASCIIString()));
         throw new WebApplicationException(Response.ok().build());
@@ -92,7 +103,7 @@ public class ValidLRAParticipant {
     public ParticipantStatus compensate(URI lraId) {
         verifyLRAId(lraId);
 
-        lraMetricService.incrementMetric(LRAMetricType.COMPENSATE, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Compensated, lraId);
 
         LOGGER.fine(String.format("LRA id '%s' was compensated", lraId.toASCIIString()));
         return ParticipantStatus.Compensating;
@@ -102,7 +113,7 @@ public class ValidLRAParticipant {
     public Response status(URI lraId) {
         verifyLRAId(lraId);
 
-        lraMetricService.incrementMetric(LRAMetricType.STATUS, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Status, lraId);
 
         LOGGER.fine(String.format("LRA id '%s' status called, return FailedToCompensate to get @Forget called", lraId.toASCIIString()));
         return Response.ok(ParticipantStatus.FailedToCompensate).build();
@@ -112,7 +123,7 @@ public class ValidLRAParticipant {
     public void forget(URI lraId) {
         verifyLRAId(lraId);
 
-        lraMetricService.incrementMetric(LRAMetricType.FORGET, lraId);
+        lraMetricService.incrementMetric(LRAMetricType.Forget, lraId);
 
         LOGGER.fine(String.format("LRA id '%s' forget called", lraId.toASCIIString()));
     }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRAMetricType.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRAMetricType.java
index 457dbfd..e1b3305 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRAMetricType.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/service/LRAMetricType.java
@@ -19,11 +19,26 @@
  *******************************************************************************/
 package org.eclipse.microprofile.lra.tck.service;
 
+import org.eclipse.microprofile.lra.annotation.LRAStatus;
+import org.eclipse.microprofile.lra.annotation.ParticipantStatus;
+
 public enum LRAMetricType {
-    
-    COMPENSATE,
-    COMPLETE,
-    STATUS,
-    FORGET,
-    NESTED
+    // LRA statistics
+    Closed(LRAStatus.Closed.name()), // an LRA that closed
+    FailedToClose(LRAStatus.FailedToClose.name()), // an LRA that failed to close
+    Cancelled(LRAStatus.Cancelled.name()), // an LRA that was cancelled
+    FailedToCancel(LRAStatus.FailedToCancel.name()), // an LRA that failed to cancel
+
+    // Participant statistics
+    Compensated(ParticipantStatus.Compensated.name()), // a participant that was asked to compensate
+    Completed(ParticipantStatus.Completed.name()), // a participant that was asked to complete
+    Status("Status"), // a participant that was asked for its status
+    Forget("Forget"), // a participant that was told to forget
+    Nested("Nested"), // a participant callback that was invoked in the context of a parent LRA
+
+    // Other statistics
+    AfterLRA("AfterLRA"); // a listener that has received a notification that an LRA finished
+
+    LRAMetricType(String label) {
+    }
 }


Test patch:
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckCancelOnTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckCancelOnTests.java
index fccdc3f..fcc460c 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckCancelOnTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckCancelOnTests.java
@@ -66,9 +66,9 @@ public class TckCancelOnTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Status.BAD_REQUEST, response, resourcePath));
         applyConsistencyDelay();
         assertEquals("After 400 compensate should be invoked", 
-            1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("After 400 complete can't be invoked", 
-            0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+            0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -84,9 +84,9 @@ public class TckCancelOnTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Status.INTERNAL_SERVER_ERROR, response, resourcePath));
         applyConsistencyDelay();
         assertEquals("After 500 compensate should be invoked", 
-            1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("After 500 complete can't be invoked", 
-            0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+            0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -102,9 +102,9 @@ public class TckCancelOnTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Status.SEE_OTHER, response, resourcePath));
         applyConsistencyDelay();
         assertEquals("After status code 303 is received, compensate should be invoked as set by attribute cancelOnFamily",
-                1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+                1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("After status code 303 is received, complete can't be invoked as not defined in annotation @LRA",
-                0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+                0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -120,9 +120,9 @@ public class TckCancelOnTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Status.MOVED_PERMANENTLY, response, resourcePath));
         applyConsistencyDelay();
         assertEquals("After status code 301 is received, compensate should be invoked as set by attribute cancelOn",
-                1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+                1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("After status code 301 is received, complete can't be invoked as not defined in annotation @LRA",
-                0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+                0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -138,9 +138,9 @@ public class TckCancelOnTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Status.INTERNAL_SERVER_ERROR, response, resourcePath));
         applyConsistencyDelay();
         assertEquals("After status code 500 is received, compensate can't be invoked as default behaviour was changed",
-                0, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+                0, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("After status code 500 is received, complete has to be called as default behaviour was changed",
-                1, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+                1, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -157,8 +157,8 @@ public class TckCancelOnTests extends TckTestBase {
         applyConsistencyDelay();
         // LraCancelOnController enlists twice the same participant, compensate is expected to be called only once
         assertEquals("Status was 200 but compensate should be called as LRA should be cancelled for remotely called participant as well",
-                1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+                1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("Even the 200 status was received the remotely called participant should cause the LRA being cancelled",
-                0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+                0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
index b1f7ab1..ef56714 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckContextTests.java
@@ -88,7 +88,7 @@ public class TckContextTests extends TckTestBase {
         invoke(false, REQUIRED_LRA_PATH, PUT, lra, 200, ContextTckResource.EndPhase.SUCCESS, 200);
 
         // verify that the resource was asked to complete
-        int completions = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra);
+        int completions = lraMetricService.getMetric(LRAMetricType.Completed, lra);
 
         assertEquals(testName.getMethodName() + ": Resource was not asked to complete", 
                 1, completions);
@@ -130,7 +130,7 @@ public class TckContextTests extends TckTestBase {
         lraClient.closeLRA(lra);
 
         // verify that the resource was not asked to complete
-        int completions = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra);
+        int completions = lraMetricService.getMetric(LRAMetricType.Completed, lra);
 
         assertEquals(testName.getMethodName() + ": Resource left but was still asked to complete",
                 0, completions);
@@ -146,11 +146,11 @@ public class TckContextTests extends TckTestBase {
         replayEndPhase(TCK_CONTEXT_RESOURCE_PATH);
 
         // the implementation should have called status which will have returned 500
-        count = lraMetricService.getMetric(LRAMetricType.STATUS, lra);
+        count = lraMetricService.getMetric(LRAMetricType.Status, lra);
         assertEquals(testName.getMethodName() + " resource status should have been called", 1, count);
 
         // the implementation should not call forget until it knows the participant status
-        count = lraMetricService.getMetric(LRAMetricType.FORGET, lra);
+        count = lraMetricService.getMetric(LRAMetricType.Forget, lra);
         assertEquals(testName.getMethodName() + " resource forget should not have been called", 0, count);
 
         // clear the fault
@@ -160,12 +160,12 @@ public class TckContextTests extends TckTestBase {
         replayEndPhase(TCK_CONTEXT_RESOURCE_PATH);
 
         // the implementation should have called status again which will have returned 200
-        count = lraMetricService.getMetric(LRAMetricType.STATUS, lra);
+        count = lraMetricService.getMetric(LRAMetricType.Status, lra);
         // the implementation should have called status at least once. Since we have alread called status in this test
         // check that the stat is at least 2
         assertTrue(testName.getMethodName() + " resource status should have been called again", count >= 2);
         // the implementation should call forget since it knows the participant status
-        count = lraMetricService.getMetric(LRAMetricType.FORGET, lra);
+        count = lraMetricService.getMetric(LRAMetricType.Forget, lra);
         assertEquals(testName.getMethodName() + " resource forget should have been called", 1, count);
     }
 
@@ -192,16 +192,16 @@ public class TckContextTests extends TckTestBase {
         // check that the resource was asked to complete twice, one in the context of the nested LRA and a
         // second time in the context of the top level LRA
 
-        int nestedCompletions = lraMetricService.getMetric(LRAMetricType.COMPLETE, nestedLRA);
+        int nestedCompletions = lraMetricService.getMetric(LRAMetricType.Completed, nestedLRA);
         assertEquals(testName.getMethodName() + ": resource should have completed for the nested LRA",
                 1, nestedCompletions);
 
-        int topLevelCompletions = lraMetricService.getMetric(LRAMetricType.COMPLETE, topLevelLRA);
+        int topLevelCompletions = lraMetricService.getMetric(LRAMetricType.Completed, topLevelLRA);
         assertEquals(testName.getMethodName() + ": resource should have completed for the top level LRA",
                 1, topLevelCompletions);
 
         // and validate that the parent LRA header was present when the nested LRA was asked to complete
-        int endCallsWithParentContextHeaderPresent = lraMetricService.getMetric(LRAMetricType.NESTED, topLevelLRA);
+        int endCallsWithParentContextHeaderPresent = lraMetricService.getMetric(LRAMetricType.Nested, topLevelLRA);
         assertEquals(testName.getMethodName() +
                         ": when the resource was asked to complete a nested LRA the parent context header was missing",
                 1, endCallsWithParentContextHeaderPresent);
@@ -219,7 +219,7 @@ public class TckContextTests extends TckTestBase {
         URI lra = URI.create(invoke(false, ASYNC_LRA_PATH1, PUT, null));
 
         // verify that the resource was asked to complete
-        int completions = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra);
+        int completions = lraMetricService.getMetric(LRAMetricType.Completed, lra);
 
         assertEquals(testName.getMethodName() + ": Resource was not asked to complete",
                 1, completions);
@@ -230,7 +230,7 @@ public class TckContextTests extends TckTestBase {
         URI lra = URI.create(invoke(false, ASYNC_LRA_PATH2, PUT, null));
 
         // verify that the resource was asked to complete
-        int completions = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra);
+        int completions = lraMetricService.getMetric(LRAMetricType.Completed, lra);
 
         assertEquals(testName.getMethodName() + ": Resource was not asked to complete",
                 1, completions);
@@ -243,8 +243,8 @@ public class TckContextTests extends TckTestBase {
             ContextTckResource.EndPhase.SUCCESS, 200));
 
         // verify that the resource was asked to compensate
-        int completions = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra);
-        int compensations = lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra);
+        int completions = lraMetricService.getMetric(LRAMetricType.Completed, lra);
+        int compensations = lraMetricService.getMetric(LRAMetricType.Compensated, lra);
 
         assertEquals(testName.getMethodName() + ": Resource was asked to complete",
                 0, completions);
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
index d52a4ce..91141f4 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckParticipantTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckParticipantTests.java
@@ -30,6 +30,7 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import javax.inject.Inject;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Response;
 
@@ -55,10 +56,10 @@ public class TckParticipantTests extends TckTestBase {
     }
     
     /**
-     * Test verifies that non-JAX-RS @Complete method is invoked according to the 
-     * LRA protocol and that if {@link javax.ws.rs.WebApplicationException} is 
+     * Test verifies that non-JAX-RS @Complete method is invoked according to the
+     * LRA protocol and that if {@link 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
+     * is carrying is extracted and acted upon according to LRA response handling
      */
     @Test
     public void validWebApplicationExceptionReturnedTest() {
@@ -68,16 +69,23 @@ public class TckParticipantTests extends TckTestBase {
         Response response = resourcePath.request().get();
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Response.Status.OK, response, resourcePath));
 
-        assertEquals("Non JAX-RS @Complete method throwing WebApplicationException shoud have been called", 
-            1, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+        assertEquals("Non JAX-RS @Complete method throwing WebApplicationException shoud have been called",
+            1, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
         assertEquals("@Compensate method should not have been called as LRA completed succesfully",
-            0, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
-
+            0, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
+
+        /*
+         * Validate that a resource can receive notifications of the final outcome of an LRA using
+         * the {@link org.eclipse.microprofile.lra.annotation.AfterLRA} annotation on
+         * a non JAX-RS method
+         */
+        assertEquals("@AfterLRA method should have been called",
+                1, lraMetricService.getMetric(LRAMetricType.AfterLRA, lraId));
     }
 
     /**
      * 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 
+     * 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 
@@ -97,16 +105,16 @@ public class TckParticipantTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Response.Status.INTERNAL_SERVER_ERROR, response, resourcePath));
         
         assertEquals("Non JAX-RS @Compensate method should have been called",
-            1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("@Complete method should not have been called as LRA compensated",
-            0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+            0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
 
         replayEndPhase(ValidLRAParticipant.RESOURCE_PATH);
 
         assertEquals("Non JAX-RS @Status method should have been called", 
-            1, lraMetricService.getMetric(LRAMetricType.STATUS, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Status, lraId));
         assertEquals("Non JAX-RS @Forget method should have been called",
-            1, lraMetricService.getMetric(LRAMetricType.FORGET, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Forget, lraId));
 
     }
 
@@ -124,9 +132,9 @@ public class TckParticipantTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Response.Status.INTERNAL_SERVER_ERROR, response, resourcePath));
 
         assertEquals("Non JAX-RS @Compensate method with CompletionStage<Void> should have been called",
-            1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
         assertEquals("Non JAX-RS @Complete method should have not been called",
-            0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+            0, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
     }
 
     /**
@@ -146,13 +154,13 @@ public class TckParticipantTests extends TckTestBase {
         URI lraId = URI.create(checkStatusReadAndCloseResponse(Response.Status.OK, response, resourcePath));
 
         assertEquals("Non JAX-RS @Complete method with CompletionStage<Response> should have been called",
-            1, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Completed, lraId));
         assertEquals("Non JAX-RS @Compensate method should have not been called",
-            0, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId));
+            0, lraMetricService.getMetric(LRAMetricType.Compensated, lraId));
 
         replayEndPhase(ValidLRACSParticipant.ROOT_PATH);
 
         assertEquals("Non JAX-RS @Status method with CompletionStage<ParticipantStatus> should have been called",
-            1, lraMetricService.getMetric(LRAMetricType.STATUS, lraId));
+            1, lraMetricService.getMetric(LRAMetricType.Status, lraId));
     }
 }
diff --git a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
index dae59d7..f69f754 100644
--- a/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
+++ b/tck/src/main/java/org/eclipse/microprofile/lra/tck/TckTests.java
@@ -20,12 +20,16 @@
 package org.eclipse.microprofile.lra.tck;
 
 import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER;
+import static org.eclipse.microprofile.lra.tck.participant.api.AfterLRAListener.AFTER_LRA_LISTENER_WORK;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraController.ACCEPT_WORK;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraController.LRA_CONTROLLER_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.LraController.TRANSACTIONAL_WORK_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.JOIN_WITH_EXISTING_LRA_PATH;
 import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.JOIN_WITH_EXISTING_LRA_PATH2;
 import static org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource.TCK_PARTICIPANT_RESOURCE_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.AfterLRAListener.AFTER_LRA_LISTENER_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.AfterLRAParticipant.AFTER_LRA_PARTICIPANT_PATH;
+import static org.eclipse.microprofile.lra.tck.participant.api.AfterLRAParticipant.AFTER_LRA_PARTICIPANT_WORK;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -44,10 +48,13 @@ import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.core.Response;
 
+import org.eclipse.microprofile.lra.annotation.AfterLRA;
 import org.eclipse.microprofile.lra.tck.participant.api.GenericLRAException;
 import org.eclipse.microprofile.lra.tck.participant.api.LraController;
 import org.eclipse.microprofile.lra.tck.participant.api.NoLRAController;
 import org.eclipse.microprofile.lra.tck.participant.api.ParticipatingTckResource;
+import org.eclipse.microprofile.lra.tck.participant.api.AfterLRAListener;
+import org.eclipse.microprofile.lra.tck.participant.api.AfterLRAParticipant;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricService;
 import org.eclipse.microprofile.lra.tck.service.LRAMetricType;
 import org.jboss.arquillian.container.test.api.Deployment;
@@ -189,7 +196,7 @@ public class TckTests extends TckTestBase {
         // check that participant was told to complete
         assertEquals("Wrong completion count for call " + resourcePath.getUri() + ". Expecting the method LRA was completed "
                 + "after joining the existing LRA " + lra, 
-                1, lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, LraController.class.getName()));
+                1, lraMetricService.getMetric(LRAMetricType.Completed, lra, LraController.class.getName()));
         
         // check that LRA coordinator no longer knows about lraId
         assertTrue("LRA '" + lra + "' should not be active anymore as it was closed yet.",
@@ -208,6 +215,52 @@ public class TckTests extends TckTestBase {
                 lraClient.isLRAFinished(lra));
     }
 
+    /**
+     * TCK test to verify that methods annotated with {@link AfterLRA}
+     * are notified correctly when an LRA terminates
+     */
+    @Test
+    public void testAfterLRAParticipant() throws WebApplicationException, InterruptedException {
+        URI lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS);
+        WebTarget resourcePath = tckSuiteTarget.path(AFTER_LRA_PARTICIPANT_PATH).path(AFTER_LRA_PARTICIPANT_WORK);
+        Response response = resourcePath
+                .request().header(LRA_HTTP_CONTEXT_HEADER, lra).put(Entity.text(""));
+        checkStatusAndCloseResponse(Response.Status.OK, response, resourcePath);
+        lraClient.closeLRA(lra);
+
+        // verify that the LRA is now in one of the terminal states
+        assertTrue("testAfterLRAParticipant: LRA did not finish",
+                lraClient.isLRAFinished(lra, lraMetricService, AfterLRAParticipant.class.getName()));
+
+        // verify that the resource was notified of the final state of the LRA
+        assertEquals("testAfterLRAParticipant: end synchronization was not invoked on resource " + resourcePath.getUri(),
+                1,
+                lraMetricService.getMetric(LRAMetricType.Closed, lra, AfterLRAParticipant.class.getName()));
+    }
+
+    /**
+     * TCK test to verify that methods annotated with {@link AfterLRA}
+     * are notified correctly when an LRA terminates
+     */
+    @Test
+    public void testAfterLRAListener() throws WebApplicationException, InterruptedException {
+        URI lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS);
+        WebTarget resourcePath = tckSuiteTarget.path(AFTER_LRA_LISTENER_PATH).path(AFTER_LRA_LISTENER_WORK);
+        Response response = resourcePath
+                .request().header(LRA_HTTP_CONTEXT_HEADER, lra).put(Entity.text(""));
+        checkStatusAndCloseResponse(Response.Status.OK, response, resourcePath);
+        lraClient.closeLRA(lra);
+
+        // verify that the LRA is now in one of the terminal states
+        assertTrue("testAfterLRAListener: LRA did not finish",
+                lraClient.isLRAFinished(lra, lraMetricService, AfterLRAListener.class.getName()));
+
+        // verify that the resource was notified of the final state of the LRA
+        assertEquals("testAfterLRAListener: end synchronization was not invoked on resource " + resourcePath.getUri(),
+                1,
+                lraMetricService.getMetric(LRAMetricType.Closed, lra, AfterLRAParticipant.class.getName()));
+    }
+
     @Test
     public void leaveLRA() throws WebApplicationException {
         URI lra = lraClient.startLRA(null, lraClientId(), lraTimeout(), ChronoUnit.MILLIS);
@@ -233,7 +286,7 @@ public class TckTests extends TckTestBase {
         assertEquals("Wrong completion count when participant left the LRA. "
                 + "Expecting the completed count hasn't change between start and end of the test. "
                 + "The test call went to LRA controller at " + resourcePath.getUri(), 
-                -1, lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, LraController.class.getName()));
+                -1, lraMetricService.getMetric(LRAMetricType.Completed, lra, LraController.class.getName()));
     }
 
     @Test
@@ -280,11 +333,11 @@ public class TckTests extends TckTestBase {
         assertEquals("The LRA should have timed out but complete was called instead of compensate. "
                 + "Expecting the number of complete call before test matches the ones after LRA timed out. "
                 + "The test call went to " + resourcePath.getUri(), 
-                0, lraMetricService.getMetric(LRAMetricType.COMPLETE, lraId, LraController.class.getName()));
+                0, lraMetricService.getMetric(LRAMetricType.Completed, lraId, LraController.class.getName()));
         assertEquals("The LRA should have timed out and compensate should be called. "
                 + "Expecting the number of compensate call before test is one less lower than the ones after LRA timed out. "
                 + "The test call went to " + resourcePath.getUri(), 
-                1, lraMetricService.getMetric(LRAMetricType.COMPENSATE, lraId, LraController.class.getName()));
+                1, lraMetricService.getMetric(LRAMetricType.Compensated, lraId, LraController.class.getName()));
     }
 
     @Test
@@ -316,8 +369,8 @@ public class TckTests extends TckTestBase {
         replayEndPhase(null);
         applyConsistencyDelay();
 
-        int completionCount = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, LraController.class.getName());
-        int compensationCount = lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, LraController.class.getName());
+        int completionCount = lraMetricService.getMetric(LRAMetricType.Completed, lra, LraController.class.getName());
+        int compensationCount = lraMetricService.getMetric(LRAMetricType.Compensated, lra, LraController.class.getName());
 
         boolean wasCalled = (close ? completionCount > 0 : compensationCount > 0);
         boolean wasNotCalled = (close ? compensationCount == 0 : completionCount == 0);
@@ -355,13 +408,13 @@ public class TckTests extends TckTestBase {
         // check that second service (the LRA aware one), namely
         // {@link org.eclipse.microprofile.lra.tck.participant.api.LraController#activityWithMandatoryLRA(String, String)}
         // was told to compensate
-        int completedCount = lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, LraController.class.getName());
-        int compensatedCount = lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, LraController.class.getName());
+        int completedCount = lraMetricService.getMetric(LRAMetricType.Completed, lra, LraController.class.getName());
+        int compensatedCount = lraMetricService.getMetric(LRAMetricType.Compensated, lra, LraController.class.getName());
 
-        assertEquals("Complete should not be called on the LRA aware service. "
+        assertEquals("Completed should not be called on the LRA aware service. "
                 + "The number of completed count for before and after test does not match. "
                 + "The test call went to " + resourcePath.getUri(), 0, completedCount);
-        assertEquals("Compensate service should be called on LRA aware service. The number of compensated count after test is bigger for one. "
+        assertEquals("Compensated service should be called on LRA aware service. The number of compensated count after test is bigger for one. "
                 + "The test call went to " + resourcePath.getUri(), 1, compensatedCount);
     }
 
@@ -448,15 +501,15 @@ public class TckTests extends TckTestBase {
             applyConsistencyDelay();
             
             assertTrue(methodName + ": resource should have completed once with no compensations",
-                    1 == lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, ParticipatingTckResource.class.getName()) && 
-                    0 == lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, ParticipatingTckResource.class.getName()));
+                    1 == lraMetricService.getMetric(LRAMetricType.Completed, lra, ParticipatingTckResource.class.getName()) &&
+                    0 == lraMetricService.getMetric(LRAMetricType.Compensated, lra, ParticipatingTckResource.class.getName()));
         } else {
             lraClient.cancelLRA(lra);
             applyConsistencyDelay();
 
             assertTrue(methodName + ":: resource should have compensated once with no completions",
-                    0 == lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, ParticipatingTckResource.class.getName()) &&
-                    1 == lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, ParticipatingTckResource.class.getName()));
+                    0 == lraMetricService.getMetric(LRAMetricType.Completed, lra, ParticipatingTckResource.class.getName()) &&
+                    1 == lraMetricService.getMetric(LRAMetricType.Compensated, lra, ParticipatingTckResource.class.getName()));
         }
     }
 
@@ -478,15 +531,15 @@ public class TckTests extends TckTestBase {
             applyConsistencyDelay();
 
             assertTrue("joinWithTwoResourcesWithClose: both resources should have completed",
-                    lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, LraController.class.getName()) == 1 &&
-                    lraMetricService.getMetric(LRAMetricType.COMPLETE, lra, ParticipatingTckResource.class.getName()) == 1);
+                    lraMetricService.getMetric(LRAMetricType.Completed, lra, LraController.class.getName()) == 1 &&
+                    lraMetricService.getMetric(LRAMetricType.Completed, lra, ParticipatingTckResource.class.getName()) == 1);
         } else {
             lraClient.cancelLRA(lra);
             applyConsistencyDelay();
 
             assertTrue("joinWithTwoResourcesWithCancel: both resources should have compensated",
-                    lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, LraController.class.getName()) == 1 &&
-                    lraMetricService.getMetric(LRAMetricType.COMPENSATE, lra, ParticipatingTckResource.class.getName()) == 1);
+                    lraMetricService.getMetric(LRAMetricType.Compensated, lra, LraController.class.getName()) == 1 &&
+                    lraMetricService.getMetric(LRAMetricType.Compensated, lra, ParticipatingTckResource.class.getName()) == 1);
         }
     }
 
@@ -531,8 +584,8 @@ public class TckTests extends TckTestBase {
                 lraClient.isLRAFinished(URI.create( lraArray[0])));
 
         applyConsistencyDelay();
-        int inMiddleCompletedCount = lraMetricService.getMetric(LRAMetricType.COMPLETE);
-        int inMiddleCompensatedCount = lraMetricService.getMetric(LRAMetricType.COMPENSATE);
+        int inMiddleCompletedCount = lraMetricService.getMetric(LRAMetricType.Completed);
+        int inMiddleCompensatedCount = lraMetricService.getMetric(LRAMetricType.Compensated);
 
         // check that all nested activities were told to complete
         assertEquals("multiLevelNestedActivity: step 3 (called test path " + resourcePath.getUri() + ")",
@@ -572,8 +625,8 @@ public class TckTests extends TckTestBase {
                 lraClient.isLRAFinished(URI.create(lraArray[i]))));
 
         applyConsistencyDelay();
-        int afterCompletedCount = lraMetricService.getMetric(LRAMetricType.COMPLETE);
-        int afterCompensatedCount = lraMetricService.getMetric(LRAMetricType.COMPENSATE);
+        int afterCompletedCount = lraMetricService.getMetric(LRAMetricType.Completed);
+        int afterCompensatedCount = lraMetricService.getMetric(LRAMetricType.Compensated);
 
         if (how == CompletionType.complete) {
             // make sure that all nested activities were not told to complete or cancel a second time