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

io.smallrye.faulttolerance.config.FaultToleranceOperation Maven / Gradle / Ivy

There is a newer version: 6.6.2
Show newest version
/*
 * Copyright 2017 Red Hat, Inc, and individual contributors.
 *
 * Licensed 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 io.smallrye.faulttolerance.config;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.StringJoiner;
import java.util.concurrent.Future;
import java.util.function.BiFunction;
import java.util.function.Function;

import javax.enterprise.inject.spi.AnnotatedMethod;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.faulttolerance.Asynchronous;
import org.eclipse.microprofile.faulttolerance.Bulkhead;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.Retry;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException;

import io.smallrye.common.annotation.Blocking;
import io.smallrye.common.annotation.NonBlocking;
import io.smallrye.faulttolerance.AsyncTypes;
import io.smallrye.reactive.converters.ReactiveTypeConverter;

/**
 * Fault tolerance operation metadata.
 *
 * @author Martin Kouba
 */
public class FaultToleranceOperation {

    public static FaultToleranceOperation of(AnnotatedMethod annotatedMethod) {
        return new FaultToleranceOperation(annotatedMethod.getDeclaringType().getJavaClass(), annotatedMethod.getJavaMember(),
                isAsync(annotatedMethod),
                isBlocking(annotatedMethod) || isNonBlocking(annotatedMethod),
                isThreadOffloadRequired(annotatedMethod),
                returnType(annotatedMethod),
                getConfig(Bulkhead.class, annotatedMethod, BulkheadConfig::new),
                getConfig(CircuitBreaker.class, annotatedMethod, CircuitBreakerConfig::new),
                getConfig(Fallback.class, annotatedMethod, FallbackConfig::new),
                getConfig(Retry.class, annotatedMethod, RetryConfig::new),
                getConfig(Timeout.class, annotatedMethod, TimeoutConfig::new));
    }

    public static FaultToleranceOperation of(Class beanClass, Method method) {
        return new FaultToleranceOperation(beanClass, method,
                isAsync(method, beanClass),
                isBlocking(method, beanClass) || isNonBlocking(method, beanClass),
                isThreadOffloadRequired(method, beanClass),
                returnType(method),
                getConfig(Bulkhead.class, beanClass, method, BulkheadConfig::new),
                getConfig(CircuitBreaker.class, beanClass, method, CircuitBreakerConfig::new),
                getConfig(Fallback.class, beanClass, method, FallbackConfig::new),
                getConfig(Retry.class, beanClass, method, RetryConfig::new),
                getConfig(Timeout.class, beanClass, method, TimeoutConfig::new));
    }

    private final Class beanClass;

    private final Method method;

    // whether @Asynchronous is present
    private final boolean async;

    // whether @Blocking or @NonBlocking is present
    private final boolean additionalAsync;

    // whether thread offload is required based on presence or absence of @Blocking and @NonBlocking
    // if the guarded method doesn't return CompletionStage, this value is meaningless
    private final boolean threadOffloadRequired;

    private final Class returnType;

    private final BulkheadConfig bulkhead;

    private final CircuitBreakerConfig circuitBreaker;

    private final FallbackConfig fallback;

    private final RetryConfig retry;

    private final TimeoutConfig timeout;

    private FaultToleranceOperation(Class beanClass,
            Method method,
            boolean async,
            boolean additionalAsync,
            boolean threadOffloadRequired,
            Class returnType,
            BulkheadConfig bulkhead,
            CircuitBreakerConfig circuitBreaker,
            FallbackConfig fallback,
            RetryConfig retry,
            TimeoutConfig timeout) {
        this.beanClass = beanClass;
        this.method = method;
        this.async = async;
        this.additionalAsync = additionalAsync;
        this.threadOffloadRequired = threadOffloadRequired;
        this.returnType = returnType;
        this.bulkhead = bulkhead;
        this.circuitBreaker = circuitBreaker;
        this.fallback = fallback;
        this.retry = retry;
        this.timeout = timeout;
    }

    public boolean isAsync() {
        return async;
    }

    public boolean isAdditionalAsync() {
        return additionalAsync;
    }

