Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config: detect injected config value mismatch during static init #36281

Merged
merged 2 commits into from
Oct 9, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@
import io.quarkus.gizmo.TryBlock;
import io.quarkus.runtime.Application;
import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.ExecutionModeManager;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.NativeImageRuntimePropertiesRecorder;
import io.quarkus.runtime.PreventFurtherStepsException;
@@ -106,6 +107,14 @@ public class MainClassBuildStep {
void.class, StartupContext.class);
public static final MethodDescriptor CONFIGURE_STEP_TIME_ENABLED = ofMethod(StepTiming.class.getName(), "configureEnabled",
void.class);
public static final MethodDescriptor RUNTIME_EXECUTION_STATIC_INIT = ofMethod(ExecutionModeManager.class.getName(),
"staticInit", void.class);
public static final MethodDescriptor RUNTIME_EXECUTION_RUNTIME_INIT = ofMethod(ExecutionModeManager.class.getName(),
"runtimeInit", void.class);
public static final MethodDescriptor RUNTIME_EXECUTION_RUNNING = ofMethod(ExecutionModeManager.class.getName(),
"running", void.class);
public static final MethodDescriptor RUNTIME_EXECUTION_UNSET = ofMethod(ExecutionModeManager.class.getName(),
"unset", void.class);
public static final MethodDescriptor CONFIGURE_STEP_TIME_START = ofMethod(StepTiming.class.getName(), "configureStart",
void.class);
private static final DotName QUARKUS_APPLICATION = DotName.createSimple(QuarkusApplication.class.getName());
@@ -170,6 +179,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
lm);

mv.invokeStaticMethod(CONFIGURE_STEP_TIME_ENABLED);
mv.invokeStaticMethod(RUNTIME_EXECUTION_STATIC_INIT);

mv.invokeStaticMethod(ofMethod(Timing.class, "staticInitStarted", void.class, boolean.class),
mv.load(launchMode.isAuxiliaryApplication()));
@@ -227,6 +237,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
mv.load(i.getKey()), mv.load(i.getValue()));
}
mv.invokeStaticMethod(ofMethod(NativeImageRuntimePropertiesRecorder.class, "doRuntime", void.class));
mv.invokeStaticMethod(RUNTIME_EXECUTION_RUNTIME_INIT);

// Set the SSL system properties
if (!javaLibraryPathAdditionalPaths.isEmpty()) {
@@ -268,6 +279,8 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
loaders, constants, gizmoOutput, startupContext, tryBlock);
}

tryBlock.invokeStaticMethod(RUNTIME_EXECUTION_RUNNING);

