Search in sources :

Example 1 with ParticipantStatus

use of org.eclipse.microprofile.lra.annotation.ParticipantStatus in project helidon by oracle.

the class Participant method sendComplete.

boolean sendComplete(Lra lra) {
    Optional<URI> endpointURI = getCompleteURI();
    for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT; ) {
        if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING))
            return false;
        LOGGER.log(Level.FINE, () -> "Sending complete, sync retry: " + i.get() + ", status: " + status.get().name() + " statusUri: " + getStatusURI().map(URI::toASCIIString).orElse(null));
        WebClientResponse response = null;
        try {
            if (status.get().isFinal()) {
                return true;
            // call for client status only on retries and when status uri is known
            } else if (!status.get().equals(Status.ACTIVE) && getStatusURI().isPresent()) {
                // If the participant does not support idempotency then it MUST be able to report its status
                // by annotating one of the methods with the @Status annotation which should report the status
                // in case we can't retrieve status from participant just retry n times
                ParticipantStatus reportedClientStatus = retrieveStatus(lra, Completing).orElse(null);
                if (reportedClientStatus == Completed) {
                    LOGGER.log(Level.INFO, "Participant reports it is completed.");
                    status.set(Status.COMPLETED);
                    return true;
                } else if (reportedClientStatus == FailedToComplete) {
                    LOGGER.log(Level.INFO, "Participant reports it failed to complete.");
                    status.set(Status.FAILED_TO_COMPLETE);
                    return true;
                } else if (reportedClientStatus == Active) {
                // last call didn't reach participant, try call again
                } else if (reportedClientStatus == Completing) {
                    LOGGER.log(Level.INFO, "Participant reports it is still completing.");
                    status.set(Status.CLIENT_COMPLETING);
                    return false;
                } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
                    LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.", new Object[] { RETRY_CNT });
                    status.set(Status.FAILED_TO_COMPLETE);
                    return true;
                } else {
                    // Unknown status, lets try in next recovery cycle
                    return false;
                }
            }
            response = getWebClient(endpointURI.get()).put().headers(lra.headers()).submit(LRAStatus.Closed.name()).await(timeout, TimeUnit.MILLISECONDS);
            switch(response.status().code()) {
                // complete or compensated
                case 200:
                case 410:
                    status.set(Status.COMPLETED);
                    return true;
                // retryable
                case 202:
                    // Still completing, check with @Status later
                    this.status.set(Status.CLIENT_COMPLETING);
                    return false;
                case 409:
                case 404:
                case 503:
                default:
                    throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
            }
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, e, () -> "Can't reach participant's complete endpoint: " + endpointURI.map(URI::toASCIIString).orElse("unknown"));
            if (remainingCloseAttempts.decrementAndGet() <= 0) {
                LOGGER.log(Level.WARNING, "Failed to complete participant of LRA {0} {1} {2}", new Object[] { lra.lraId(), this.getCompleteURI(), e.getMessage() });
                status.set(Status.FAILED_TO_COMPLETE);
            } else {
                status.set(Status.COMPLETING);
            }
        } finally {
            Optional.ofNullable(response).ifPresent(WebClientResponse::close);
            sendingStatus.set(SendingStatus.NOT_SENDING);
        }
    }
    return false;
}
Also used : WebClientResponse(io.helidon.webclient.WebClientResponse) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) URI(java.net.URI)

Example 2 with ParticipantStatus

use of org.eclipse.microprofile.lra.annotation.ParticipantStatus in project helidon by oracle.

the class Participant method retrieveStatus.