    public boolean isThreadOffloadRequired() {
        return threadOffloadRequired;
    }

    public Class getReturnType() {
        return returnType;
    }

    public boolean hasBulkhead() {
        return bulkhead != null;
    }

    public BulkheadConfig getBulkhead() {
        return bulkhead;
    }

    public CircuitBreakerConfig getCircuitBreaker() {
        return circuitBreaker;
    }

    public boolean hasCircuitBreaker() {
        return circuitBreaker != null;
    }

    public FallbackConfig getFallback() {
        return fallback;
    }

    public boolean hasFallback() {
        return fallback != null;
    }

    public RetryConfig getRetry() {
        return retry;
    }

    public boolean hasRetry() {
        return retry != null;
    }

    public TimeoutConfig getTimeout() {
        return timeout;
    }

    public boolean hasTimeout() {
        return timeout != null;
    }

    public Method getMethod() {
        return method;
    }

    public Class getBeanClass() {
        return beanClass;
    }

    public boolean isLegitimate() {
        // @Blocking and @NonBlocking alone do _not_ trigger the fault tolerance interceptor,
        // only in combination with other fault tolerance annotations
        return async || bulkhead != null || circuitBreaker != null || fallback != null || retry != null || timeout != null;
    }

    public boolean isValid() {
        try {
            validate();
            return true;
        } catch (FaultToleranceDefinitionException e) {
            return false;
        }
    }

    /**
     * Throws {@link FaultToleranceDefinitionException} if validation fails.
     */
    public void validate() {
        if (async && !isAcceptableAsyncReturnType(method.getReturnType())) {
            throw new FaultToleranceDefinitionException("Invalid @Asynchronous on " + method
                    + ": must return java.util.concurrent.Future or " + describeAsyncReturnTypes());
        }
        if (additionalAsync && !AsyncTypes.isKnown(method.getReturnType())) {
            throw new FaultToleranceDefinitionException("Invalid @Blocking/@NonBlocking on " + method
                    + ": must return " + describeAsyncReturnTypes());
        }
        if (bulkhead != null) {
            bulkhead.validate();
        }
        if (circuitBreaker != null) {
            circuitBreaker.validate();
        }
        if (fallback != null) {
            fallback.validate();
        }
        if (retry != null) {
            retry.validate();
        }
        if (timeout != null) {
            timeout.validate();
        }
    }

    private boolean isAcceptableAsyncReturnType(Class returnType) {
        return Future.class.equals(returnType) || AsyncTypes.isKnown(returnType);
    }

    @Override
    public String toString() {
        return "FaultToleranceOperation [beanClass=" + beanClass + ", method=" + method.toGenericString() + "]";
    }

    private static String describeAsyncReturnTypes() {
        StringJoiner result = new StringJoiner(" or ");
        for (ReactiveTypeConverter converter : AsyncTypes.allKnown()) {
            result.add(converter.type().getName());
        }
        return result.toString();
    }

    private static Class returnType(Method annotatedMethod) {
        return annotatedMethod.getReturnType();
    }

    private static Class returnType(AnnotatedMethod annotatedMethod) {
        return annotatedMethod.getJavaMember().getReturnType();
    }

    private static boolean isAsync(Method method, Class beanClass) {
        return getConfigStatus(Asynchronous.class, method) && isAnnotated(Asynchronous.class, method, beanClass);
    }

    private static boolean isAsync(AnnotatedMethod method) {
        return getConfigStatus(Asynchronous.class, method.getJavaMember()) && isAnnotated(Asynchronous.class, method);
    }

    private static boolean isBlocking(Method method, Class beanClass) {
        return getConfigStatus(Blocking.class, method) && isAnnotated(Blocking.class, method, beanClass);
    }

    private static boolean isBlocking(AnnotatedMethod method) {
        return getConfigStatus(Blocking.class, method.getJavaMember()) && isAnnotated(Blocking.class, method);
    }

    private static boolean isNonBlocking(Method method, Class beanClass) {
        return getConfigStatus(NonBlocking.class, method) && isAnnotated(NonBlocking.class, method, beanClass);
    }

