Search in sources :

Example 1 with Compensate

use of org.eclipse.microprofile.lra.annotation.Compensate 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());
    }
}
Also used : Response(javax.ws.rs.core.Response) Arrays(java.util.Arrays) Produces(javax.ws.rs.Produces) AfterLRA(org.eclipse.microprofile.lra.annotation.AfterLRA) LRAService(io.narayana.lra.coordinator.domain.service.LRAService) URISyntaxException(java.net.URISyntaxException) Path(javax.ws.rs.Path) COORDINATOR_PATH_NAME(io.narayana.lra.LRAConstants.COORDINATOR_PATH_NAME) Application(javax.ws.rs.core.Application) com.arjuna.ats.arjuna.common.arjPropertyManager(com.arjuna.ats.arjuna.common.arjPropertyManager) ParticipantStatusOctetStreamProvider(io.narayana.lra.provider.ParticipantStatusOctetStreamProvider) MediaType(javax.ws.rs.core.MediaType) QueryParam(javax.ws.rs.QueryParam) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) After(org.junit.After) DefaultValue(javax.ws.rs.DefaultValue) HeaderParam(javax.ws.rs.HeaderParam) UndertowJaxrsServer(org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer) Assert.fail(org.junit.Assert.fail) URI(java.net.URI) DELETE(javax.ws.rs.DELETE) ServerLRAFilter(io.narayana.lra.filter.ServerLRAFilter) Forget(org.eclipse.microprofile.lra.annotation.Forget) LRA_HTTP_RECOVERY_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER) Set(java.util.Set) Entity(javax.ws.rs.client.Entity) NotFoundException(javax.ws.rs.NotFoundException) Objects(java.util.Objects) NarayanaLRAClient(io.narayana.lra.client.NarayanaLRAClient) Response(javax.ws.rs.core.Response) Assert.assertFalse(org.junit.Assert.assertFalse) WebApplicationException(javax.ws.rs.WebApplicationException) Complete(org.eclipse.microprofile.lra.annotation.Complete) Link(javax.ws.rs.core.Link) IntStream(java.util.stream.IntStream) LRA_HTTP_CONTEXT_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER) BeforeClass(org.junit.BeforeClass) GET(javax.ws.rs.GET) Client(javax.ws.rs.client.Client) HashSet(java.util.HashSet) LRARecoveryModule(io.narayana.lra.coordinator.internal.LRARecoveryModule) ClientBuilder(javax.ws.rs.client.ClientBuilder) TestName(org.junit.rules.TestName) StringTokenizer(java.util.StringTokenizer) Coordinator(io.narayana.lra.coordinator.api.Coordinator) LRA_HTTP_PARENT_CONTEXT_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER) Before(org.junit.Before) ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) TestPortProvider(org.jboss.resteasy.test.TestPortProvider) Assert.assertNotNull(org.junit.Assert.assertNotNull) ApplicationPath(javax.ws.rs.ApplicationPath) Assert.assertTrue(org.junit.Assert.assertTrue) Test(org.junit.Test) File(java.io.File) LRAStatus(org.eclipse.microprofile.lra.annotation.LRAStatus) Rule(org.junit.Rule) ChronoUnit(java.time.temporal.ChronoUnit) Assert.assertNull(org.junit.Assert.assertNull) LRA(org.eclipse.microprofile.lra.annotation.ws.rs.LRA) Compensate(org.eclipse.microprofile.lra.annotation.Compensate) LRALogger(io.narayana.lra.logging.LRALogger) PUT(javax.ws.rs.PUT) WebTarget(javax.ws.rs.client.WebTarget) Assert(org.junit.Assert) Assert.assertEquals(org.junit.Assert.assertEquals) WebTarget(javax.ws.rs.client.WebTarget) URISyntaxException(java.net.URISyntaxException) URI(java.net.URI)

Example 2 with Compensate

use of org.eclipse.microprofile.lra.annotation.Compensate 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
            }
        }
    }
}
Also used : WebApplicationException(javax.ws.rs.WebApplicationException) Complete(org.eclipse.microprofile.lra.annotation.Complete) Compensate(org.eclipse.microprofile.lra.annotation.Compensate) URISyntaxException(java.net.URISyntaxException) URI(java.net.URI) ProcessingException(javax.ws.rs.ProcessingException) Status(org.eclipse.microprofile.lra.annotation.Status) LRAStatus(org.eclipse.microprofile.lra.annotation.LRAStatus) AfterLRA(org.eclipse.microprofile.lra.annotation.AfterLRA) UnsupportedEncodingException(java.io.UnsupportedEncodingException) LRAParticipant(io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipant) Method(java.lang.reflect.Method) URISyntaxException(java.net.URISyntaxException) NotFoundException(javax.ws.rs.NotFoundException) ProcessingException(javax.ws.rs.ProcessingException) WebApplicationException(javax.ws.rs.WebApplicationException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) Response(javax.ws.rs.core.Response) AfterLRA(org.eclipse.microprofile.lra.annotation.AfterLRA) LRA(org.eclipse.microprofile.lra.annotation.ws.rs.LRA)

Aggregations

URI (java.net.URI)2 URISyntaxException (java.net.URISyntaxException)2 NotFoundException (javax.ws.rs.NotFoundException)2 WebApplicationException (javax.ws.rs.WebApplicationException)2 Response (javax.ws.rs.core.Response)2 AfterLRA (org.eclipse.microprofile.lra.annotation.AfterLRA)2 com.arjuna.ats.arjuna.common.arjPropertyManager (com.arjuna.ats.arjuna.common.arjPropertyManager)1 COORDINATOR_PATH_NAME (io.narayana.lra.LRAConstants.COORDINATOR_PATH_NAME)1 NarayanaLRAClient (io.narayana.lra.client.NarayanaLRAClient)1 LRAParticipant (io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipant)1 Coordinator (io.narayana.lra.coordinator.api.Coordinator)1 LRAService (io.narayana.lra.coordinator.domain.service.LRAService)1 LRARecoveryModule (io.narayana.lra.coordinator.internal.LRARecoveryModule)1 ServerLRAFilter (io.narayana.lra.filter.ServerLRAFilter)1 LRALogger (io.narayana.lra.logging.LRALogger)1 ParticipantStatusOctetStreamProvider (io.narayana.lra.provider.ParticipantStatusOctetStreamProvider)1 File (java.io.File)1 UnsupportedEncodingException (java.io.UnsupportedEncodingException)1 Method (java.lang.reflect.Method)1 ChronoUnit (java.time.temporal.ChronoUnit)1