diff --git a/doc/modules/ROOT/pages/howto/begin.adoc b/doc/modules/ROOT/pages/howto/begin.adoc index 6e271fe14..7e4d4c19a 100644 --- a/doc/modules/ROOT/pages/howto/begin.adoc +++ b/doc/modules/ROOT/pages/howto/begin.adoc @@ -114,6 +114,9 @@ For example: [source,properties] ---- +smallrye.faulttolerance."com.example.MyService/hello".retry.max-retries=5 <1> + +# alternatively, a specification-defined property can be used com.example.MyService/hello/Retry/maxRetries=5 <1> ---- <1> Even though the `@Retry` annotation says that `maxRetries` should be 10, this configuration takes precedence. diff --git a/doc/modules/ROOT/pages/integration/metrics.adoc b/doc/modules/ROOT/pages/integration/metrics.adoc index 07af188dc..fa14ba3cc 100644 --- a/doc/modules/ROOT/pages/integration/metrics.adoc +++ b/doc/modules/ROOT/pages/integration/metrics.adoc @@ -68,6 +68,6 @@ This bean is used to emit the actual metrics. === Noop -If the "noop" provider is used, metrics are completely disabled and the `MP_Fault_Tolerance_Metrics_Enabled` configuration property is ignored at runtime. +If the "noop" provider is used, metrics are completely disabled. No metrics API and implementation is required in this case. diff --git a/doc/modules/ROOT/pages/reference/config.adoc b/doc/modules/ROOT/pages/reference/config.adoc index 45d275c01..0b252765b 100644 --- a/doc/modules/ROOT/pages/reference/config.adoc +++ b/doc/modules/ROOT/pages/reference/config.adoc @@ -31,6 +31,9 @@ To change the `maxRetries` property using MicroProfile Config, set: [source,properties] ---- +smallrye.faulttolerance."com.example.MyService/hello".retry.max-retries=5 + +# alternatively, a specification-defined property can be used com.example.MyService/hello/Retry/maxRetries=5 ---- @@ -58,6 +61,9 @@ The MicroProfile Config configuration property doesn't include the method name i [source,properties] ---- +smallrye.faulttolerance."com.example.MyService".retry.max-retries=5 + +# alternatively, a specification-defined property can be used com.example.MyService/Retry/maxRetries=5 ---- @@ -76,6 +82,9 @@ For example: [source,properties] ---- +smallrye.faulttolerance.global.retry.max-retries=5 + +# alternatively, a specification-defined property can be used Retry/maxRetries=5 ---- @@ -109,6 +118,11 @@ The following configuration: [source,properties] ---- +smallrye.faulttolerance.global.retry.max-retries=30 +smallrye.faulttolerance."com.example.MyService".retry.max-retries=15 +smallrye.faulttolerance."com.example.MyService/complexHello".retry.max-retries=20 + +# alternatively, specification-defined properties can be used Retry/maxRetries=30 com.example.MyService/Retry/maxRetries=15 com.example.MyService/complexHello/Retry/maxRetries=20 @@ -126,6 +140,9 @@ It is possible to disable certain fault tolerance annotation on some method, if [source,properties] ---- +smallrye.faulttolerance."com.example.MyService/hello".retry.enabled=false + +# alternatively, a specification-defined property can be used com.example.MyService/hello/Retry/enabled=false ---- @@ -133,6 +150,9 @@ Or on some class, if the annotation is present on the class: [source,properties] ---- +smallrye.faulttolerance."com.example.MyService".retry.enabled=5 + +# alternatively, a specification-defined property can be used com.example.MyService/Retry/enabled=false ---- @@ -140,6 +160,9 @@ Or globally: [source,properties] ---- +smallrye.faulttolerance.global.retry.enabled=5 + +# alternatively, a specification-defined property can be used Retry/enabled=false ---- @@ -147,11 +170,36 @@ It is also possible to disable all fault tolerance completely: [source,properties] ---- +smallrye.faulttolerance.enabled=false + +# alternatively, a specification-defined property can be used MP_Fault_Tolerance_NonFallback_Enabled=false ---- This will leave only fallbacks enabled, all other annotations will be disabled. +== {smallrye-fault-tolerance} Configuration Properties + +As demonstrated in the examples above, {smallrye-fault-tolerance} provides its own configuration properties, in addition to the specification-defined properties. +The specification-defined properties can of course be used, but the {smallrye-fault-tolerance} configuration properties have higher priority. + +The mapping is relatively straightforward: + +- `///` moves to `smallrye.faulttolerance."/"..` +- `//` moves to `smallrye.faulttolerance.""..` +- `/` moves to `smallrye.faulttolerance.global..` + +All the `` and `` parts are changed from camel case (`BeforeRetry`, `methodName`) to kebab case (`before-retry`, `method-name`). +Two annotation members are special cased to improve consistency: + +- `Retry/durationUnit` moves to `retry.max-duration-unit`, because the value property is called `maxDuration` (`max-duration`) +- `Retry/jitterDelayUnit` moves to `retry.jitter-unit`, because the value property is called `jitter` + +Further: + +- `MP_Fault_Tolerance_NonFallback_Enabled` moves to `smallrye.faulttolerance.enabled` +- `MP_Fault_Tolerance_Metrics_Enabled` moves to `smallrye.faulttolerance.metrics.enabled` + == Links -This is described in detail in the {microprofile-fault-tolerance-url}#configuration[{microprofile-fault-tolerance} specification]. +Configuration is described in detail in the {microprofile-fault-tolerance-url}#configuration[{microprofile-fault-tolerance} specification]. diff --git a/doc/modules/ROOT/pages/reference/metrics.adoc b/doc/modules/ROOT/pages/reference/metrics.adoc index ee026c6d6..ee4bbb5f7 100644 --- a/doc/modules/ROOT/pages/reference/metrics.adoc +++ b/doc/modules/ROOT/pages/reference/metrics.adoc @@ -87,6 +87,9 @@ It is possible to completely disable fault tolerance metrics using MicroProfile [source,properties] ---- +smallrye.faulttolerance.metrics.enabled=false + +# alternatively, a specification-defined property can be used MP_Fault_Tolerance_Metrics_Enabled=false ---- @@ -104,4 +107,6 @@ smallrye.faulttolerance.opentelemetry.disabled=true smallrye.faulttolerance.micrometer.disabled=true ---- -This applies when only one metric provider is present as well as when multiple metric providers are present. +Note that setting `smallrye.faulttolerance.*.disabled` to `false` does not mean the provider is enabled unconditionally. +When that provider is not discovered or selected by the integrator, it cannot be enabled in any way. +These properties are only meant for disabling an otherwise enabled metrics provider; not the other way around. diff --git a/doc/modules/ROOT/pages/reference/programmatic-api.adoc b/doc/modules/ROOT/pages/reference/programmatic-api.adoc index 272e1866c..544e2a7a2 100644 --- a/doc/modules/ROOT/pages/reference/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/reference/programmatic-api.adoc @@ -58,10 +58,10 @@ If you don't set a configuration property, it will default to the same value the **** There's one exception to the "no external configuration" rule. -The CDI implementation looks for the well-known `MP_Fault_Tolerance_NonFallback_Enabled` configuration property in MicroProfile Config. -The standalone implementation looks for a system property with the same name, or obtains the value from custom configuration (as xref:integration/programmatic-api.adoc[described] in the integration section). +The CDI implementation looks for the `smallrye.faulttolerance.enabled` / `MP_Fault_Tolerance_NonFallback_Enabled` configuration properties in MicroProfile Config. +The standalone implementation looks for system properties with the same name, or obtains the value from custom configuration (as xref:integration/programmatic-api.adoc[described] in the integration section). -If such property exists and is set to `false`, only the fallback and thread offload fault tolerance strategies will be applied. +If at least one of these properties exists and is set to `false`, only the fallback and thread offload fault tolerance strategies will be applied. Everything else will be ignored. Note that this is somewhat different to the declarative, annotation-based API, where only fallback is retained and the `@Asynchronous` strategy is skipped as well. @@ -241,7 +241,7 @@ All event listeners registered like this must run quickly and must not throw exc == Configuration -As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no support for external configuration of fault tolerance strategies. +As mentioned above, except of `smallrye.faulttolerance.enabled` / `MP_Fault_Tolerance_NonFallback_Enabled`, there is no support for external configuration of fault tolerance strategies. This may change in the future, though possibly only in the CDI implementation. == Metrics diff --git a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/AutoConfig.java b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/AutoConfig.java index 85baf5bda..8a845295c 100644 --- a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/AutoConfig.java +++ b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/AutoConfig.java @@ -21,7 +21,16 @@ public @interface AutoConfig { /** * Whether the annotation values can be overridden by MP Config. + *

