use of org.eclipse.microprofile.lra.annotation.Complete in project narayana by jbosstm.
the class LRATest method multiLevelNestedActivity.
private void multiLevelNestedActivity(CompletionType how, int nestedCnt) throws WebApplicationException, URISyntaxException {
WebTarget resourcePath = client.target(TestPortProvider.generateURL("/base/test/multiLevelNestedActivity"));
if (how == CompletionType.mixed && nestedCnt <= 1) {
how = CompletionType.complete;
}
URI lra = new URI(client.target(TestPortProvider.generateURL("/base/test/start")).request().get(String.class));
Response response = resourcePath.queryParam("nestedCnt", nestedCnt).request().header(LRA_HTTP_CONTEXT_HEADER, lra).put(Entity.text(""));
// the response is a comma separated list of URIs (parent, children)
String lraStr = response.readEntity(String.class);
assertNotNull("expecting a LRA string returned from " + resourcePath.getUri(), lraStr);
URI[] uris = Arrays.stream(lraStr.split(",")).map(s -> {
try {
return new URI(s);
} catch (URISyntaxException e) {
fail(e.getMessage());
return null;
}
}).toArray(URI[]::new);
// check that the multiLevelNestedActivity method returned the mandatory LRA followed by any nested LRAs
assertEquals("multiLevelNestedActivity: step 1 (the test call went to " + resourcePath.getUri() + ")", nestedCnt + 1, uris.length);
// first element should be the mandatory LRA
assertEquals("multiLevelNestedActivity: step 2 (the test call went to " + resourcePath.getUri() + ")", lra, uris[0]);
// and the mandatory lra seen by the multiLevelNestedActivity method
assertFalse("multiLevelNestedActivity: top level LRA should be active (path called " + resourcePath.getUri() + ")", isFinished(uris[0]));
// check that all nested activities were told to complete
assertEquals("multiLevelNestedActivity: step 3 (called test path " + resourcePath.getUri() + ")", nestedCnt, completeCount.get());
assertEquals("multiLevelNestedActivity: step 4 (called test path " + resourcePath.getUri() + ")", 0, compensateCount.get());
// close the LRA
if (how == CompletionType.compensate) {
lraClient.cancelLRA(lra);
// validate that the top level and nested LRAs are gone
assertAllFinished(uris);
/*
* the test starts LRA1 calls a @Mandatory method multiLevelNestedActivity which enlists in LRA1
* multiLevelNestedActivity then calls an @Nested method which starts L2 and enlists another participant
* when the method returns the nested participant is completed (ie completed count is incremented)
* Canceling L1 should then compensate the L1 enlistment (ie compensate count is incremented)
* which will then tell L2 to compensate (ie the compensate count is incremented again)
*/
// each nested participant should have completed (the +nestedCnt)
assertEquals("multiLevelNestedActivity: step 7 (called test path " + resourcePath.getUri() + ")", nestedCnt, completeCount.get());
// each nested participant should have compensated. The top level enlistment should have compensated (the +1)
assertEquals("multiLevelNestedActivity: step 8 (called test path " + resourcePath.getUri() + ")", nestedCnt + 1, compensateCount.get());
} else if (how == CompletionType.complete) {
lraClient.closeLRA(lra);
// validate that the top level and nested LRAs are gone
assertAllFinished(uris);
// each nested participant and the top level participant should have completed (nestedCnt + 1) at least once
assertTrue("multiLevelNestedActivity: step 5a (called test path " + resourcePath.getUri() + ")", completeCount.get() >= nestedCnt + 1);
// each nested participant should have been told to forget
assertEquals("multiLevelNestedActivity: step 5b (called test path " + resourcePath.getUri() + ")", forgetCount.get(), nestedCnt);
// and that neither were still not told to compensate
assertEquals("multiLevelNestedActivity: step 6 (called test path " + resourcePath.getUri() + ")", 0, compensateCount.get());
} else {
// compensate the first nested LRA in the enlisted resource
try (Response r = client.target(TestPortProvider.generateURL("/base/test/end")).queryParam("cancel", true).request().header(LRA_HTTP_CONTEXT_HEADER, uris[1]).put(Entity.text(""))) {
assertEquals("compensate the first nested LRA", 500, r.getStatus());
}
// should not complete any nested LRAs (since they have already completed via the interceptor)
lraClient.closeLRA(lra);
/*
* Expect nestedCnt + 1 completions, 1 for the top level and one for each nested LRA
* (NB the first nested LRA is completed and compensated)
* Note that the top level complete should not call complete again on the nested LRA
*/
assertEquals("multiLevelNestedActivity: step 10 (called test path " + resourcePath.getUri() + ")", nestedCnt + 1, completeCount.get());
/*
* The test is calling for a mixed outcome:
* - the top level LRA was closed
* - one of the nested LRAs was compensated the rest should have been completed
*/
// there should be just 1 compensation (the first nested LRA)
assertEquals("multiLevelNestedActivity: step 9 (called test path " + resourcePath.getUri() + ")", 1, compensateCount.get());
}
}
use of org.eclipse.microprofile.lra.annotation.Complete in project narayana by jbosstm.
the class ServerLRAFilter method filter.
@Override
public void filter(ContainerRequestContext containerRequestContext) {
// Note that this filter uses abortWith instead of throwing exceptions on encountering exceptional
// conditions. This facilitates async because filters for asynchronous JAX-RS methods are
// not allowed to throw exceptions.
Method method = resourceInfo.getResourceMethod();
MultivaluedMap<String, String> headers = containerRequestContext.getHeaders();
LRA.Type type = null;
LRA transactional = AnnotationResolver.resolveAnnotation(LRA.class, method);
URI lraId;
URI newLRA = null;
Long timeout = null;
URI suspendedLRA = null;
URI incommingLRA = null;
URI recoveryUrl;
boolean isLongRunning = false;
boolean requiresActiveLRA = false;
ArrayList<Progress> progress = null;
if (transactional == null) {
transactional = method.getDeclaringClass().getDeclaredAnnotation(LRA.class);
}
if (transactional != null) {
type = transactional.value();
isLongRunning = !transactional.end();
Response.Status.Family[] cancel0nFamily = transactional.cancelOnFamily();
Response.Status[] cancel0n = transactional.cancelOn();
if (cancel0nFamily.length != 0) {
containerRequestContext.setProperty(CANCEL_ON_FAMILY_PROP, cancel0nFamily);
}
if (cancel0n.length != 0) {
containerRequestContext.setProperty(CANCEL_ON_PROP, cancel0n);
}
if (transactional.timeLimit() != 0) {
timeout = Duration.of(transactional.timeLimit(), transactional.timeUnit()).toMillis();
}
}
boolean endAnnotation = AnnotationResolver.isAnnotationPresent(Complete.class, method) || AnnotationResolver.isAnnotationPresent(Compensate.class, method) || AnnotationResolver.isAnnotationPresent(Leave.class, method) || AnnotationResolver.isAnnotationPresent(Status.class, method) || AnnotationResolver.isAnnotationPresent(Forget.class, method) || AnnotationResolver.isAnnotationPresent(AfterLRA.class, method);
if (headers.containsKey(LRA_HTTP_CONTEXT_HEADER)) {
try {
incommingLRA = new URI(Current.getLast(headers.get(LRA_HTTP_CONTEXT_HEADER)));
} catch (URISyntaxException e) {
String msg = String.format("header %s contains an invalid URL %s", LRA_HTTP_CONTEXT_HEADER, Current.getLast(headers.get(LRA_HTTP_CONTEXT_HEADER)));
abortWith(containerRequestContext, null, Response.Status.PRECONDITION_FAILED.getStatusCode(), msg, null);
// user error, bail out
return;
}
if (AnnotationResolver.isAnnotationPresent(Leave.class, method)) {
// leave the LRA
Map<String, String> terminateURIs = NarayanaLRAClient.getTerminationUris(resourceInfo.getResourceClass(), containerRequestContext.getUriInfo(), timeout);
String compensatorId = terminateURIs.get("Link");
if (compensatorId == null) {
abortWith(containerRequestContext, incommingLRA.toASCIIString(), Response.Status.BAD_REQUEST.getStatusCode(), "Missing complete or compensate annotations", null);
// user error, bail out
return;
}
try {
getLRAClient().leaveLRA(incommingLRA, compensatorId);
// leave succeeded
progress = updateProgress(progress, ProgressStep.Left, null);
} catch (WebApplicationException e) {
// leave may have failed
progress = updateProgress(progress, ProgressStep.LeaveFailed, e.getMessage());
abortWith(containerRequestContext, incommingLRA.toASCIIString(), e.getResponse().getStatus(), e.getMessage(), progress);
// the error will be handled or reported via the response filter
return;
} catch (ProcessingException e) {
// a remote coordinator was unavailable
// leave may have failed
progress = updateProgress(progress, ProgressStep.LeaveFailed, e.getMessage());
abortWith(containerRequestContext, incommingLRA.toASCIIString(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e.getMessage(), progress);
// the error will be handled or reported via the response filter
return;
}
// let the participant know which lra he left by leaving the header intact
}
}
if (type == null) {
if (!endAnnotation) {
Current.clearContext(headers);
}
if (incommingLRA != null) {
Current.push(incommingLRA);
containerRequestContext.setProperty(SUSPENDED_LRA_PROP, incommingLRA);
}
// not transactional
return;
}
// check the incoming request for an LRA context
if (!headers.containsKey(LRA_HTTP_CONTEXT_HEADER)) {
Object lraContext = containerRequestContext.getProperty(LRA_HTTP_CONTEXT_HEADER);
if (lraContext != null) {
incommingLRA = (URI) lraContext;
}
}
if (endAnnotation && incommingLRA == null) {
return;
}
if (incommingLRA != null) {
// set the parent context header
try {
headers.putSingle(LRA_HTTP_PARENT_CONTEXT_HEADER, Current.getFirstParent(incommingLRA));
} catch (UnsupportedEncodingException e) {
abortWith(containerRequestContext, incommingLRA.toASCIIString(), Response.Status.PRECONDITION_FAILED.getStatusCode(), String.format("incoming LRA %s contains an invalid parent: %s", incommingLRA, e.getMessage()), progress);
// any previous actions (the leave request) will be reported via the response filter
return;
}
}
switch(type) {
case // a txn must be present
MANDATORY:
if (isTxInvalid(containerRequestContext, type, incommingLRA, true, progress)) {
// any previous actions (eg the leave request) will be reported via the response filter
return;
}
lraId = incommingLRA;
requiresActiveLRA = true;
break;
case // a txn must not be present
NEVER:
if (isTxInvalid(containerRequestContext, type, incommingLRA, false, progress)) {
// any previous actions (the leave request) will be reported via the response filter
return;
}
// must not run with any context
lraId = null;
break;
case NOT_SUPPORTED:
suspendedLRA = incommingLRA;
// must not run with any context
lraId = null;
break;
case NESTED:
// FALLTHRU
case REQUIRED:
if (incommingLRA != null) {
if (type == NESTED) {
headers.putSingle(LRA_HTTP_PARENT_CONTEXT_HEADER, incommingLRA.toASCIIString());
// if there is an LRA present nest a new LRA under it
suspendedLRA = incommingLRA;
if (progress == null) {
progress = new ArrayList<>();
}
newLRA = lraId = startLRA(containerRequestContext, incommingLRA, method, timeout, progress);
if (newLRA == null) {
// the failure plus any previous actions (the leave request) will be reported via the response filter
return;
}
} else {
lraId = incommingLRA;
// incomingLRA will be resumed
requiresActiveLRA = true;
}
} else {
if (progress == null) {
// NB my IDE seems to think this check is redundant
progress = new ArrayList<>();
}
newLRA = lraId = startLRA(containerRequestContext, null, method, timeout, progress);
if (newLRA == null) {
// the failure and any previous actions (the leave request) will be reported via the response filter
return;
}
}
break;
case REQUIRES_NEW:
// previous = AtomicAction.suspend();
suspendedLRA = incommingLRA;
if (progress == null) {
progress = new ArrayList<>();
}
newLRA = lraId = startLRA(containerRequestContext, null, method, timeout, progress);
if (newLRA == null) {
// the failure and any previous actions (the leave request) will be reported via the response filter
return;
}
break;
case SUPPORTS:
lraId = incommingLRA;
break;
default:
lraId = incommingLRA;
}
if (lraId == null) {
// the method call needs to run without a transaction
Current.clearContext(headers);
if (suspendedLRA != null) {
containerRequestContext.setProperty(SUSPENDED_LRA_PROP, suspendedLRA);
}
// non transactional
return;
}
if (!isLongRunning) {
containerRequestContext.setProperty(TERMINAL_LRA_PROP, lraId);
}
// store state with the current thread. TODO for the async version use containerRequestContext.setProperty("lra", Current.peek());
// make the current LRA available to the called method
Current.updateLRAContext(lraId, headers);
if (newLRA != null) {
if (suspendedLRA != null) {
containerRequestContext.setProperty(SUSPENDED_LRA_PROP, incommingLRA);
}
containerRequestContext.setProperty(NEW_LRA_PROP, newLRA);
}
Current.push(lraId);
try {
// make the current LRA available to the called method
getLRAClient().setCurrentLRA(lraId);
} catch (Exception e) {
// should not happen since lraId has already been validated
// (perhaps we should not use the client API to set the context)
abortWith(containerRequestContext, lraId.toASCIIString(), Response.Status.BAD_REQUEST.getStatusCode(), e.getMessage(), progress);
// any previous actions (such as leave and start requests) will be reported via the response filter
return;
}
// TODO make sure it is possible to do compensations inside a new LRA
if (!endAnnotation) {
// don't enlist for methods marked with Compensate, Complete or Leave
URI baseUri = containerRequestContext.getUriInfo().getBaseUri();
Map<String, String> terminateURIs = NarayanaLRAClient.getTerminationUris(resourceInfo.getResourceClass(), containerRequestContext.getUriInfo(), timeout);
String timeLimitStr = terminateURIs.get(TIMELIMIT_PARAM_NAME);
long timeLimit = timeLimitStr == null ? DEFAULT_TIMEOUT_MILLIS : Long.parseLong(timeLimitStr);
LRAParticipant participant = lraParticipantRegistry != null ? lraParticipantRegistry.getParticipant(resourceInfo.getResourceClass().getName()) : null;
if (terminateURIs.containsKey("Link") || participant != null) {
try {
if (participant != null) {
participant.augmentTerminationURIs(terminateURIs, baseUri);
}
recoveryUrl = getLRAClient().joinLRA(lraId, timeLimit, toURI(terminateURIs.get(COMPENSATE)), toURI(terminateURIs.get(COMPLETE)), toURI(terminateURIs.get(FORGET)), toURI(terminateURIs.get(LEAVE)), toURI(terminateURIs.get(AFTER)), toURI(terminateURIs.get(STATUS)), null);
progress = updateProgress(progress, ProgressStep.Joined, null);
headers.putSingle(LRA_HTTP_RECOVERY_HEADER, START_END_QUOTES_PATTERN.matcher(recoveryUrl.toASCIIString()).replaceAll(""));
} catch (WebApplicationException e) {
progress = updateProgress(progress, ProgressStep.JoinFailed, e.getMessage());
abortWith(containerRequestContext, lraId.toASCIIString(), e.getResponse().getStatus(), String.format("%s: %s", e.getClass().getSimpleName(), e.getMessage()), progress);
// the failure plus any previous actions (such as leave and start requests) will be reported via the response filter
} catch (URISyntaxException e) {
// one or more of the participant end points was invalid
progress = updateProgress(progress, ProgressStep.JoinFailed, e.getMessage());
abortWith(containerRequestContext, lraId.toASCIIString(), Response.Status.BAD_REQUEST.getStatusCode(), String.format("%s %s: %s", lraId, e.getClass().getSimpleName(), e.getMessage()), progress);
// the failure plus any previous actions (such as leave and start requests) will be reported via the response filter
} catch (ProcessingException e) {
// a remote coordinator was unavailable
progress = updateProgress(progress, ProgressStep.JoinFailed, e.getMessage());
abortWith(containerRequestContext, lraId.toASCIIString(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), String.format("%s %s,", e.getClass().getSimpleName(), e.getMessage()), progress);
// the failure plus any previous actions (such as leave and start requests) will be reported via the response filter
}
} else if (requiresActiveLRA && getLRAClient().getStatus(lraId) != LRAStatus.Active) {
Current.clearContext(headers);
Current.pop(lraId);
containerRequestContext.removeProperty(SUSPENDED_LRA_PROP);
if (type == MANDATORY) {
abortWith(containerRequestContext, lraId.toASCIIString(), Response.Status.PRECONDITION_FAILED.getStatusCode(), "LRA should have been active: ", progress);
// any previous actions (such as leave and start requests) will be reported via the response filter
}
}
}
}
Aggregations