Optional<ParticipantStatus> retrieveStatus(Lra lra, ParticipantStatus inProgressStatus) {
    URI statusURI = this.getStatusURI().get();
    try {
        WebClientResponse response = getWebClient(statusURI).get().headers(h -> {
            // Dont send parent!
            h.add(LRA_HTTP_CONTEXT_HEADER, lra.lraContextId());
            h.add(LRA_HTTP_RECOVERY_HEADER, lra.lraContextId() + "/recovery");
            h.add(LRA_HTTP_ENDED_CONTEXT_HEADER, lra.lraContextId());
            return h;
        }).request().await(timeout, TimeUnit.MILLISECONDS);
        int code = response.status().code();
        switch(code) {
            case 202:
                return Optional.of(inProgressStatus);
            case // GONE
            410:
                // Completing -> FailedToComplete ...
                return status.get().failedFinalStatus();
            case 503:
            case 500:
                throw new IllegalStateException(String.format("Client reports unexpected status %s %s, " + "current participant state is %s, " + "lra: %s " + "status uri: %s", code, response.content().as(String.class), status.get(), lra.lraId(), statusURI.toASCIIString()));
            default:
                ParticipantStatus reportedStatus = valueOf(response.content().as(String.class).await());
                Status currentStatus = status.get();
                if (currentStatus.validateNextStatus(reportedStatus)) {
                    return Optional.of(reportedStatus);
                } else {
                    LOGGER.log(Level.WARNING, "Client reports unexpected status {0} {1}, " + "current participant state is {2}, " + "lra: {3} " + "status uri: {4}", new Object[] { code, reportedStatus, currentStatus, lra.lraId(), statusURI.toASCIIString() });
                    return Optional.empty();
                }
        }
    } catch (Exception e) {
        LOGGER.log(Level.WARNING, "Error when getting participant status. " + statusURI, e);
        // skip dependent compensation call, another retry with status call might be luckier
        throw e;
    }
}
Also used : ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) LRAStatus(org.eclipse.microprofile.lra.annotation.LRAStatus) WebClientResponse(io.helidon.webclient.WebClientResponse) ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) URI(java.net.URI)

Example 3 with ParticipantStatus

use of org.eclipse.microprofile.lra.annotation.ParticipantStatus in project helidon by oracle.

the class NonJaxRsResource method createNonJaxRsParticipantResource.

Service createNonJaxRsParticipantResource() {
    return rules -> rules.any("/{type}/{fqdn}/{methodName}", (req, res) -> {
        LOGGER.log(Level.FINE, () -> "Non JAX-RS LRA resource " + req.method().name() + " " + req.absoluteUri());
        RequestHeaders headers = req.headers();
        HttpRequest.Path path = req.path();
        URI lraId = headers.first(LRA_HTTP_CONTEXT_HEADER).or(() -> headers.first(LRA_HTTP_ENDED_CONTEXT_HEADER)).map(URI::create).orElse(null);
        URI parentId = headers.first(LRA_HTTP_PARENT_CONTEXT_HEADER).map(URI::create).orElse(null);
        PropagatedHeaders propagatedHeaders = participantService.prepareCustomHeaderPropagation(headers.toMap());
        String fqdn = path.param("fqdn");
        String method = path.param("methodName");
        String type = path.param("type");
        switch(type) {
            case "compensate":
            case "complete":
                Single.<Optional<?>>empty().observeOn(exec).onCompleteResumeWithSingle(o -> participantService.invoke(fqdn, method, lraId, parentId, propagatedHeaders)).forSingle(result -> result.ifPresentOrElse(r -> sendResult(res, r), res::send)).exceptionallyAccept(t -> sendError(lraId, req, res, t));
                break;
            case "afterlra":
                req.content().as(String.class).map(LRAStatus::valueOf).observeOn(exec).flatMapSingle(s -> Single.defer(() -> participantService.invoke(fqdn, method, lraId, s, propagatedHeaders))).onComplete(res::send).onError(t -> sendError(lraId, req, res, t)).ignoreElement();
                break;
            case "status":
                Single.<Optional<?>>empty().observeOn(exec).onCompleteResumeWithSingle(o -> participantService.invoke(fqdn, method, lraId, null, propagatedHeaders)).forSingle(result -> result.ifPresentOrElse(r -> sendResult(res, r), // or in the case of non-JAX-RS method returning ParticipantStatus null.
                () -> res.status(Response.Status.GONE.getStatusCode()).send())).exceptionallyAccept(t -> sendError(lraId, req, res, t));
                break;
            case "forget":
                Single.<Optional<?>>empty().observeOn(exec).onCompleteResumeWithSingle(o -> participantService.invoke(fqdn, method, lraId, parentId, propagatedHeaders)).onComplete(res::send).onError(t -> sendError(lraId, req, res, t)).ignoreElement();
                break;
            default:
                LOGGER.severe(() -> "Unexpected non Jax-Rs LRA compensation type " + type + ": " + req.absoluteUri());
                res.status(404).send();
                break;
        }
    });
}
Also used : LRAResponse(org.eclipse.microprofile.lra.LRAResponse) LRA_HTTP_CONTEXT_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER) PropagatedHeaders(io.helidon.lra.coordinator.client.PropagatedHeaders) LRA_HTTP_ENDED_CONTEXT_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_ENDED_CONTEXT_HEADER) Supplier(java.util.function.Supplier) Level(java.util.logging.Level) Response(jakarta.ws.rs.core.Response) Map(java.util.Map) ServerResponse(io.helidon.webserver.ServerResponse) Single(io.helidon.common.reactive.Single) URI(java.net.URI) Service(io.helidon.webserver.Service) Reflected(io.helidon.common.Reflected) ExecutorService(java.util.concurrent.ExecutorService) LRA_HTTP_PARENT_CONTEXT_HEADER(org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER) ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) Config(io.helidon.config.Config) PreDestroy(jakarta.annotation.PreDestroy) Logger(java.util.logging.Logger) RequestHeaders(io.helidon.webserver.RequestHeaders) Collectors(java.util.stream.Collectors) ServerRequest(io.helidon.webserver.ServerRequest) TimeUnit(java.util.concurrent.TimeUnit) LRAStatus(org.eclipse.microprofile.lra.annotation.LRAStatus) Optional(java.util.Optional) ConfigProperty(org.eclipse.microprofile.config.inject.ConfigProperty) Inject(jakarta.inject.Inject) ThreadPoolSupplier(io.helidon.common.configurable.ThreadPoolSupplier) HttpRequest(io.helidon.common.http.HttpRequest) HttpRequest(io.helidon.common.http.HttpRequest) PropagatedHeaders(io.helidon.lra.coordinator.client.PropagatedHeaders) Optional(java.util.Optional) RequestHeaders(io.helidon.webserver.RequestHeaders) URI(java.net.URI)

Example 4 with ParticipantStatus

use of org.eclipse.microprofile.lra.annotation.ParticipantStatus in project helidon by oracle.

the class Participant method sendCancel.