* Usually {@code true}, but there may be annotations for which that is not desirable. */ boolean configurable() default true; + + /** + * Whether the annotation values can be overridden by SmallRye Fault Tolerance-specific MP Config + * properties, in addition to the specification-defined MP Config properties. + *

+ * Usually {@code true}, but there may be annotations for which that is not desirable. + */ + boolean newConfigAllowed() default true; } diff --git a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/Config.java b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/Config.java index dc19ed8e6..b4d9044a7 100644 --- a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/Config.java +++ b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/Config.java @@ -1,10 +1,6 @@ package io.smallrye.faulttolerance.autoconfig; import java.lang.annotation.Annotation; -import java.util.Optional; - -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.faulttolerance.Fallback; public interface Config { /** @@ -38,38 +34,4 @@ public interface Config { * are guaranteed to not touch MP Config. */ void materialize(); - - // --- - - static boolean isEnabled(Class annotationType, MethodDescriptor method) { - // TODO converting strings to boolean here is inconsistent, - // but it's how SmallRye Fault Tolerance has always done it - - org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig(); - - Optional onMethod = config.getOptionalValue(method.declaringClass.getName() + - "/" + method.name + "/" + annotationType.getSimpleName() + "/enabled", String.class); - if (onMethod.isPresent()) { - return Boolean.parseBoolean(onMethod.get()); - } - - Optional onClass = config.getOptionalValue(method.declaringClass.getName() + - "/" + annotationType.getSimpleName() + "/enabled", String.class); - if (onClass.isPresent()) { - return Boolean.parseBoolean(onClass.get()); - } - - Optional onGlobal = config.getOptionalValue(annotationType.getSimpleName() - + "/enabled", String.class); - if (onGlobal.isPresent()) { - return Boolean.parseBoolean(onGlobal.get()); - } - - if (Fallback.class.equals(annotationType)) { - return true; - } - - return config.getOptionalValue("MP_Fault_Tolerance_NonFallback_Enabled", Boolean.class) - .orElse(true); - } } diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 8ca800a6c..7b11a4508 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -21,6 +21,11 @@ com.squareup javapoet + + io.smallrye.config + smallrye-config-common + ${version.smallrye-config} + diff --git a/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/AutoConfigProcessor.java b/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/AutoConfigProcessor.java index da8049ba5..4fc4887c1 100644 --- a/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/AutoConfigProcessor.java +++ b/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/AutoConfigProcessor.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -36,22 +38,29 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import io.smallrye.config.common.utils.StringUtil; import io.smallrye.faulttolerance.autoconfig.AutoConfig; @SupportedAnnotationTypes("io.smallrye.faulttolerance.autoconfig.AutoConfig") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class AutoConfigProcessor extends AbstractProcessor { + private final Set newConfigAnnotations = new HashSet<>(); + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { try { - return doProcess(annotations, roundEnv); + if (roundEnv.processingOver()) { + generateNewConfig(newConfigAnnotations); + } else { + doProcess(annotations, roundEnv); + } } catch (Exception e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unexpected error: " + e.getMessage()); - return false; } + return false; } - private boolean doProcess(Set annotations, RoundEnvironment roundEnv) throws IOException { + private void doProcess(Set annotations, RoundEnvironment roundEnv) throws IOException { if (!annotations.isEmpty()) { // this processor only supports 1 annotation, so this is it TypeElement generateConfigForAnnotation = annotations.iterator().next(); @@ -61,8 +70,6 @@ private boolean doProcess(Set annotations, RoundEnvironme processConfigClass((TypeElement) annotated); } } - - return false; } private void processConfigClass(TypeElement configClass) throws IOException { @@ -104,7 +111,12 @@ private void processConfigClass(TypeElement configClass) throws IOException { } private void generateConfigImpl(TypeElement configClass, TypeElement annotationDeclaration) throws IOException { - boolean configurable = configClass.getAnnotation(AutoConfig.class).configurable(); + AutoConfig autoConfig = configClass.getAnnotation(AutoConfig.class); + boolean configurable = autoConfig.configurable(); + boolean newConfigAllowed = autoConfig.newConfigAllowed(); + if (configurable && newConfigAllowed) { + newConfigAnnotations.add(annotationDeclaration); + } String packageName = configClass.getQualifiedName().toString().replace("." + configClass.getSimpleName(), ""); ClassName configImplClassName = ClassName.get(packageName, configClass.getSimpleName() + "Impl"); @@ -124,7 +136,6 @@ private void generateConfigImpl(TypeElement configClass, TypeElement annotationD ? "Backing annotation instance. Used when runtime configuration doesn't override it." : "Backing annotation instance.") .build()) - // TODO this is not really necessary for non-configurable annotations .addField(FieldSpec.builder(TypeName.BOOLEAN, "onMethod", Modifier.PRIVATE, Modifier.FINAL) .addJavadoc( "{@code true} if annotation was placed on a method; {@code false} if annotation was placed on a class.") @@ -158,7 +169,7 @@ private void generateConfigImpl(TypeElement configClass, TypeElement annotationD .endControlFlow() .addCode(configurable ? CodeBlock.builder() .beginControlFlow("if (!$1T.isEnabled($2T.class, method.method))", - TypeNames.CONFIG, annotationType) + TypeNames.CONFIG_UTIL, annotationType) .addStatement("return null") .endControlFlow() .build() : CodeBlock.builder().build()) @@ -195,7 +206,7 @@ private void generateConfigImpl(TypeElement configClass, TypeElement annotationD .addModifiers(Modifier.PUBLIC) .returns(TypeName.get(annotationMember.getReturnType())) .addCode(configurable - ? generateConfigurableMethod(annotationMember, annotationDeclaration) + ? generateConfigurableMethod(annotationMember, annotationDeclaration, newConfigAllowed) : generateNonconfigurableMethod(annotationMember)) .build()) .collect(Collectors.toList())) @@ -211,41 +222,86 @@ private void generateConfigImpl(TypeElement configClass, TypeElement annotationD .writeTo(processingEnv.getFiler()); } - private CodeBlock generateConfigurableMethod(ExecutableElement annotationMember, TypeElement annotationDeclaration) { - return CodeBlock.builder() - .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) - // TODO maybe cache `Config` in a `private static final` field? - .addStatement("$1T config = $2T.getConfig()", TypeNames.MP_CONFIG, TypeNames.MP_CONFIG_PROVIDER) - .beginControlFlow("if (onMethod)") - .add("// ///\n") - .addStatement("String key = method.declaringClass.getName() + $1S + method.name + $2S", - "/", "/" + annotationDeclaration.getSimpleName() + "/" + annotationMember.getSimpleName()) - .addStatement( - "_$1L = config.getOptionalValue(key, $2T.class).orElse(null)", - annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) - .nextControlFlow("else") - .add("// //\n") - .addStatement("String key = method.declaringClass.getName() + $1S", - "/" + annotationDeclaration.getSimpleName() + "/" + annotationMember.getSimpleName()) - .addStatement( - "_$1L = config.getOptionalValue(key, $2T.class).orElse(null)", - annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) - .endControlFlow() - .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) - .add("// /\n") - .addStatement( - "_$1L = config.getOptionalValue($2S, $3T.class).orElse(null)", - annotationMember.getSimpleName(), - annotationDeclaration.getSimpleName() + "/" + annotationMember.getSimpleName(), - rawType(annotationMember.getReturnType())) - .endControlFlow() - .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) - .add("// annotation value\n") - .addStatement("_$1L = instance.$1L()", annotationMember.getSimpleName()) - .endControlFlow() - .endControlFlow() - .addStatement("return _$L", annotationMember.getSimpleName()) - .build(); + private CodeBlock generateConfigurableMethod(ExecutableElement annotationMember, TypeElement annotationDeclaration, + boolean newConfigAllowed) { + if (newConfigAllowed) { + return CodeBlock.builder() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .addStatement("$1T config = $2T.getConfig()", TypeNames.MP_CONFIG, TypeNames.MP_CONFIG_PROVIDER) + .beginControlFlow("if (onMethod)") + .add("// smallrye.faulttolerance.\"/\"..\n") + .addStatement("String newKey = ConfigUtil.newKey($1T.class, $2S, method.declaringClass, method.name)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .add("// ///\n") + .addStatement("String oldKey = ConfigUtil.oldKey($1T.class, $2S, method.declaringClass, method.name)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(newKey, $2T.class).or(() -> config.getOptionalValue(oldKey, $2T.class)).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .nextControlFlow("else") + .add("// smallrye.faulttolerance.\"\"..\n") + .addStatement("String newKey = ConfigUtil.newKey($1T.class, $2S, method.declaringClass)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .add("// //\n") + .addStatement("String oldKey = ConfigUtil.oldKey($1T.class, $2S, method.declaringClass)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(newKey, $2T.class).or(() -> config.getOptionalValue(oldKey, $2T.class)).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .endControlFlow() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .add("// smallrye.faulttolerance.global..\n") + .addStatement("String newKey = ConfigUtil.newKey($1T.class, $2S)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .add("// /\n") + .addStatement("String oldKey = ConfigUtil.oldKey($1T.class, $2S)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(newKey, $2T.class).or(() -> config.getOptionalValue(oldKey, $2T.class)).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .endControlFlow() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .add("// annotation value\n") + .addStatement("_$1L = instance.$1L()", annotationMember.getSimpleName()) + .endControlFlow() + .endControlFlow() + .addStatement("return _$L", annotationMember.getSimpleName()) + .build(); + } else { + return CodeBlock.builder() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .addStatement("$1T config = $2T.getConfig()", TypeNames.MP_CONFIG, TypeNames.MP_CONFIG_PROVIDER) + .beginControlFlow("if (onMethod)") + .add("// ///\n") + .addStatement("String key = ConfigUtil.oldKey($1T.class, $2S, method.declaringClass, method.name)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(key, $2T.class).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .nextControlFlow("else") + .add("// //\n") + .addStatement("String key = ConfigUtil.oldKey($1T.class, $2S, method.declaringClass)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(key, $2T.class).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .endControlFlow() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .add("// /\n") + .addStatement("String key = ConfigUtil.oldKey($1T.class, $2S)", + TypeName.get(annotationDeclaration.asType()), annotationMember.getSimpleName()) + .addStatement( + "_$1L = config.getOptionalValue(key, $2T.class).orElse(null)", + annotationMember.getSimpleName(), rawType(annotationMember.getReturnType())) + .endControlFlow() + .beginControlFlow("if (_$L == null)", annotationMember.getSimpleName()) + .add("// annotation value\n") + .addStatement("_$1L = instance.$1L()", annotationMember.getSimpleName()) + .endControlFlow() + .endControlFlow() + .addStatement("return _$L", annotationMember.getSimpleName()) + .build(); + } } private CodeBlock generateNonconfigurableMethod(ExecutableElement annotationMember) { @@ -290,4 +346,74 @@ protected TypeName defaultAction(TypeMirror e, Void unused) { private static String firstToLowerCase(String str) { return str.substring(0, 1).toLowerCase(Locale.ROOT) + str.substring(1); } + + // TODO `NewConfig` is not the best name + private void generateNewConfig(Set configAnnotations) throws IOException { + String packageName = "io.smallrye.faulttolerance.config"; + ClassName mappingClassName = ClassName.get(packageName, "NewConfig"); + + JavaFile.builder(packageName, TypeSpec.classBuilder(mappingClassName) + .addJavadoc("Automatically generated from all config annotations, do not modify.") + .addModifiers(Modifier.FINAL) + .addField(FieldSpec.builder(TypeNames.HASHMAP_OF_STRING_TO_STRING, "MAP", + Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("mapping()") + .build()) + .addMethod(MethodSpec.methodBuilder("mapping") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(TypeNames.HASHMAP_OF_STRING_TO_STRING) + .addCode(generateMappingTable(configAnnotations)) + .build()) + .addMethod(MethodSpec.methodBuilder("get") + .addModifiers(Modifier.STATIC) + .returns(TypeNames.STRING) + .addParameter(TypeNames.CLASS_OF_ANNOTATION, "annotation") + .addStatement("return MAP.get(annotation.getSimpleName())") + .build()) + .addMethod(MethodSpec.methodBuilder("get") + .addModifiers(Modifier.STATIC) + .returns(TypeNames.STRING) + .addParameter(TypeNames.CLASS_OF_ANNOTATION, "annotation") + .addParameter(TypeNames.STRING, "member") + .addStatement("return MAP.get(annotation.getSimpleName() + \"/\" + member)") + .build()) + .build()) + .indent(" ") // 4 spaces + .build() + .writeTo(processingEnv.getFiler()); + } + + private CodeBlock generateMappingTable(Set configAnnotations) { + CodeBlock.Builder result = CodeBlock.builder(); + result.addStatement("$1T result = new $1T()", TypeNames.HASHMAP_OF_STRING_TO_STRING); + configAnnotations + .stream() + .sorted(Comparator.comparing(it -> it.getSimpleName().toString())) + .forEach(configAnnotation -> { + String originalName = configAnnotation.getSimpleName().toString(); + String proprietaryName = StringUtil.skewer(originalName); + result.addStatement("result.put($S, $S)", originalName, proprietaryName); + + result.addStatement("result.put($S, $S)", originalName + "/enabled", proprietaryName + ".enabled"); + ElementFilter.methodsIn(configAnnotation.getEnclosedElements()) + .stream() + .sorted(Comparator.comparing(it -> it.getSimpleName().toString())) + .forEach(method -> { + String methodOriginalName = originalName + "/" + method.getSimpleName(); + String fixedName = method.getSimpleName().toString(); + switch (method.getSimpleName().toString()) { + case "jitterDelayUnit": + fixedName = "jitterUnit"; + break; + case "durationUnit": + fixedName = "maxDurationUnit"; + break; + } + String methodProprietaryName = proprietaryName + "." + StringUtil.skewer(fixedName); + result.addStatement("result.put($S, $S)", methodOriginalName, methodProprietaryName); + }); + }); + result.addStatement("return result"); + return result.build(); + } } diff --git a/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/TypeNames.java b/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/TypeNames.java index bbeb137ff..5f08bceca 100644 --- a/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/TypeNames.java +++ b/implementation/autoconfig/processor/src/main/java/io/smallrye/faulttolerance/autoconfig/processor/TypeNames.java @@ -1,6 +1,7 @@ package io.smallrye.faulttolerance.autoconfig.processor; import java.lang.annotation.Annotation; +import java.util.HashMap; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; @@ -8,17 +9,18 @@ import com.squareup.javapoet.WildcardTypeName; final class TypeNames { + static final TypeName ANNOTATION = ClassName.get(Annotation.class); static final TypeName CLASS = ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(TypeName.OBJECT)); static final TypeName CLASS_OF_ANNOTATION = ParameterizedTypeName.get( - ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(Annotation.class))); + ClassName.get(Class.class), WildcardTypeName.subtypeOf(ANNOTATION)); static final TypeName STRING = ClassName.get(String.class); + static final TypeName HASHMAP_OF_STRING_TO_STRING = ParameterizedTypeName.get(ClassName.get(HashMap.class), STRING, STRING); static final TypeName MP_CONFIG = ClassName.get("org.eclipse.microprofile.config", "Config"); static final TypeName MP_CONFIG_PROVIDER = ClassName.get("org.eclipse.microprofile.config", "ConfigProvider"); - static final TypeName CONFIG = ClassName.get("io.smallrye.faulttolerance.autoconfig", - "Config"); + static final TypeName CONFIG_UTIL = ClassName.get("io.smallrye.faulttolerance.config", "ConfigUtil"); static final TypeName METHOD_DESCRIPTOR = ClassName.get("io.smallrye.faulttolerance.autoconfig", "MethodDescriptor"); static final TypeName FAULT_TOLERANCE_METHOD = ClassName.get("io.smallrye.faulttolerance.autoconfig", diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java index fd91e4e6c..a85378a7f 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java @@ -8,8 +8,6 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.eclipse.microprofile.config.inject.ConfigProperty; - import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.Guard; @@ -40,8 +38,7 @@ public BasicCircuitBreakerMaintenanceImpl cbMaintenance() { @Singleton public static class LazyDependencies implements BuilderLazyDependencies { @Inject - @ConfigProperty(name = "MP_Fault_Tolerance_NonFallback_Enabled", defaultValue = "true") - boolean ftEnabled; + Enablement enablement; @Inject ExecutorHolder executorHolder; @@ -51,7 +48,7 @@ public static class LazyDependencies implements BuilderLazyDependencies { @Override public boolean ftEnabled() { - return ftEnabled; + return enablement.ft(); } @Override diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/Enablement.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/Enablement.java new file mode 100644 index 000000000..01623cfe6 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/Enablement.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance; + +import java.util.Optional; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.smallrye.faulttolerance.config.ConfigPrefix; + +@Singleton +public class Enablement { + private final boolean ftEnabled; + private final boolean metricsEnabled; + + @Inject + Enablement( + @ConfigProperty(name = ConfigPrefix.VALUE + "enabled") Optional newFtEnabled, + @ConfigProperty(name = "MP_Fault_Tolerance_NonFallback_Enabled") Optional oldFtEnabled, + @ConfigProperty(name = ConfigPrefix.VALUE + "metrics.enabled") Optional newMetricsEnabled, + @ConfigProperty(name = "MP_Fault_Tolerance_Metrics_Enabled") Optional oldMetricsEnabled) { + ftEnabled = newFtEnabled.orElse(oldFtEnabled.orElse(true)); + metricsEnabled = newMetricsEnabled.orElse(oldMetricsEnabled.orElse(true)); + } + + public boolean ft() { + return ftEnabled; + } + + public boolean metrics() { + return metricsEnabled; + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java index daa3d300d..e902710b6 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java @@ -182,6 +182,7 @@ void registerInterceptorBindings(@Observes BeforeBeanDiscovery bbd, BeanManager bbd.addAnnotatedType(bm.createAnnotatedType(RequestContextIntegration.class), RequestContextIntegration.class.getName()); bbd.addAnnotatedType(bm.createAnnotatedType(SpecCompatibility.class), SpecCompatibility.class.getName()); + bbd.addAnnotatedType(bm.createAnnotatedType(Enablement.class), Enablement.class.getName()); bbd.addAnnotatedType(bm.createAnnotatedType(CdiSpi.EagerDependencies.class), CdiSpi.EagerDependencies.class.getName()); bbd.addAnnotatedType(bm.createAnnotatedType(CdiSpi.LazyDependencies.class), diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java index 6faa8ee46..9af83384e 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java @@ -6,12 +6,13 @@ import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.inject.ConfigProperty; +import io.smallrye.faulttolerance.config.ConfigPrefix; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; @Singleton public class SpecCompatibility { - private static final String PROPERTY = "smallrye.faulttolerance.mp-compatibility"; + private static final String PROPERTY = ConfigPrefix.VALUE + "mp-compatibility"; private final boolean compatible; diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java index d826d9c48..afba558e3 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java @@ -6,7 +6,7 @@ import io.smallrye.faulttolerance.autoconfig.AutoConfig; import io.smallrye.faulttolerance.autoconfig.Config; -@AutoConfig +@AutoConfig(newConfigAllowed = false) public interface ApplyFaultToleranceConfig extends ApplyFaultTolerance, Config { @Override default void validate() { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/BlockingConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/BlockingConfig.java index 07c3aefea..bc814adac 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/BlockingConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/BlockingConfig.java @@ -4,7 +4,7 @@ import io.smallrye.faulttolerance.autoconfig.AutoConfig; import io.smallrye.faulttolerance.autoconfig.Config; -@AutoConfig +@AutoConfig(newConfigAllowed = false) public interface BlockingConfig extends Blocking, Config { @Override default void validate() { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigPrefix.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigPrefix.java new file mode 100644 index 000000000..9b1f84554 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigPrefix.java @@ -0,0 +1,5 @@ +package io.smallrye.faulttolerance.config; + +public final class ConfigPrefix { + public static final String VALUE = "smallrye.faulttolerance."; +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigUtil.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigUtil.java new file mode 100644 index 000000000..5db58598c --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ConfigUtil.java @@ -0,0 +1,99 @@ +package io.smallrye.faulttolerance.config; + +import java.lang.annotation.Annotation; +import java.util.Optional; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.autoconfig.MethodDescriptor; + +final class ConfigUtil { + static final String ENABLED = "enabled"; + static final String GLOBAL = "global"; + + static String newKey(Class annotation, String member, Class declaringClass, String method) { + return ConfigPrefix.VALUE + "\"" + declaringClass.getName() + "/" + method + "\"." + + NewConfig.get(annotation, member); + } + + static String oldKey(Class annotation, String member, Class declaringClass, String method) { + return declaringClass.getName() + "/" + method + "/" + annotation.getSimpleName() + "/" + member; + } + + static String newKey(Class annotation, String member, Class declaringClass) { + return ConfigPrefix.VALUE + "\"" + declaringClass.getName() + "\"." + + NewConfig.get(annotation, member); + } + + static String oldKey(Class annotation, String member, Class declaringClass) { + return declaringClass.getName() + "/" + annotation.getSimpleName() + "/" + member; + } + + static String newKey(Class annotation, String member) { + return ConfigPrefix.VALUE + GLOBAL + "." + NewConfig.get(annotation, member); + } + + static String oldKey(Class annotation, String member) { + return annotation.getSimpleName() + "/" + member; + } + + // --- + + static boolean isEnabled(Class annotationType, MethodDescriptor method) { + // TODO converting strings to boolean here is inconsistent, + // but it's how SmallRye Fault Tolerance has always done it + + org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig(); + + Optional onMethodNew = config.getOptionalValue( + newKey(annotationType, ENABLED, method.declaringClass, method.name), String.class); + if (onMethodNew.isPresent()) { + return Boolean.parseBoolean(onMethodNew.get()); + } + + Optional onMethod = config.getOptionalValue( + oldKey(annotationType, ENABLED, method.declaringClass, method.name), String.class); + if (onMethod.isPresent()) { + return Boolean.parseBoolean(onMethod.get()); + } + + Optional onClassNew = config.getOptionalValue( + newKey(annotationType, ENABLED, method.declaringClass), String.class); + if (onClassNew.isPresent()) { + return Boolean.parseBoolean(onClassNew.get()); + } + + Optional onClass = config.getOptionalValue( + oldKey(annotationType, ENABLED, method.declaringClass), String.class); + if (onClass.isPresent()) { + return Boolean.parseBoolean(onClass.get()); + } + + Optional onGlobalNew = config.getOptionalValue(newKey(annotationType, ENABLED), String.class); + if (onGlobalNew.isPresent()) { + return Boolean.parseBoolean(onGlobalNew.get()); + } + + Optional onGlobal = config.getOptionalValue(oldKey(annotationType, ENABLED), String.class); + if (onGlobal.isPresent()) { + return Boolean.parseBoolean(onGlobal.get()); + } + + if (Fallback.class.equals(annotationType)) { + return true; + } + + Optional ftEnabledNew = config.getOptionalValue(ConfigPrefix.VALUE + ENABLED, Boolean.class); + if (ftEnabledNew.isPresent()) { + return ftEnabledNew.get(); + } + + Optional ftEnabled = config.getOptionalValue("MP_Fault_Tolerance_NonFallback_Enabled", Boolean.class); + if (ftEnabled.isPresent()) { + return ftEnabled.get(); + } + + return true; + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/NonBlockingConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/NonBlockingConfig.java index 6fc0e755c..a1b23aeee 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/NonBlockingConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/NonBlockingConfig.java @@ -4,7 +4,7 @@ import io.smallrye.faulttolerance.autoconfig.AutoConfig; import io.smallrye.faulttolerance.autoconfig.Config; -@AutoConfig +@AutoConfig(newConfigAllowed = false) public interface NonBlockingConfig extends NonBlocking, Config { @Override default void validate() { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CompoundMetricsProvider.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CompoundMetricsProvider.java index 246da18f2..fc98b33c0 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CompoundMetricsProvider.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CompoundMetricsProvider.java @@ -15,6 +15,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; +import io.smallrye.faulttolerance.Enablement; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; @@ -33,10 +34,10 @@ public class CompoundMetricsProvider implements MetricsProvider { @Inject CompoundMetricsProvider( Instance lookup, - @ConfigProperty(name = Constants.METRICS_ENABLED, defaultValue = "true") boolean metricsEnabled, - @ConfigProperty(name = Constants.MPMETRICS_DISABLED, defaultValue = "false") boolean mpMetricsDisabled, - @ConfigProperty(name = Constants.OPENTELEMETRY_DISABLED, defaultValue = "false") boolean openTelemetryDisabled, - @ConfigProperty(name = Constants.MICROMETER_DISABLED, defaultValue = "false") boolean micrometerDisabled) { + Enablement enablement, + @ConfigProperty(name = MicroProfileMetricsProvider.DISABLED, defaultValue = "false") boolean mpMetricsDisabled, + @ConfigProperty(name = OpenTelemetryProvider.DISABLED, defaultValue = "false") boolean openTelemetryDisabled, + @ConfigProperty(name = MicrometerProvider.DISABLED, defaultValue = "false") boolean micrometerDisabled) { List providers = new ArrayList<>(); if (!mpMetricsDisabled) { @@ -60,7 +61,7 @@ public class CompoundMetricsProvider implements MetricsProvider { // either the bean does not exist, or some of its dependencies does not exist } } - this.enabled = providers.isEmpty() ? false : metricsEnabled; + this.enabled = providers.isEmpty() ? false : enablement.metrics(); this.providers = providers.toArray(new MetricsProvider[0]); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/Constants.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/Constants.java deleted file mode 100644 index 8084688b8..000000000 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/Constants.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.smallrye.faulttolerance.metrics; - -final class Constants { - static final String METRICS_ENABLED = "MP_Fault_Tolerance_Metrics_Enabled"; - static final String MPMETRICS_DISABLED = "smallrye.faulttolerance.mpmetrics.disabled"; - static final String OPENTELEMETRY_DISABLED = "smallrye.faulttolerance.opentelemetry.disabled"; - static final String MICROMETER_DISABLED = "smallrye.faulttolerance.micrometer.disabled"; -} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicroProfileMetricsProvider.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicroProfileMetricsProvider.java index 7eabe6f25..125f42f9f 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicroProfileMetricsProvider.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicroProfileMetricsProvider.java @@ -14,7 +14,9 @@ import org.eclipse.microprofile.metrics.Tag; import org.eclipse.microprofile.metrics.annotation.RegistryType; +import io.smallrye.faulttolerance.Enablement; import io.smallrye.faulttolerance.ExecutorHolder; +import io.smallrye.faulttolerance.config.ConfigPrefix; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; @@ -24,6 +26,8 @@ @Singleton public class MicroProfileMetricsProvider implements MetricsProvider { + static final String DISABLED = ConfigPrefix.VALUE + "mpmetrics.disabled"; + private final boolean enabled; private final MetricRegistry registry; @@ -34,10 +38,10 @@ public class MicroProfileMetricsProvider implements MetricsProvider { MicroProfileMetricsProvider( // lazy for `CompoundMetricsProvider` @RegistryType(type = MetricRegistry.Type.BASE) Provider registry, - @ConfigProperty(name = Constants.METRICS_ENABLED, defaultValue = "true") boolean metricsEnabled, - @ConfigProperty(name = Constants.MPMETRICS_DISABLED, defaultValue = "false") boolean mpMetricsDisabled, + Enablement enablement, + @ConfigProperty(name = DISABLED, defaultValue = "false") boolean mpMetricsDisabled, ExecutorHolder executorHolder) { - this.enabled = metricsEnabled && !mpMetricsDisabled; + this.enabled = enablement.metrics() && !mpMetricsDisabled; this.registry = registry.get(); if (enabled) { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicrometerProvider.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicrometerProvider.java index 40aa089a2..2258f3223 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicrometerProvider.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/MicrometerProvider.java @@ -12,7 +12,9 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; +import io.smallrye.faulttolerance.Enablement; import io.smallrye.faulttolerance.ExecutorHolder; +import io.smallrye.faulttolerance.config.ConfigPrefix; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; @@ -22,6 +24,8 @@ @Singleton public class MicrometerProvider implements MetricsProvider { + static final String DISABLED = ConfigPrefix.VALUE + "micrometer.disabled"; + private final boolean enabled; private final MeterRegistry registry; @@ -32,10 +36,10 @@ public class MicrometerProvider implements MetricsProvider { MicrometerProvider( // lazy for `CompoundMetricsProvider` Provider registry, - @ConfigProperty(name = Constants.METRICS_ENABLED, defaultValue = "true") boolean metricsEnabled, - @ConfigProperty(name = Constants.MICROMETER_DISABLED, defaultValue = "false") boolean micrometerDisabled, + Enablement enablement, + @ConfigProperty(name = DISABLED, defaultValue = "false") boolean micrometerDisabled, ExecutorHolder executorHolder) { - this.enabled = metricsEnabled && !micrometerDisabled; + this.enabled = enablement.metrics() && !micrometerDisabled; this.registry = registry.get(); if (enabled) { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/OpenTelemetryProvider.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/OpenTelemetryProvider.java index 86e403685..9079252f2 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/OpenTelemetryProvider.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/OpenTelemetryProvider.java @@ -12,7 +12,9 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; +import io.smallrye.faulttolerance.Enablement; import io.smallrye.faulttolerance.ExecutorHolder; +import io.smallrye.faulttolerance.config.ConfigPrefix; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; @@ -22,6 +24,8 @@ @Singleton public class OpenTelemetryProvider implements MetricsProvider { + static final String DISABLED = ConfigPrefix.VALUE + "opentelemetry.disabled"; + private final boolean enabled; private final Meter meter; @@ -32,10 +36,10 @@ public class OpenTelemetryProvider implements MetricsProvider { OpenTelemetryProvider( // lazy for `CompoundMetricsProvider` Provider meter, - @ConfigProperty(name = Constants.METRICS_ENABLED, defaultValue = "true") boolean metricsEnabled, - @ConfigProperty(name = Constants.OPENTELEMETRY_DISABLED, defaultValue = "false") boolean openTelemetryDisabled, + Enablement enablement, + @ConfigProperty(name = DISABLED, defaultValue = "false") boolean openTelemetryDisabled, ExecutorHolder executorHolder) { - this.enabled = metricsEnabled && !openTelemetryDisabled; + this.enabled = enablement.metrics() && !openTelemetryDisabled; this.meter = meter.get(); if (enabled) { diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/DefaultConfiguration.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/DefaultConfiguration.java index b03fe4d45..66c9637e3 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/DefaultConfiguration.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/DefaultConfiguration.java @@ -5,8 +5,14 @@ import java.util.concurrent.TimeUnit; final class DefaultConfiguration implements Configuration { - private final boolean enabled = !"false".equals(System.getProperty("MP_Fault_Tolerance_NonFallback_Enabled")); - private final ExecutorService executor = Executors.newCachedThreadPool(); + private final boolean enabled; + private final ExecutorService executor; + + DefaultConfiguration() { + enabled = !"false".equals(System.getProperty("smallrye.faulttolerance.enabled", + System.getProperty("MP_Fault_Tolerance_NonFallback_Enabled"))); + executor = Executors.newCachedThreadPool(); + } @Override public boolean enabled() { diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigBean.java new file mode 100644 index 000000000..5d2179c35 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigBean.java @@ -0,0 +1,36 @@ +package io.smallrye.faulttolerance.config.better; + +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.Future; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Bulkhead; + +import io.smallrye.faulttolerance.core.util.barrier.Barrier; + +@ApplicationScoped +public class BulkheadConfigBean { + @Bulkhead(value = 5) + public void value(Barrier barrier) { + try { + barrier.await(); + } catch (InterruptedException e) { + throw sneakyThrow(e); + } + } + + @Bulkhead(value = 1, waitingTaskQueue = 5) + @Asynchronous + public Future waitingTaskQueue(Barrier barrier) { + try { + barrier.await(); + } catch (InterruptedException e) { + throw sneakyThrow(e); + } + return completedFuture(null); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigTest.java new file mode 100644 index 000000000..6e8d5db8a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/BulkheadConfigTest.java @@ -0,0 +1,67 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.core.util.barrier.Barrier; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.BulkheadConfigBean/value\".bulkhead.value", value = "1") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.BulkheadConfigBean/waitingTaskQueue\".bulkhead.waiting-task-queue", value = "1") +public class BulkheadConfigTest { + @Inject + private BulkheadConfigBean bean; + + private ExecutorService executor; + + @BeforeEach + public void setUp() { + executor = Executors.newCachedThreadPool(); + } + + @AfterEach + public void tearDown() throws InterruptedException { + executor.shutdownNow(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void value() throws Exception { + Barrier barrier = Barrier.interruptible(); + + executor.submit(() -> bean.value(barrier)); + Thread.sleep(500); + assertThatThrownBy(() -> bean.value(null)).isExactlyInstanceOf(BulkheadException.class); + + barrier.open(); + } + + @Test + public void waitingTaskQueue() throws Exception { + Barrier barrier1 = Barrier.interruptible(); + Barrier barrier2 = Barrier.interruptible(); + + executor.submit(() -> bean.waitingTaskQueue(barrier1)); + executor.submit(() -> bean.waitingTaskQueue(barrier2)); + Thread.sleep(500); + assertThatThrownBy(() -> bean.waitingTaskQueue(null).get()) + .isExactlyInstanceOf(ExecutionException.class) + .hasCauseExactlyInstanceOf(BulkheadException.class); + + barrier1.open(); + barrier2.open(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigBean.java new file mode 100644 index 000000000..e829c2fde --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigBean.java @@ -0,0 +1,46 @@ +package io.smallrye.faulttolerance.config.better; + +import java.time.temporal.ChronoUnit; + +import jakarta.enterprise.context.Dependent; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; + +@Dependent +public class CircuitBreakerConfigBean { + @CircuitBreaker(requestVolumeThreshold = 2, failOn = TestConfigExceptionB.class) + public void failOn() { + throw new TestConfigExceptionA(); + } + + @CircuitBreaker(requestVolumeThreshold = 2) + public void skipOn() { + throw new TestConfigExceptionA(); + } + + @CircuitBreaker(requestVolumeThreshold = 2, delay = 20, delayUnit = ChronoUnit.MICROS) + public void delay(boolean fail) { + if (fail) { + throw new TestConfigExceptionA(); + } + } + + @CircuitBreaker(requestVolumeThreshold = 2) + public void requestVolumeThreshold() { + throw new TestConfigExceptionA(); + } + + @CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 1.0) + public void failureRatio(boolean fail) { + if (fail) { + throw new TestConfigExceptionA(); + } + } + + @CircuitBreaker(requestVolumeThreshold = 10, successThreshold = 4, delay = 1000) + public void successThreshold(boolean fail) { + if (fail) { + throw new TestConfigExceptionA(); + } + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigTest.java new file mode 100644 index 000000000..96fae2de5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/CircuitBreakerConfigTest.java @@ -0,0 +1,118 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/skipOn\".circuit-breaker.skip-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/failOn\".circuit-breaker.fail-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/delay\".circuit-breaker.delay", value = "1000") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/delay\".circuit-breaker.delay-unit", value = "millis") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/requestVolumeThreshold\".circuit-breaker.request-volume-threshold", value = "4") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/failureRatio\".circuit-breaker.failure-ratio", value = "0.8") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.CircuitBreakerConfigBean/successThreshold\".circuit-breaker.success-threshold", value = "2") +public class CircuitBreakerConfigTest { + @Inject + private CircuitBreakerConfigBean bean; + + @Test + public void failOn() { + assertThatThrownBy(() -> bean.failOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.failOn()).isExactlyInstanceOf(CircuitBreakerOpenException.class); + } + + @Test + public void testConfigureSkipOn() { + assertThatThrownBy(() -> bean.skipOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.skipOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.skipOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + } + + @Test + public void delay() { + assertThatThrownBy(() -> bean.delay(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.delay(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.delay(false)).isExactlyInstanceOf(CircuitBreakerOpenException.class); + + long start = System.nanoTime(); + await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> { + assertThatCode(() -> bean.delay(false)).doesNotThrowAnyException(); + }); + long end = System.nanoTime(); + + long durationInMillis = Duration.ofNanos(end - start).toMillis(); + assertThat(durationInMillis).isGreaterThan(800); + assertThat(durationInMillis).isLessThan(2000); + } + + @Test + public void requestVolumeThreshold() { + assertThatThrownBy(() -> bean.requestVolumeThreshold()).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.requestVolumeThreshold()).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.requestVolumeThreshold()).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.requestVolumeThreshold()).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.requestVolumeThreshold()).isExactlyInstanceOf(CircuitBreakerOpenException.class); + } + + @Test + public void failureRatio() { + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatCode(() -> bean.failureRatio(false)).doesNotThrowAnyException(); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatCode(() -> bean.failureRatio(false)).doesNotThrowAnyException(); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + assertThatThrownBy(() -> bean.failureRatio(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.failureRatio(false)).isExactlyInstanceOf(CircuitBreakerOpenException.class); + } + + @Test + public void successThreshold() { + for (int i = 0; i < 10; i++) { + assertThatThrownBy(() -> bean.successThreshold(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + } + + assertThatThrownBy(() -> bean.successThreshold(false)).isExactlyInstanceOf(CircuitBreakerOpenException.class); + await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> { + assertThatCode(() -> bean.successThreshold(false)).doesNotThrowAnyException(); + }); + + assertThatCode(() -> bean.successThreshold(false)).doesNotThrowAnyException(); + + for (int i = 0; i < 10; i++) { + assertThatThrownBy(() -> bean.successThreshold(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + } + + assertThatThrownBy(() -> bean.successThreshold(false)).isExactlyInstanceOf(CircuitBreakerOpenException.class); + await().atMost(1, TimeUnit.MINUTES).untilAsserted(() -> { + assertThatCode(() -> bean.successThreshold(false)).doesNotThrowAnyException(); + }); + + assertThatThrownBy(() -> bean.successThreshold(true)).isExactlyInstanceOf(TestConfigExceptionA.class); + + assertThatThrownBy(() -> bean.successThreshold(false)).isExactlyInstanceOf(CircuitBreakerOpenException.class); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyBean.java new file mode 100644 index 000000000..0e863a78c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyBean.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.config.better; + +import jakarta.enterprise.context.Dependent; + +import org.eclipse.microprofile.faulttolerance.Retry; + +@Dependent +@Retry +public class ConfigPropertyBean { + private int retry = 0; + + @Retry + public void triggerException() { + retry++; + throw new IllegalStateException("Exception"); + } + + public int getRetry() { + return retry; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassTest.java new file mode 100644 index 000000000..420b43d43 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassTest.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.global.retry.max-retries", value = "7") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.BeanWithRetry\".retry.max-retries", value = "5") +public class ConfigPropertyGlobalVsClassTest { + @Inject + private ConfigPropertyBean bean; + + @Test + void test() { + assertThatThrownBy(() -> bean.triggerException()).isExactlyInstanceOf(IllegalStateException.class); + assertThat(bean.getRetry()).isEqualTo(8); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassVsMethodTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassVsMethodTest.java new file mode 100644 index 000000000..f1c4fab17 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyGlobalVsClassVsMethodTest.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.global.retry.max-retries", value = "7") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.ConfigPropertyBean\".retry.max-retries", value = "5") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.ConfigPropertyBean/triggerException\".retry.max-retries", value = "6") +public class ConfigPropertyGlobalVsClassVsMethodTest { + @Inject + private ConfigPropertyBean bean; + + @Test + void test() { + assertThatThrownBy(() -> bean.triggerException()).isExactlyInstanceOf(IllegalStateException.class); + assertThat(bean.getRetry()).isEqualTo(7); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyOnClassAndMethodTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyOnClassAndMethodTest.java new file mode 100644 index 000000000..864eeaf41 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/ConfigPropertyOnClassAndMethodTest.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.ConfigPropertyBean\".retry.max-retries", value = "5") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.ConfigPropertyBean/triggerException\".retry.max-retries", value = "6") +public class ConfigPropertyOnClassAndMethodTest { + @Inject + private ConfigPropertyBean bean; + + @Test + void test() { + assertThatThrownBy(() -> bean.triggerException()).isExactlyInstanceOf(IllegalStateException.class); + assertThat(bean.getRetry()).isEqualTo(7); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackApplyOnConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackApplyOnConfigTest.java new file mode 100644 index 000000000..ef6ae7ffe --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackApplyOnConfigTest.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.global.fallback.apply-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +public class FallbackApplyOnConfigTest { + @Inject + private FallbackConfigBean bean; + + @Test + public void applyOn() { + assertThat(bean.applyOn()).isEqualTo("FALLBACK"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigBean.java new file mode 100644 index 000000000..529b1c2d6 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigBean.java @@ -0,0 +1,36 @@ +package io.smallrye.faulttolerance.config.better; + +import jakarta.enterprise.context.Dependent; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +@Dependent +public class FallbackConfigBean { + @Fallback(fallbackMethod = "theFallback", applyOn = TestConfigExceptionB.class) + public String applyOn() { + throw new TestConfigExceptionA(); + } + + @Fallback(fallbackMethod = "theFallback") + public String skipOn() { + throw new TestConfigExceptionA(); + } + + @Fallback(fallbackMethod = "theFallback") + public String fallbackMethod() { + throw new IllegalArgumentException(); + } + + @Fallback(FallbackHandlerA.class) + public String fallbackHandler() { + throw new IllegalArgumentException(); + } + + public String theFallback() { + return "FALLBACK"; + } + + public String anotherFallback() { + return "ANOTHER FALLBACK"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigTest.java new file mode 100644 index 000000000..2878949cd --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackConfigTest.java @@ -0,0 +1,41 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.FallbackConfigBean/applyOn\".fallback.apply-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.FallbackConfigBean/skipOn\".fallback.skip-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.FallbackConfigBean/fallbackMethod\".fallback.fallback-method", value = "anotherFallback") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.FallbackConfigBean/fallbackHandler\".fallback.value", value = "io.smallrye.faulttolerance.config.better.FallbackHandlerB") +public class FallbackConfigTest { + @Inject + private FallbackConfigBean bean; + + @Test + public void applyOn() { + assertThat(bean.applyOn()).isEqualTo("FALLBACK"); + } + + @Test + public void skipOn() { + assertThatThrownBy(() -> bean.skipOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + } + + @Test + public void fallbackMethod() { + assertThat(bean.fallbackMethod()).isEqualTo("ANOTHER FALLBACK"); + } + + @Test + public void fallbackHandler() { + assertThat(bean.fallbackHandler()).isEqualTo("FallbackHandlerB"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerA.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerA.java new file mode 100644 index 000000000..4c2ef8ac9 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerA.java @@ -0,0 +1,11 @@ +package io.smallrye.faulttolerance.config.better; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; + +public class FallbackHandlerA implements FallbackHandler { + @Override + public String handle(ExecutionContext context) { + return "FallbackHandlerA"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerB.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerB.java new file mode 100644 index 000000000..7a25af3f5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackHandlerB.java @@ -0,0 +1,11 @@ +package io.smallrye.faulttolerance.config.better; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; + +public class FallbackHandlerB implements FallbackHandler { + @Override + public String handle(ExecutionContext context) { + return "FallbackHandlerB"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackSkipOnConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackSkipOnConfigTest.java new file mode 100644 index 000000000..411c9e43e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/FallbackSkipOnConfigTest.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.global.fallback.skip-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA") +public class FallbackSkipOnConfigTest { + @Inject + private FallbackConfigBean bean; + + @Test + public void skipOn() { + assertThatThrownBy(() -> bean.skipOn()).isExactlyInstanceOf(TestConfigExceptionA.class); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigBean.java new file mode 100644 index 000000000..67aa8dec5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigBean.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.config.better; + +import java.time.temporal.ChronoUnit; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.RateLimit; + +@ApplicationScoped +public class RateLimitConfigBean { + @RateLimit(value = 10) + public String value() { + return "value"; + } + + @RateLimit(value = 3, window = 10, windowUnit = ChronoUnit.MINUTES) + public String window() { + return "window"; + } + + @RateLimit(value = 3, minSpacing = 10, minSpacingUnit = ChronoUnit.MINUTES) + public String minSpacing() { + return "minSpacing"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigTest.java new file mode 100644 index 000000000..332f9a41f --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RateLimitConfigTest.java @@ -0,0 +1,53 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.RateLimitException; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RateLimitConfigBean/value\".rate-limit.value", value = "3") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RateLimitConfigBean/window\".rate-limit.window", value = "100") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RateLimitConfigBean/window\".rate-limit.window-unit", value = "millis") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RateLimitConfigBean/minSpacing\".rate-limit.min-spacing", value = "100") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RateLimitConfigBean/minSpacing\".rate-limit.min-spacing-unit", value = "millis") +public class RateLimitConfigTest { + @Inject + private RateLimitConfigBean bean; + + @Test + public void value() throws Exception { + for (int i = 0; i < 3; i++) { + assertThat(bean.value()).isEqualTo("value"); + } + assertThatThrownBy(() -> bean.value()).isExactlyInstanceOf(RateLimitException.class); + } + + @Test + public void window() throws Exception { + for (int i = 0; i < 3; i++) { + assertThat(bean.window()).isEqualTo("window"); + } + assertThatThrownBy(() -> bean.window()).isExactlyInstanceOf(RateLimitException.class); + + Thread.sleep(500); + + assertThat(bean.window()).isEqualTo("window"); + } + + @Test + public void minSpacing() throws Exception { + assertThat(bean.minSpacing()).isEqualTo("minSpacing"); + assertThatThrownBy(() -> bean.minSpacing()).isExactlyInstanceOf(RateLimitException.class); + + Thread.sleep(500); + + assertThat(bean.minSpacing()).isEqualTo("minSpacing"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigBean.java new file mode 100644 index 000000000..5e88f5a08 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigBean.java @@ -0,0 +1,56 @@ +package io.smallrye.faulttolerance.config.better; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Retry; + +@ApplicationScoped +public class RetryConfigBean { + @Retry(delay = 0, jitter = 0) + public void maxRetries(AtomicInteger counter) { + counter.getAndIncrement(); + throw new TestException(); + } + + @Retry(maxDuration = 10000, durationUnit = ChronoUnit.MILLIS, maxRetries = 10000, delay = 200, jitter = 0) + public void maxDuration() { + throw new TestException(); + } + + @Retry(maxRetries = 5, delay = 2, delayUnit = ChronoUnit.SECONDS, jitter = 0) + public void delay() { + throw new TestException(); + } + + @Retry(maxRetries = 1, delay = 0, jitter = 0) + public void retryOn(RuntimeException e, AtomicInteger counter) { + counter.getAndIncrement(); + throw e; + } + + @Retry(retryOn = { TestConfigExceptionA.class, + TestConfigExceptionB.class }, abortOn = RuntimeException.class, maxRetries = 1, delay = 0, jitter = 0) + public void abortOn(RuntimeException e, AtomicInteger counter) { + counter.getAndIncrement(); + throw e; + } + + private long lastStartTime = 0; + + @Retry(abortOn = TestConfigExceptionA.class, delay = 0, jitter = 0, maxRetries = 1000, maxDuration = 10, durationUnit = ChronoUnit.SECONDS) + public void jitter() { + long startTime = System.nanoTime(); + if (lastStartTime != 0) { + Duration delay = Duration.ofNanos(startTime - lastStartTime); + if (delay.compareTo(Duration.ofMillis(100)) > 0) { + throw new TestConfigExceptionA(); + } + } + lastStartTime = startTime; + throw new TestException(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigTest.java new file mode 100644 index 000000000..52c73764e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/RetryConfigTest.java @@ -0,0 +1,109 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/maxRetries\".retry.max-retries", value = "10") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/maxDuration\".retry.max-duration", value = "1") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/maxDuration\".retry.max-duration-unit", value = "seconds") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/delay\".retry.delay", value = "2000") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/delay\".retry.delay-unit", value = "micros") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/retryOn\".retry.retry-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA,io.smallrye.faulttolerance.config.better.TestConfigExceptionB") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/abortOn\".retry.abort-on", value = "io.smallrye.faulttolerance.config.better.TestConfigExceptionA,io.smallrye.faulttolerance.config.better.TestConfigExceptionB1") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/jitter\".retry.jitter", value = "1") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.RetryConfigBean/jitter\".retry.jitter-unit", value = "seconds") +public class RetryConfigTest { + @Inject + private RetryConfigBean bean; + + @Test + public void maxRetries() { + AtomicInteger counter = new AtomicInteger(); + assertThatThrownBy(() -> bean.maxRetries(counter)).isExactlyInstanceOf(TestException.class); + assertThat(counter).hasValue(11); + } + + @Test + public void maxDuration() { + long startTime = System.nanoTime(); + assertThatThrownBy(() -> bean.maxDuration()).isExactlyInstanceOf(TestException.class); + long endTime = System.nanoTime(); + + Duration duration = Duration.ofNanos(endTime - startTime); + assertThat(duration).isLessThan(Duration.ofSeconds(8)); + } + + @Test + public void delay() { + long startTime = System.nanoTime(); + assertThatThrownBy(() -> bean.delay()).isExactlyInstanceOf(TestException.class); + long endTime = System.nanoTime(); + + Duration duration = Duration.ofNanos(endTime - startTime); + assertThat(duration).isLessThan(Duration.ofSeconds(8)); + } + + @Test + public void retryOn() { + AtomicInteger counter = new AtomicInteger(); + + counter.set(0); + assertThatThrownBy(() -> bean.retryOn(new TestException(), counter)).isExactlyInstanceOf(TestException.class); + assertThat(counter).hasValue(1); + + counter.set(0); + assertThatThrownBy(() -> bean.retryOn(new TestConfigExceptionA(), counter)) + .isExactlyInstanceOf(TestConfigExceptionA.class); + assertThat(counter).hasValue(2); + + counter.set(0); + assertThatThrownBy(() -> bean.retryOn(new TestConfigExceptionB(), counter)) + .isExactlyInstanceOf(TestConfigExceptionB.class); + assertThat(counter).hasValue(2); + + counter.set(0); + assertThatThrownBy(() -> bean.retryOn(new TestConfigExceptionB1(), counter)) + .isExactlyInstanceOf(TestConfigExceptionB1.class); + assertThat(counter).hasValue(2); + } + + @Test + public void abortOn() { + AtomicInteger counter = new AtomicInteger(); + + counter.set(0); + assertThatThrownBy(() -> bean.abortOn(new TestException(), counter)).isExactlyInstanceOf(TestException.class); + assertThat(counter).hasValue(1); + + counter.set(0); + assertThatThrownBy(() -> bean.abortOn(new TestConfigExceptionA(), counter)) + .isExactlyInstanceOf(TestConfigExceptionA.class); + assertThat(counter).hasValue(1); + + counter.set(0); + assertThatThrownBy(() -> bean.abortOn(new TestConfigExceptionB(), counter)) + .isExactlyInstanceOf(TestConfigExceptionB.class); + assertThat(counter).hasValue(2); + + counter.set(0); + assertThatThrownBy(() -> bean.abortOn(new TestConfigExceptionB1(), counter)) + .isExactlyInstanceOf(TestConfigExceptionB1.class); + assertThat(counter).hasValue(1); + } + + @Test + public void jitter() { + assertThatThrownBy(() -> bean.jitter()).isExactlyInstanceOf(TestConfigExceptionA.class); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionA.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionA.java new file mode 100644 index 000000000..a35489f57 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionA.java @@ -0,0 +1,4 @@ +package io.smallrye.faulttolerance.config.better; + +public class TestConfigExceptionA extends RuntimeException { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB.java new file mode 100644 index 000000000..bfe7be191 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB.java @@ -0,0 +1,4 @@ +package io.smallrye.faulttolerance.config.better; + +public class TestConfigExceptionB extends RuntimeException { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB1.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB1.java new file mode 100644 index 000000000..7bb1d24da --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestConfigExceptionB1.java @@ -0,0 +1,4 @@ +package io.smallrye.faulttolerance.config.better; + +public class TestConfigExceptionB1 extends TestConfigExceptionB { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestException.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestException.java new file mode 100644 index 000000000..43897140d --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TestException.java @@ -0,0 +1,4 @@ +package io.smallrye.faulttolerance.config.better; + +public class TestException extends RuntimeException { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigBean.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigBean.java new file mode 100644 index 000000000..899b50297 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigBean.java @@ -0,0 +1,31 @@ +package io.smallrye.faulttolerance.config.better; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Timeout; + +@ApplicationScoped +public class TimeoutConfigBean { + @Timeout(value = 1, unit = ChronoUnit.MILLIS) + public void value() throws InterruptedException { + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + } + + @Timeout(value = 1000, unit = ChronoUnit.MICROS) + public void unit() throws InterruptedException { + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + } + + @Timeout(value = 10, unit = ChronoUnit.MICROS) + @Asynchronous + public CompletionStage both() throws InterruptedException { + Thread.sleep(TimeUnit.MINUTES.toMillis(1)); + return CompletableFuture.completedFuture(null); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigTest.java new file mode 100644 index 000000000..b8916bb5e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/config/better/TimeoutConfigTest.java @@ -0,0 +1,58 @@ +package io.smallrye.faulttolerance.config.better; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.TimeoutConfigBean/value\".timeout.value", value = "1000") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.TimeoutConfigBean/unit\".timeout.unit", value = "millis") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.TimeoutConfigBean/both\".timeout.value", value = "1000") +@WithSystemProperty(key = "smallrye.faulttolerance.\"io.smallrye.faulttolerance.config.better.TimeoutConfigBean/both\".timeout.unit", value = "millis") +public class TimeoutConfigTest { + @Inject + private TimeoutConfigBean bean; + + @Test + public void value() { + doTest(() -> bean.value()); + } + + @Test + public void unit() { + doTest(() -> bean.unit()); + } + + @Test + public void both() { + doTest(() -> { + try { + bean.both().toCompletableFuture().get(1, TimeUnit.MINUTES); + } catch (ExecutionException e) { + throw e.getCause(); + } + }); + } + + private void doTest(ThrowingCallable action) { + long start = System.nanoTime(); + assertThatThrownBy(action).isExactlyInstanceOf(TimeoutException.class); + long end = System.nanoTime(); + + long durationInMillis = Duration.ofNanos(end - start).toMillis(); + assertThat(durationInMillis).isGreaterThan(800); + assertThat(durationInMillis).isLessThan(2000); + } +}