    private static boolean isNonBlocking(AnnotatedMethod method) {
        return getConfigStatus(NonBlocking.class, method.getJavaMember()) && isAnnotated(NonBlocking.class, method);
    }

    private static > C getConfig(Class annotationType,
            AnnotatedMethod annotatedMethod, Function, C> function) {
        if (getConfigStatus(annotationType, annotatedMethod.getJavaMember()) && isAnnotated(annotationType, annotatedMethod)) {
            return function.apply(annotatedMethod);
        }
        return null;
    }

    private static > C getConfig(Class annotationType, Class beanClass,
            Method method, BiFunction, Method, C> function) {

        if (getConfigStatus(annotationType, method) && isAnnotated(annotationType, method, beanClass)) {
            return function.apply(beanClass, method);
        }
        return null;
    }

    private static  boolean getConfigStatus(Class annotationType, Method method) {
        Config config = ConfigProvider.getConfig();
        final String undefined = "undefined";
        String onMethod = config.getOptionalValue(method.getDeclaringClass().getName() +
                "/" + method.getName() + "/" + annotationType.getSimpleName() + "/enabled", String.class).orElse(undefined);
        String onClass = config.getOptionalValue(method.getDeclaringClass().getName() +
                "/" + annotationType.getSimpleName() + "/enabled", String.class).orElse(undefined);
        String onGlobal = config.getOptionalValue(annotationType.getSimpleName() + "/enabled", String.class).orElse(undefined);
        boolean returnConfig = !annotationType.equals(Fallback.class)
                ? config.getOptionalValue("MP_Fault_Tolerance_NonFallback_Enabled", Boolean.class).orElse(true)
                : true;

        if (!undefined.equals(onMethod)) {
            returnConfig = Boolean.parseBoolean(onMethod);
        } else if (!undefined.equals(onClass)) {
            returnConfig = Boolean.parseBoolean(onClass);
        } else if (!undefined.equals(onGlobal)) {
            returnConfig = Boolean.parseBoolean(onGlobal);
        }
        return returnConfig;
    }

    private static  boolean isAnnotated(Class annotationType, AnnotatedMethod annotatedMethod) {
        return annotatedMethod.isAnnotationPresent(annotationType)
                || annotatedMethod.getDeclaringType().isAnnotationPresent(annotationType);
    }

    private static  boolean isAnnotated(Class annotationType, Method method, Class beanClass) {
        if (method.isAnnotationPresent(annotationType)) {
            return true;
        }
        while (beanClass != null) {
            if (beanClass.isAnnotationPresent(annotationType)) {
                return true;
            }
            beanClass = beanClass.getSuperclass();
        }
        return false;
    }

    // @Blocking and @NonBlocking can currently only be used on methods, but that will likely change,
    // so we'd better define meaning for class annotations as well
    // the result of isThreadOffloadRequired only makes sense if the method in question returns CompletionStage

    private static boolean isThreadOffloadRequired(AnnotatedMethod method) {
        if (method.isAnnotationPresent(Blocking.class)) {
            return true;
        }
        if (method.isAnnotationPresent(NonBlocking.class)) {
            return false;
        }

        if (method.getDeclaringType().isAnnotationPresent(Blocking.class)) {
            return true;
        }
        if (method.getDeclaringType().isAnnotationPresent(NonBlocking.class)) {
            return false;
        }

        // no @Blocking or @NonBlocking, we should offload to another thread as that's MP FT default
        return true;
    }

    private static boolean isThreadOffloadRequired(Method method, Class beanClass) {
        if (method.isAnnotationPresent(Blocking.class)) {
            return true;
        }
        if (method.isAnnotationPresent(NonBlocking.class)) {
            return false;
        }

        while (beanClass != null) {
            if (beanClass.isAnnotationPresent(Blocking.class)) {
                return true;
            }
            if (beanClass.isAnnotationPresent(NonBlocking.class)) {
                return false;
            }
            beanClass = beanClass.getSuperclass();
        }

        // no @Blocking or @NonBlocking, we should offload to another thread as that's MP FT default
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy