All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.component.resilience4j.ResilienceProcessor Maven / Gradle / Ivy

There is a newer version: 4.9.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.component.resilience4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Supplier;

import io.github.resilience4j.bulkhead.Bulkhead;
import io.github.resilience4j.bulkhead.BulkheadConfig;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import io.vavr.control.Try;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Exchange;
import org.apache.camel.ExtendedExchange;
import org.apache.camel.Navigate;
import org.apache.camel.Processor;
import org.apache.camel.RuntimeExchangeException;
import org.apache.camel.api.management.ManagedAttribute;
import org.apache.camel.api.management.ManagedOperation;
import org.apache.camel.api.management.ManagedResource;
import org.apache.camel.spi.CircuitBreakerConstants;
import org.apache.camel.spi.IdAware;
import org.apache.camel.support.AsyncProcessorSupport;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of Circuit Breaker EIP using resilience4j.
 */
@ManagedResource(description = "Managed Resilience Processor")
public class ResilienceProcessor extends AsyncProcessorSupport implements CamelContextAware, Navigate, org.apache.camel.Traceable, IdAware {

    private static final Logger LOG = LoggerFactory.getLogger(ResilienceProcessor.class);

    private volatile CircuitBreaker circuitBreaker;
    private CamelContext camelContext;
    private String id;
    private final CircuitBreakerConfig circuitBreakerConfig;
    private final BulkheadConfig bulkheadConfig;
    private final TimeLimiterConfig timeLimiterConfig;
    private final Processor processor;
    private final Processor fallback;
    private boolean shutdownExecutorService;
    private ExecutorService executorService;

    public ResilienceProcessor(CircuitBreakerConfig circuitBreakerConfig, BulkheadConfig bulkheadConfig, TimeLimiterConfig timeLimiterConfig, Processor processor,
                               Processor fallback) {
        this.circuitBreakerConfig = circuitBreakerConfig;
        this.bulkheadConfig = bulkheadConfig;
        this.timeLimiterConfig = timeLimiterConfig;
        this.processor = processor;
        this.fallback = fallback;
    }

    @Override
    public CamelContext getCamelContext() {
        return camelContext;
    }