// Startup log messages
List<String> featureNames = new ArrayList<>();
for (FeatureBuildItem feature : features) {
@@ -324,6 +337,7 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,

mv = file.getMethodCreator("doStop", void.class);
mv.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
mv.invokeStaticMethod(RUNTIME_EXECUTION_UNSET);
startupContext = mv.readStaticField(scField.getFieldDescriptor());
mv.invokeVirtualMethod(ofMethod(StartupContext.class, "close", void.class), startupContext);
mv.returnValue(null);
30 changes: 30 additions & 0 deletions core/runtime/src/main/java/io/quarkus/runtime/ExecutionMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.runtime;

/**
* The runtime execution mode.
*/
public enum ExecutionMode {

/**
* Static initializiation.
*/
STATIC_INIT,

/**
* Runtime initialization.
*/
RUNTIME_INIT,

/**
* The application is running.
*/
RUNNING,

UNSET,
;

public static ExecutionMode current() {
return ExecutionModeManager.getExecutionMode();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.runtime;

public final class ExecutionModeManager {

private static volatile ExecutionMode executionMode = ExecutionMode.UNSET;

public static void staticInit() {
executionMode = ExecutionMode.STATIC_INIT;
}

public static void runtimeInit() {
executionMode = ExecutionMode.RUNTIME_INIT;
}

public static void running() {
executionMode = ExecutionMode.RUNNING;
}

public static void unset() {
executionMode = ExecutionMode.UNSET;
}

public static ExecutionMode getExecutionMode() {
return executionMode;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package io.quarkus.runtime.annotations;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.eclipse.microprofile.config.inject.ConfigProperty;

/**
* Used to mark a {@link org.eclipse.microprofile.config.spi.ConfigSource},
* {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider} or {@link io.smallrye.config.ConfigSourceFactory}
* as safe to be initialized during STATIC INIT.
* Used to mark a configuration object as safe to be initialized during the STATIC INIT phase.
* <p>
* The target configuration objects include {@link org.eclipse.microprofile.config.spi.ConfigSource},
* {@link org.eclipse.microprofile.config.spi.ConfigSourceProvider}, {@link io.smallrye.config.ConfigSourceFactory} and
* {@link io.smallrye.config.ConfigMapping}. Moreover, this annotation can be used for
* {@link org.eclipse.microprofile.config.inject.ConfigProperty} injection points.
* <p>
*
* When a Quarkus application is starting up, Quarkus will execute first a static init method which contains some
* extensions actions and configurations. Example:
@@ -36,7 +44,7 @@
* previous code example and a ConfigSource that requires database access. In this case, it is impossible to properly
* initialize such ConfigSource, because the database services are not yet available so the ConfigSource in unusable.
*/
@Target(TYPE)
@Target({ TYPE, FIELD, PARAMETER })
@Retention(RUNTIME)
@Documented
public @interface StaticInitSafe {
Original file line number Diff line number Diff line change
@@ -30,7 +30,6 @@
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.CreationException;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigValue;
import org.eclipse.microprofile.config.inject.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -42,10 +41,8 @@
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;
import org.jboss.logging.Logger;

import io.quarkus.arc.Unremovable;
import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem;
@@ -67,7 +64,6 @@
import io.quarkus.deployment.builditem.ConfigPropertiesBuildItem;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.configuration.definition.RootDefinition;
import io.quarkus.deployment.recording.RecorderContext;
@@ -80,14 +76,11 @@
* MicroProfile Config related build steps.
*/
public class ConfigBuildStep {
private static final Logger LOGGER = Logger.getLogger(ConfigBuildStep.class.getName());

private static final DotName MP_CONFIG = DotName.createSimple(Config.class.getName());
private static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName());
static final DotName MP_CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName());
private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName());
private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName());

private static final DotName SR_CONFIG = DotName.createSimple(io.smallrye.config.SmallRyeConfig.class.getName());
private static final DotName SR_CONFIG_VALUE_NAME = DotName.createSimple(io.smallrye.config.ConfigValue.class.getName());

private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName());
@@ -474,67 +467,6 @@ void validateConfigPropertiesInjectionPoints(
toRegister.forEach(configProperties::produce);
}

@BuildStep
void warnStaticInitInjectionPoints(
CombinedIndexBuildItem indexBuildItem,
ValidationPhaseBuildItem validationPhase,
List<ConfigClassBuildItem> configClasses,
List<ConfigInjectionStaticInitBuildItem> configInjectionStaticInit,
BuildProducer<RunTimeConfigurationDefaultBuildItem> runTimeConfigurationDefault) {

// Add here annotated classes that are initialized during static init
Set<DotName> declaringClassCandidates = configInjectionStaticInit.stream()
.map(ConfigInjectionStaticInitBuildItem::getDeclaringCandidate).collect(toSet());

Set<Type> configClassesTypes = configClasses.stream().map(ConfigClassBuildItem::getTypes).flatMap(Collection::stream)
.collect(toSet());

for (InjectionPointInfo injectionPoint : validationPhase.getContext().getInjectionPoints()) {
if (injectionPoint.getType().name().equals(DotNames.INSTANCE)) {
continue;
}

Type type = Type.create(injectionPoint.getRequiredType().name(), Type.Kind.CLASS);
DotName injectionTypeName = null;
if (type.name().equals(MP_CONFIG) || type.name().equals(SR_CONFIG)) {
injectionTypeName = type.name();
} else if (injectionPoint.getRequiredQualifier(MP_CONFIG_PROPERTY_NAME) != null) {
injectionTypeName = MP_CONFIG_PROPERTY_NAME;
} else if (configClassesTypes.contains(type)) {
injectionTypeName = type.name();
}

if (injectionTypeName != null) {
AnnotationTarget target = injectionPoint.getTarget();
if (FIELD.equals(target.kind())) {
FieldInfo field = target.asField();
ClassInfo declaringClass = field.declaringClass();
Map<DotName, List<AnnotationInstance>> annotationsMap = declaringClass.annotationsMap();
for (DotName declaringClassCandidate : declaringClassCandidates) {
List<AnnotationInstance> annotationInstances = annotationsMap.get(declaringClassCandidate);
if (annotationInstances != null && annotationInstances.size() == 1) {
AnnotationInstance annotationInstance = annotationInstances.get(0);
if (annotationInstance.target().equals(declaringClass)) {
LOGGER.warn("Directly injecting a " +
injectionTypeName +
" into a " +
declaringClassCandidate +
" may lead to unexpected results. To ensure proper results, please " +
"change the type of the field to " +
ParameterizedType.create(DotNames.INSTANCE, new Type[] { type }, null) +
". Offending field is '" +
field.name() +
"' of class '" +
field.declaringClass() +
"'");
}
}
}
}
}
}
}

@BuildStep
@Record(RUNTIME_INIT)
void registerConfigClasses(
Original file line number Diff line number Diff line change
@@ -4,6 +4,11 @@

import io.quarkus.builder.item.MultiBuildItem;

/**
*
* @deprecated This build item is not used anymore
*/
@Deprecated(forRemoval = true)
public final class ConfigInjectionStaticInitBuildItem extends MultiBuildItem {
private final DotName declaringCandidate;

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.arc.deployment;

import org.jboss.jandex.DotName;

import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.runtime.ConfigStaticInitCheck;
import io.quarkus.arc.runtime.ConfigStaticInitCheckInterceptor;
import io.quarkus.arc.runtime.ConfigStaticInitValues;
import io.quarkus.deployment.annotations.BuildStep;

public class ConfigStaticInitBuildSteps {

@BuildStep
AdditionalBeanBuildItem registerBeans() {
return AdditionalBeanBuildItem.builder()
.addBeanClasses(ConfigStaticInitCheckInterceptor.class, ConfigStaticInitValues.class,
ConfigStaticInitCheck.class)
.build();
}

@BuildStep
AnnotationsTransformerBuildItem transformConfigProducer() {
DotName configProducerName = DotName.createSimple("io.smallrye.config.inject.ConfigProducer");

return new AnnotationsTransformerBuildItem(AnnotationsTransformer.appliedToMethod().whenMethod(m -> {
// Apply to all producer methods declared on io.smallrye.config.inject.ConfigProducer
return m.declaringClass().name().equals(configProducerName)
&& m.hasAnnotation(DotNames.PRODUCES)
&& m.hasAnnotation(ConfigBuildStep.MP_CONFIG_PROPERTY_NAME);
}).thenTransform(t -> {
t.add(ConfigStaticInitCheck.class);
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.arc.test.config.staticinit;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;

@Singleton
public class StaticInitBean {

@ConfigProperty(name = "apfelstrudel")
String value;

// bean is instantiated during STATIC_INIT
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkus.arc.test.config.staticinit;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;

import org.eclipse.microprofile.config.spi.ConfigSource;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class StaticInitConfigInjectionFailureTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addClasses(StaticInitBean.class, StaticInitEagerBean.class, UnsafeConfigSource.class)
.addAsServiceProvider(ConfigSource.class, UnsafeConfigSource.class)
// the value from application.properties should be injected during STATIC_INIT
.addAsResource(new StringAsset("apfelstrudel=jandex"), "application.properties"))
.assertException(t -> {
assertThat(t).isInstanceOf(IllegalStateException.class)
.hasMessageContainingAll(
"A runtime config property value differs from the value that was injected during the static intialization phase",
"the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitBean#value",
"the runtime value of 'apfelstrudel' is [gizmo] but the value [jandex] was injected into io.quarkus.arc.test.config.staticinit.StaticInitEagerBean#value");
});

@Test
public void test() {
fail();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.arc.test.config.staticinit;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;

@Singleton
public class StaticInitEagerBean {

@ConfigProperty(name = "apfelstrudel")
Instance<String> value;

// bean is instantiated during STATIC_INIT
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
// this should trigger the failure
value.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.arc.test.config.staticinit;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;

@Singleton
public class StaticInitLazyBean {

@ConfigProperty(name = "apfelstrudel")
Instance<String> value;

// bean is instantiated during STATIC_INIT
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
// value is not obtained...
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.arc.test.config.staticinit;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkus.runtime.annotations.StaticInitSafe;

@Singleton
public class StaticInitSafeBean {

String value;

public StaticInitSafeBean(@StaticInitSafe @ConfigProperty(name = "apfelstrudel") String value) {
this.value = value;
}

// bean is instantiated during STATIC_INIT
void onInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.arc.test.config.staticinit;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;

import org.eclipse.microprofile.config.spi.ConfigSource;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;

public class StaticInitSafeConfigInjectionTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addClasses(StaticInitSafeBean.class, StaticInitLazyBean.class, UnsafeConfigSource.class)
.addAsServiceProvider(ConfigSource.class, UnsafeConfigSource.class)
// the value from application.properties should be injected during STATIC_INIT
.addAsResource(new StringAsset("apfelstrudel=jandex"), "application.properties"));

@Inject
StaticInitSafeBean bean;

@Test
public void test() {
assertEquals("jandex", bean.value);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.quarkus.arc.test.config.staticinit;

import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

// Intentionally not annotated with @StaticInitSafe so that it's not considered durin the STATIC_INIT
public class UnsafeConfigSource implements ConfigSource {

@Override
public Set<String> getPropertyNames() {
return Set.of("apfelstrudel");
}

@Override
public String getValue(String propertyName) {
return propertyName.equals("apfelstrudel") ? "gizmo" : null;
}

@Override
public String getName() {
return "Unsafe Test";
}

@Override
public int getOrdinal() {
return 500;
}

}
Original file line number Diff line number Diff line change
@@ -32,6 +32,8 @@ public Object create(CreationalContext<Object> creationalContext, Map<String, Ob
throw new IllegalStateException("No current injection point found");
}

ConfigStaticInitCheckInterceptor.recordConfigValue(injectionPoint, null);

try {
return ConfigProducerUtil.getValue(injectionPoint, ConfigProvider.getConfig());
} catch (Exception e) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.arc.runtime;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import jakarta.interceptor.InterceptorBinding;

/**
* Interceptor binding for {@link ConfigStaticInitCheckInterceptor}.
*/
@InterceptorBinding
@Retention(RUNTIME)
@Target(TYPE)
public @interface ConfigStaticInitCheck {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.quarkus.arc.runtime;

import java.lang.annotation.Annotation;

import jakarta.annotation.Priority;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.inject.Inject;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

import io.quarkus.arc.Arc;
import io.quarkus.arc.impl.InjectionPointProvider;
import io.quarkus.runtime.ExecutionMode;
import io.quarkus.runtime.annotations.StaticInitSafe;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.inject.ConfigProducer;

/**
* Intercepts the producer methods declared on {@link ConfigProducer} and records the config value during the static
* initialization phase unless the injection point is annotated with {@link StaticInitSafe}. It's no-op for any other execution
* mode.
*/
@ConfigStaticInitCheck
@Priority(jakarta.interceptor.Interceptor.Priority.PLATFORM_BEFORE)
@Interceptor
public class ConfigStaticInitCheckInterceptor {

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

@Inject
ConfigStaticInitValues configValues;

@AroundInvoke
Object aroundInvoke(InvocationContext context) throws Exception {
recordConfigValue(null, configValues);
return context.proceed();
}

static void recordConfigValue(InjectionPoint injectionPoint, ConfigStaticInitValues configValues) {
if (ExecutionMode.current() != ExecutionMode.STATIC_INIT) {
// No-op for any other execution mode
return;
}
if (injectionPoint == null) {
injectionPoint = InjectionPointProvider.get();
}
if (injectionPoint == null) {
throw new IllegalStateException("No current injection point found");
}
ConfigProperty configProperty = null;
for (Annotation qualifier : injectionPoint.getQualifiers()) {
if (qualifier instanceof ConfigProperty) {
configProperty = ((ConfigProperty) qualifier);
}
}
if (configProperty == null
|| injectionPoint.getAnnotated().isAnnotationPresent(StaticInitSafe.class)) {
return;
}
String propertyName = configProperty.name();
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
String value = config.getConfigValue(propertyName).getValue();
if (value == null) {
value = getDefaultValue(injectionPoint, configProperty);
}
if (value == null) {
LOG.debugf("No config value found for %s", propertyName);
return;
}
if (configValues == null) {
configValues = Arc.container().instance(ConfigStaticInitValues.class).get();
}
configValues.recordConfigValue(injectionPoint, propertyName, value);
}

private static String getDefaultValue(InjectionPoint injectionPoint, ConfigProperty configProperty) {
String str = configProperty.defaultValue();
if (!ConfigProperty.UNCONFIGURED_VALUE.equals(str)) {
return str;
}
if (injectionPoint.getType() instanceof Class && ((Class<?>) injectionPoint.getType()).isPrimitive()) {
if (injectionPoint.getType() == char.class) {
return null;
} else if (injectionPoint.getType() == boolean.class) {
return "false";
} else {
return "0";
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.quarkus.arc.runtime;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jakarta.annotation.Priority;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.AnnotatedConstructor;
import jakarta.enterprise.inject.spi.AnnotatedField;
import jakarta.enterprise.inject.spi.AnnotatedParameter;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.inject.Singleton;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.ConfigValue;

import io.quarkus.runtime.StartupEvent;
import io.smallrye.config.SmallRyeConfig;

/**
* This is a store for all config values injected (directly or programmatically) during the static init phase.
* <p>
* The values are then compared with the current values during the runtime init, i.e. when the application starts. If a mismatch
* is found the startup fails with an actionable error.
*/
@Singleton
public class ConfigStaticInitValues {

private final List<InjectedValue> injectedValues = Collections.synchronizedList(new ArrayList<>());

void recordConfigValue(InjectionPoint injectionPoint, String name, String value) {
injectedValues.add(new InjectedValue(injectionPoint, name, value));
}

void onStart(@Observes @Priority(Integer.MIN_VALUE) StartupEvent event) {
if (injectedValues.isEmpty()) {
// No config values were recorded during static init phase
return;
}
SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
List<String> mismatches = new ArrayList<>();
for (InjectedValue injectedValue : injectedValues) {
ConfigValue currentValue = config.getConfigValue(injectedValue.name);
if (currentValue.getValue() != null && !injectedValue.value.equals(currentValue.getValue())) {
mismatches.add(
" - the runtime value of '" + injectedValue.name + "' is [" + currentValue.getValue()
+ "] but the value [" + injectedValue.value
+ "] was injected into "
+ injectedValue.injectionPointInfo);
}
}
injectedValues.clear();
if (!mismatches.isEmpty()) {
throw new IllegalStateException(
"A runtime config property value differs from the value that was injected during the static intialization phase:\n"
+ String.join("\n", mismatches)
+ "\n\nIf that's intentional then annotate the injected field/parameter with @io.quarkus.runtime.annotations.StaticInitSafe to eliminate the false positive.");
}
}

private static class InjectedValue {

private final String injectionPointInfo;
private final String name;
private final String value;

private InjectedValue(InjectionPoint injectionPoint, String name, String value) {
this.injectionPointInfo = injectionPointToString(injectionPoint);
this.name = name;
this.value = value;
}

}

private static String injectionPointToString(InjectionPoint injectionPoint) {
Annotated annotated = injectionPoint.getAnnotated();
if (annotated instanceof AnnotatedField) {
AnnotatedField<?> field = (AnnotatedField<?>) annotated;
return field.getDeclaringType().getJavaClass().getName() + "#" + field.getJavaMember().getName();
} else if (annotated instanceof AnnotatedParameter) {
AnnotatedParameter<?> param = (AnnotatedParameter<?>) annotated;
if (param.getDeclaringCallable() instanceof AnnotatedConstructor) {
return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "()";
} else {
return param.getDeclaringCallable().getDeclaringType().getJavaClass().getName() + "#"
+ param.getDeclaringCallable().getJavaMember().getName() + "()";
}
}
return injectionPoint.toString();
}

}
Original file line number Diff line number Diff line change
@@ -43,7 +43,6 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.ConfigInjectionStaticInitBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.Capabilities;
@@ -181,11 +180,6 @@ ResteasyInjectionReadyBuildItem setupResteasyInjection(
return new ResteasyInjectionReadyBuildItem(injectorFactory);
}

@BuildStep
ConfigInjectionStaticInitBuildItem configInjectionStaticInitProvider() {
return new ConfigInjectionStaticInitBuildItem(ResteasyDotNames.PROVIDER);
}

@BuildStep
JaxrsProvidersToRegisterBuildItem setupProviders(BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
CombinedIndexBuildItem indexBuildItem,

This file was deleted.

Original file line number Diff line number Diff line change
@@ -81,7 +81,6 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.ConfigInjectionStaticInitBuildItem;
import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem;
import io.quarkus.arc.deployment.ContextRegistrationPhaseBuildItem.ContextConfiguratorBuildItem;
import io.quarkus.arc.deployment.CustomScopeBuildItem;
@@ -685,11 +684,6 @@ SyntheticBeanBuildItem servletContextBean(
.supplier(recorder.servletContextSupplier()).done();
}

@BuildStep
ConfigInjectionStaticInitBuildItem configInjectionStaticInitAnnotations() {
return new ConfigInjectionStaticInitBuildItem(WEB_FILTER);
}

/**
* Process a single index.
*

This file was deleted.

Original file line number Diff line number Diff line change
@@ -4,10 +4,11 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.quarkus.jackson.ObjectMapperCustomizer
import jakarta.inject.Singleton
import org.eclipse.microprofile.config.inject.ConfigProperty
import io.quarkus.runtime.annotations.StaticInitSafe

@Singleton
class RegisterCustomModuleCustomizer : ObjectMapperCustomizer {
@ConfigProperty(name = "test.prop") lateinit var testProp: String
@StaticInitSafe @ConfigProperty(name = "test.prop") lateinit var testProp: String

override fun customize(objectMapper: ObjectMapper) {
GreetingResource.MY_PROPERTY.set(testProp)