boolean sendCancel(Lra lra) {
    Optional<URI> endpointURI = getCompensateURI();
    for (AtomicInteger i = new AtomicInteger(0); i.getAndIncrement() < SYNCHRONOUS_RETRY_CNT; ) {
        if (!sendingStatus.compareAndSet(SendingStatus.NOT_SENDING, SendingStatus.SENDING))
            return false;
        if (!compensateCalled.compareAndSet(CompensateStatus.NOT_SENT, CompensateStatus.SENDING))
            return false;
        LOGGER.log(Level.FINE, () -> "Sending compensate, sync retry: " + i.get() + ", status: " + status.get().name() + " statusUri: " + getStatusURI().map(URI::toASCIIString).orElse(null));
        WebClientResponse response = null;
        try {
            // call for client status only on retries and when status uri is known
            if (!status.get().equals(Status.ACTIVE) && getStatusURI().isPresent()) {
                // If the participant does not support idempotency then it MUST be able to report its status
                // by annotating one of the methods with the @Status annotation which should report the status
                // in case we can't retrieve status from participant just retry n times
                ParticipantStatus reportedClientStatus = retrieveStatus(lra, Compensating).orElse(null);
                if (reportedClientStatus == Compensated) {
                    LOGGER.log(Level.INFO, "Participant reports it is compensated.");
                    status.set(Status.COMPENSATED);
                    return true;
                } else if (reportedClientStatus == FailedToCompensate) {
                    LOGGER.log(Level.INFO, "Participant reports it failed to compensate.");
                    status.set(Status.FAILED_TO_COMPENSATE);
                    return true;
                } else if (reportedClientStatus == Active) {
                // last call didn't reach participant, try call again
                } else if (reportedClientStatus == Completed && lra.isChild()) {
                // completed participant can be compensated again in case of nested tx
                } else if (reportedClientStatus == Compensating) {
                    LOGGER.log(Level.INFO, "Participant reports it is still compensating.");
                    status.set(Status.CLIENT_COMPENSATING);
                    return false;
                } else if (remainingCloseAttempts.decrementAndGet() <= 0) {
                    LOGGER.log(Level.INFO, "Participant didnt report final status after {0} status call retries.", new Object[] { RETRY_CNT });
                    status.set(Status.FAILED_TO_COMPENSATE);
                    return true;
                } else {
                    // Unknown status, lets try in next recovery cycle
                    LOGGER.log(Level.INFO, "Unknown status of " + lra.lraId());
                    return false;
                }
            }
            response = getWebClient(endpointURI.get()).put().headers(lra.headers()).submit(LRAStatus.Cancelled.name()).await(timeout, TimeUnit.MILLISECONDS);
            switch(response.status().code()) {
                // complete or compensated
                case 200:
                case 410:
                    LOGGER.log(Level.INFO, "Compensated participant of LRA {0} {1}", new Object[] { lra.lraId(), this.getCompensateURI() });
                    status.set(Status.COMPENSATED);
                    compensateCalled.set(CompensateStatus.SENT);
                    return true;
                // retryable
                case 202:
                    // Still compensating, check with @Status later
                    this.status.set(Status.CLIENT_COMPENSATING);
                    return false;
                case 409:
                case 404:
                case 503:
                default:
                    throw new Exception(response.status().code() + " " + response.status().reasonPhrase());
            }
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, e, () -> "Can't reach participant's compensate endpoint: " + endpointURI.map(URI::toASCIIString).orElse("unknown"));
            if (remainingCloseAttempts.decrementAndGet() <= 0) {
                LOGGER.log(Level.WARNING, "Failed to compensate participant of LRA {0} {1} {2}", new Object[] { lra.lraId(), this.getCompensateURI(), e.getMessage() });
                status.set(Status.FAILED_TO_COMPENSATE);
            } else {
                status.set(Status.COMPENSATING);
            }
        } finally {
            Optional.ofNullable(response).ifPresent(WebClientResponse::close);
            sendingStatus.set(SendingStatus.NOT_SENDING);
            compensateCalled.compareAndSet(CompensateStatus.SENDING, CompensateStatus.NOT_SENT);
        }
    }
    return false;
}
Also used : WebClientResponse(io.helidon.webclient.WebClientResponse) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) ParticipantStatus(org.eclipse.microprofile.lra.annotation.ParticipantStatus) URI(java.net.URI)

Aggregations

URI (java.net.URI)4 ParticipantStatus (org.eclipse.microprofile.lra.annotation.ParticipantStatus)4 WebClientResponse (io.helidon.webclient.WebClientResponse)3 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)2 LRAStatus (org.eclipse.microprofile.lra.annotation.LRAStatus)2 Reflected (io.helidon.common.Reflected)1 ThreadPoolSupplier (io.helidon.common.configurable.ThreadPoolSupplier)1 HttpRequest (io.helidon.common.http.HttpRequest)1 Single (io.helidon.common.reactive.Single)1 Config (io.helidon.config.Config)1 PropagatedHeaders (io.helidon.lra.coordinator.client.PropagatedHeaders)1 RequestHeaders (io.helidon.webserver.RequestHeaders)1 ServerRequest (io.helidon.webserver.ServerRequest)1 ServerResponse (io.helidon.webserver.ServerResponse)1 Service (io.helidon.webserver.Service)1 PreDestroy (jakarta.annotation.PreDestroy)1 Inject (jakarta.inject.Inject)1 Response (jakarta.ws.rs.core.Response)1 Map (java.util.Map)1 Optional (java.util.Optional)1