    @Override
    public void setCamelContext(CamelContext camelContext) {
        this.camelContext = camelContext;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public void setId(String id) {
        this.id = id;
    }

    public CircuitBreaker getCircuitBreaker() {
        return circuitBreaker;
    }

    public void setCircuitBreaker(CircuitBreaker circuitBreaker) {
        this.circuitBreaker = circuitBreaker;
    }

    public boolean isShutdownExecutorService() {
        return shutdownExecutorService;
    }

    public void setShutdownExecutorService(boolean shutdownExecutorService) {
        this.shutdownExecutorService = shutdownExecutorService;
    }

    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    @Override
    public String getTraceLabel() {
        return "resilience4j";
    }

    @ManagedAttribute(description = "Returns the current failure rate in percentage.")
    public float getFailureRate() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getFailureRate();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current percentage of calls which were slower than a certain threshold.")
    public float getSlowCallRate() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getSlowCallRate();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current total number of calls which were slower than a certain threshold.")
    public int getNumberOfSlowCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfSlowCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current number of successful calls which were slower than a certain threshold.")
    public int getNumberOfSlowSuccessfulCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfSlowCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current number of failed calls which were slower than a certain threshold.")
    public int getNumberOfSlowFailedCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfSlowFailedCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current total number of buffered calls in the ring buffer.")
    public int getNumberOfBufferedCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfBufferedCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current number of failed buffered calls in the ring buffer.")
    public int getNumberOfFailedCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfFailedCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current number of successful buffered calls in the ring buffer")
    public int getNumberOfSuccessfulCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfSuccessfulCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current number of not permitted calls, when the state is OPEN.")
    public long getNumberOfNotPermittedCalls() {
        if (circuitBreaker != null) {
            return circuitBreaker.getMetrics().getNumberOfNotPermittedCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute(description = "Returns the current state of the circuit breaker")
    public String getCircuitBreakerState() {
        if (circuitBreaker != null) {
            return circuitBreaker.getState().name();
        } else {
            return null;
        }
    }

    @ManagedOperation(description = "Transitions the circuit breaker to CLOSED state.")
    public void transitionToCloseState() {
        if (circuitBreaker != null) {
            circuitBreaker.transitionToClosedState();
        }
    }

    @ManagedOperation(description = "Transitions the circuit breaker to OPEN state.")
    public void transitionToOpenState() {
        if (circuitBreaker != null) {
            circuitBreaker.transitionToOpenState();
        }
    }

    @ManagedOperation(description = "Transitions the circuit breaker to HALF_OPEN state.")
    public void transitionToHalfOpenState() {
        if (circuitBreaker != null) {
            circuitBreaker.transitionToHalfOpenState();
        }
    }

    @ManagedOperation(description = "Transitions the state machine to a FORCED_OPEN state, stopping state transition, metrics and event publishing.")
    public void transitionToForcedOpenState() {
        if (circuitBreaker != null) {
            circuitBreaker.transitionToForcedOpenState();
        }
    }

    @ManagedAttribute
    public float getCircuitBreakerFailureRateThreshold() {
        return circuitBreakerConfig.getFailureRateThreshold();
    }

    @ManagedAttribute
    public float getCircuitBreakerSlowCallRateThreshold() {
        return circuitBreakerConfig.getSlowCallRateThreshold();
    }

    @ManagedAttribute
    public int getCircuitBreakerMinimumNumberOfCalls() {
        return circuitBreakerConfig.getMinimumNumberOfCalls();
    }

    @ManagedAttribute
    public int getCircuitBreakerPermittedNumberOfCallsInHalfOpenState() {
        return circuitBreakerConfig.getPermittedNumberOfCallsInHalfOpenState();
    }

    @ManagedAttribute
    public int getCircuitBreakerSlidingWindowSize() {
        return circuitBreakerConfig.getSlidingWindowSize();
    }

    @ManagedAttribute
    public String getCircuitBreakerSlidingWindowType() {
        return circuitBreakerConfig.getSlidingWindowType().name();
    }

    @ManagedAttribute
    public long getCircuitBreakerWaitDurationInOpenState() {
        return circuitBreakerConfig.getWaitDurationInOpenState().getSeconds();
    }

    @ManagedAttribute
    public boolean isCircuitBreakerTransitionFromOpenToHalfOpenEnabled() {
        return circuitBreakerConfig.isAutomaticTransitionFromOpenToHalfOpenEnabled();
    }

    @ManagedAttribute
    public boolean isCircuitBreakerWritableStackTraceEnabled() {
        return circuitBreakerConfig.isWritableStackTraceEnabled();
    }

    @ManagedAttribute
    public boolean isBulkheadEnabled() {
        return bulkheadConfig != null;
    }

    @ManagedAttribute
    public int getBulkheadMaxConcurrentCalls() {
        if (bulkheadConfig != null) {
            return bulkheadConfig.getMaxConcurrentCalls();
        } else {
            return 0;
        }
    }

    @ManagedAttribute()
    public long getBulkheadMaxWaitDuration() {
        if (bulkheadConfig != null) {
            return bulkheadConfig.getMaxWaitDuration().toMillis();
        } else {
            return 0;
        }
    }

    @ManagedAttribute
    public boolean isTimeoutEnabled() {
        return timeLimiterConfig != null;
    }

    @ManagedAttribute
    public long getTimeoutDuration() {
        if (timeLimiterConfig != null) {
            return timeLimiterConfig.getTimeoutDuration().toMillis();
        } else {
            return 0;
        }
    }

    @Override
    public List next() {
        if (!hasNext()) {
            return null;
        }
        List answer = new ArrayList<>();
        answer.add(processor);
        if (fallback != null) {
            answer.add(fallback);
        }
        return answer;
    }

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public boolean process(Exchange exchange, AsyncCallback callback) {
        // run this as if we run inside try .. catch so there is no regular
        // Camel error handler
        exchange.setProperty(Exchange.TRY_ROUTE_BLOCK, true);

        Callable task;

        if (timeLimiterConfig != null) {
            // timeout handling is more complex with thread-pools

            TimeLimiter tl = TimeLimiter.of(id, timeLimiterConfig);
            Supplier> futureSupplier;
            if (executorService == null) {
                futureSupplier = () -> CompletableFuture.supplyAsync(() -> processInCopy(exchange));
            } else {
                futureSupplier = () -> CompletableFuture.supplyAsync(() -> processInCopy(exchange), executorService);
            }
            task = TimeLimiter.decorateFutureSupplier (tl, futureSupplier);
        } else {
            task = new CircuitBreakerTask(() -> processInCopy(exchange));
        }

        if (bulkheadConfig != null) {
            Bulkhead bh = Bulkhead.of(id, bulkheadConfig);
            task = Bulkhead.decorateCallable(bh, task);
        }

        task = CircuitBreaker.decorateCallable(circuitBreaker, task);

        Function fallbackTask = new CircuitBreakerFallbackTask(this.fallback, exchange);
        Try.ofCallable(task).recover(fallbackTask).andFinally(() -> callback.done(false)).get();
        return false;
    }

    private Exchange processInCopy(Exchange exchange) {
        try {
            LOG.debug("Running processor: {} with exchange: {}", processor, exchange);
            // prepare a copy of exchange so downstream processors don't
            // cause side-effects if they mutate the exchange
            // in case timeout processing and continue with the fallback etc
            Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, false, false);
            // process the processor until its fully done
            processor.process(copy);
            if (copy.getException() != null) {
                exchange.setException(copy.getException());
            } else {
                // copy the result as its regarded as success
                ExchangeHelper.copyResults(exchange, copy);
                exchange.setProperty(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, true);
                exchange.setProperty(CircuitBreakerConstants.RESPONSE_FROM_FALLBACK, false);
            }
        } catch (Throwable e) {
            exchange.setException(e);
        }
        if (exchange.getException() != null) {
            // throw exception so resilient4j know it was a failure
            throw RuntimeExchangeException.wrapRuntimeException(exchange.getException());
        }
        return exchange;
    }

    @Override
    protected void doStart() throws Exception {
        ObjectHelper.notNull(camelContext, "CamelContext", this);
        if (circuitBreaker == null) {
            circuitBreaker = CircuitBreaker.of(id, circuitBreakerConfig);
        }
    }

    @Override
    protected void doStop() throws Exception {
        if (shutdownExecutorService && executorService != null) {
            getCamelContext().getExecutorServiceManager().shutdownNow(executorService);
        }
    }

    private static final class CircuitBreakerTask implements Callable {

        Supplier supplier;

        public CircuitBreakerTask(Supplier supplier) {
            this.supplier = supplier;
        }

        @Override
        public Exchange call() throws Exception {
            return supplier.get();
        }
    }

    private static final class CircuitBreakerFallbackTask implements Function {

        private final Processor processor;
        private final Exchange exchange;

        private CircuitBreakerFallbackTask(Processor processor, Exchange exchange) {
            this.processor = processor;
            this.exchange = exchange;
        }

        @Override
        public Exchange apply(Throwable throwable) {
            if (processor == null) {
                if (throwable instanceof TimeoutException) {
                    // the circuit breaker triggered a timeout (and there is no
                    // fallback) so lets mark the exchange as failed
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, false);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_FROM_FALLBACK, false);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_SHORT_CIRCUITED, false);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_TIMED_OUT, true);
                    exchange.setException(throwable);
                    return exchange;
                } else if (throwable instanceof CallNotPermittedException) {
                    // the circuit breaker triggered a call rejected
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, false);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_FROM_FALLBACK, false);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_SHORT_CIRCUITED, true);
                    exchange.setProperty(CircuitBreakerConstants.RESPONSE_REJECTED, true);
                    throw RuntimeExchangeException.wrapRuntimeException(throwable);
                } else {
                    // throw exception so resilient4j know it was a failure
                    throw RuntimeExchangeException.wrapRuntimeException(throwable);
                }
            }

            // fallback route is handling the exception so its short-circuited
            exchange.setProperty(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, false);
            exchange.setProperty(CircuitBreakerConstants.RESPONSE_FROM_FALLBACK, true);
            exchange.setProperty(CircuitBreakerConstants.RESPONSE_SHORT_CIRCUITED, true);

            // store the last to endpoint as the failure endpoint
            if (exchange.getProperty(Exchange.FAILURE_ENDPOINT) == null) {
                exchange.setProperty(Exchange.FAILURE_ENDPOINT, exchange.getProperty(Exchange.TO_ENDPOINT));
            }
            // give the rest of the pipeline another chance
            exchange.setProperty(Exchange.EXCEPTION_HANDLED, true);
            exchange.setProperty(Exchange.EXCEPTION_CAUGHT, exchange.getException());
            exchange.setRouteStop(false);
            exchange.setException(null);
            // and we should not be regarded as exhausted as we are in a try ..
            // catch block
            exchange.adapt(ExtendedExchange.class).setRedeliveryExhausted(false);
            // run the fallback processor
            try {
                LOG.debug("Running fallback: {} with exchange: {}", processor, exchange);
                // process the fallback until its fully done
                processor.process(exchange);
                LOG.debug("Running fallback: {} with exchange: {} done", processor, exchange);
            } catch (Exception e) {
                exchange.setException(e);
            }

            return exchange;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy