Search in sources :

Example 1 with Status

use of org.eclipse.microprofile.lra.annotation.Status in project narayana by jbosstm.

the class NarayanaLRAClient method getTerminationUris.

/**
 * For particular compensator class it returns termination uris based on the provided base uri.
 * You get map of string and URI.
 *
 * @param compensatorClass  compensator class to examine
 * @param uriInfo  the uri that triggered this join request.
 * @return map of URI
 */
public static Map<String, String> getTerminationUris(Class<?> compensatorClass, UriInfo uriInfo, Long timeout) {
    Map<String, String> paths = new HashMap<>();
    final boolean[] asyncTermination = { false };
    URI baseUri = uriInfo.getBaseUri();
    /*
         * Calculate which path to prepend to the LRA participant methods. If there is more than one matching URI
         * then the second matched URI comes from either the class level Path annotation or from a sub-resource locator.
         * In both cases the second matched URI can be used as a prefix for the LRA participant URIs:
         */
    List<String> matchedURIs = uriInfo.getMatchedURIs();
    int matchedURI = (matchedURIs.size() > 1 ? 1 : 0);
    final String uriPrefix = baseUri + matchedURIs.get(matchedURI);
    String timeoutValue = timeout != null ? Long.toString(timeout) : "0";
    Arrays.stream(compensatorClass.getMethods()).forEach(method -> {
        Path pathAnnotation = method.getAnnotation(Path.class);
        if (pathAnnotation != null) {
            if (checkMethod(paths, method, COMPENSATE, pathAnnotation, method.getAnnotation(Compensate.class), uriPrefix) != 0) {
                paths.put(TIMELIMIT_PARAM_NAME, timeoutValue);
                if (isAsyncCompletion(method)) {
                    asyncTermination[0] = true;
                }
            }
            if (checkMethod(paths, method, COMPLETE, pathAnnotation, method.getAnnotation(Complete.class), uriPrefix) != 0) {
                paths.put(TIMELIMIT_PARAM_NAME, timeoutValue);
                if (isAsyncCompletion(method)) {
                    asyncTermination[0] = true;
                }
            }
            checkMethod(paths, method, STATUS, pathAnnotation, method.getAnnotation(Status.class), uriPrefix);
            checkMethod(paths, method, FORGET, pathAnnotation, method.getAnnotation(Forget.class), uriPrefix);
            checkMethod(paths, method, LEAVE, pathAnnotation, method.getAnnotation(Leave.class), uriPrefix);
            checkMethod(paths, method, AFTER, pathAnnotation, method.getAnnotation(AfterLRA.class), uriPrefix);
        }
    });
    if (asyncTermination[0] && !paths.containsKey(STATUS) && !paths.containsKey(FORGET)) {
        String logMsg = LRALogger.i18nLogger.error_asyncTerminationBeanMissStatusAndForget(compensatorClass);
        LRALogger.logger.warn(logMsg);
        throw new WebApplicationException(Response.status(BAD_REQUEST).entity(logMsg).build());
    }
    StringBuilder linkHeaderValue = new StringBuilder();
    if (paths.size() != 0) {
        paths.forEach((k, v) -> makeLink(linkHeaderValue, null, k, v));
        paths.put(LINK_TEXT, linkHeaderValue.toString());
    }
    return paths;
}
Also used : Path(javax.ws.rs.Path) Status(org.eclipse.microprofile.lra.annotation.Status) LRAStatus(org.eclipse.microprofile.lra.annotation.LRAStatus) AfterLRA(org.eclipse.microprofile.lra.annotation.AfterLRA) WebApplicationException(javax.ws.rs.WebApplicationException) HashMap(java.util.HashMap) Forget(org.eclipse.microprofile.lra.annotation.Forget) Leave(org.eclipse.microprofile.lra.annotation.ws.rs.Leave) URI(java.net.URI)

Example 2 with Status

use of org.eclipse.microprofile.lra.annotation.Status 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 WebApplicationException (javax.ws.rs.WebApplicationException)2 AfterLRA (org.eclipse.microprofile.lra.annotation.AfterLRA)2 LRAStatus (org.eclipse.microprofile.lra.annotation.LRAStatus)2 Status (org.eclipse.microprofile.lra.annotation.Status)2 LRAParticipant (io.narayana.lra.client.internal.proxy.nonjaxrs.LRAParticipant)1 UnsupportedEncodingException (java.io.UnsupportedEncodingException)1 Method (java.lang.reflect.Method)1 URISyntaxException (java.net.URISyntaxException)1 HashMap (java.util.HashMap)1 NotFoundException (javax.ws.rs.NotFoundException)1 Path (javax.ws.rs.Path)1 ProcessingException (javax.ws.rs.ProcessingException)1 Response (javax.ws.rs.core.Response)1 Compensate (org.eclipse.microprofile.lra.annotation.Compensate)1 Complete (org.eclipse.microprofile.lra.annotation.Complete)1 Forget (org.eclipse.microprofile.lra.annotation.Forget)1 LRA (org.eclipse.microprofile.lra.annotation.ws.rs.LRA)1 Leave (org.eclipse.microprofile.lra.annotation.ws.rs.Leave)1