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

Properly handle missing classes in metadata + precalculated requirements #10936

Merged
merged 5 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
Expand Up @@ -752,7 +752,7 @@ private static void pushEmptyObjectsArray(GeneratorAdapter methodVisitor) {
methodVisitor.getStatic(Type.getType(ArrayUtils.class), "EMPTY_OBJECT_ARRAY", Type.getType(Object[].class));
}

private static void invokeLoadClassValueMethod(
public static void invokeLoadClassValueMethod(
Type declaringType,
ClassVisitor declaringClassWriter,
GeneratorAdapter methodVisitor,
Expand Down

Large diffs are not rendered by default.

43 changes: 40 additions & 3 deletions core/src/main/java/io/micronaut/core/util/CollectionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,31 @@
*/
package io.micronaut.core.util;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.UsedByGeneratedCode;
import io.micronaut.core.convert.ConversionService;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;

/**
* <p>Utility methods for working with {@link java.util.Collection} types</p>.
Expand Down Expand Up @@ -458,4 +476,23 @@ public static <T> Set<T> iterableToSet(Iterable<T> iterable) {
}

}

/**
* Create an enum set from an array.
* NOTE: At least one item is required
*
* @param enums The array of enums
* @param <E> The enum type
* @return The enum set
* @since 4.6
*/
@NonNull
public static <E extends Enum<E>> EnumSet<E> enumSet(@NonNull E... enums) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add nullability annotations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of nullability annotations?

if (enums.length == 0) {
throw new IllegalStateException("At least one item is required!");
}
EnumSet<E> set = EnumSet.noneOf(enums[0].getDeclaringClass());
set.addAll(Arrays.asList(enums));
return set;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class RequiresBeanSpec extends Specification {
def list = e.message.readLines().toList()
list[0] == 'No bean of type [io.micronaut.inject.configurations.requiresproperty.RequiresProperty] exists. '
list[1] == '* [RequiresProperty] is disabled because it is within the package [io.micronaut.inject.configurations.requiresproperty] which is disabled due to bean requirements: '
list[2] == ' - Required property [data-source.url] with value [null] not present'
list[2] == ' - Required property [data-source.url] not present'

cleanup:
context.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class MyBean {
lines[1] == "* [MyBean] requires the presence of a bean of type [test.MyClient]."
lines[2] == " * [MyClient] requires the presence of a bean of type [test.MyConfiguration]."
lines[3] == " * [MyConfiguration] is disabled because:"
lines[4] == " - Required property [myconf] with value [null] not present"
lines[4] == " - Required property [myconf] not present"
cleanup:
context.close()
}
Expand Down Expand Up @@ -89,7 +89,7 @@ class MyBean {
lines[3] == " * [MyMultiConfiguration] a candidate of [MyConfiguration] is disabled because:"
lines[4] == " - Configuration requires entries under the prefix: [myconf2.multiple.default]"
lines[5] == " * [MyDefaultConfiguration] a candidate of [MyConfiguration] is disabled because:"
lines[6] == " - Required property [myconf] with value [null] not present"
lines[6] == " - Required property [myconf] not present"
lines.size() == 7
cleanup:
context.close()
Expand Down Expand Up @@ -140,11 +140,11 @@ class MyBean {
lines[1] == "* [MyBean] requires the presence of a bean of type [test.MyClient]."
lines[2] == " * [MyClient] requires the presence of a bean of type [test.MyConfiguration]."
lines[3] == " * [MyDefaultConfiguration] a candidate of [MyConfiguration] is disabled because:"
lines[4] == " - Required property [myconf] with value [null] not present"
lines[4] == " - Required property [myconf] not present"
lines[5] == " * [MyMultiConfiguration] a candidate of [MyConfiguration] is disabled because:"
lines[6] == " - No bean of type [test.MyHelper] present within context"
lines[7] == " * [MyHelper] is disabled because:"
lines[8] == " - Required property [myconf.helper] with value [null] not present"
lines[8] == " - Required property [myconf.helper] not present"

lines.size() == 9
cleanup:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,26 @@ public boolean isEnabled(@NonNull BeanContext context, @Nullable BeanResolutionC
this, resolutionContext);
boolean enabled = condition == null || condition.matches(conditionContext);
if (!enabled) {
if (ConditionLog.LOG.isDebugEnabled()) {
if (this instanceof BeanConfiguration) {
ConditionLog.LOG.debug("{} will not be loaded due to failing conditions:", this);
} else {
ConditionLog.LOG.debug("Bean [{}] will not be loaded due to failing conditions:", this);
}
for (Failure failure : conditionContext.getFailures()) {
ConditionLog.LOG.debug("* {}", failure.getMessage());
}
}
defaultBeanContext.trackDisabledComponent(conditionContext);
onFail(conditionContext, defaultBeanContext);
}

return enabled;
}

protected final void onFail(DefaultConditionContext<AbstractBeanContextConditional> conditionContext, DefaultBeanContext defaultBeanContext) {
if (ConditionLog.LOG.isDebugEnabled()) {
if (this instanceof BeanConfiguration) {
ConditionLog.LOG.debug("{} will not be loaded due to failing conditions:", this);
} else {
ConditionLog.LOG.debug("Bean [{}] will not be loaded due to failing conditions:", this);
}
for (Failure failure : conditionContext.getFailures()) {
ConditionLog.LOG.debug("* {}", failure.getMessage());
}
}
defaultBeanContext.trackDisabledComponent(conditionContext);
}

@SuppressWarnings("java:S3416")
static final class ConditionLog {
static final Logger LOG = LoggerFactory.getLogger(Condition.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
*/
package io.micronaut.context;

import io.micronaut.context.condition.Condition;
import io.micronaut.context.condition.ConditionContext;
import io.micronaut.context.conditions.MatchesDynamicCondition;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.ExecutableMethodsDefinition;
import io.micronaut.inject.annotation.EvaluatedAnnotationMetadata;

import java.util.Map;

Expand All @@ -34,6 +39,10 @@
*/
public abstract class AbstractInitializableBeanDefinitionAndReference<T> extends AbstractInitializableBeanDefinition<T> implements BeanDefinitionReference<T> {

private final Throwable failedInitialization;
private final Condition[] preLoadConditions;
private final Condition[] postLoadConditions;

protected AbstractInitializableBeanDefinitionAndReference(Class<T> beanType,
@Nullable MethodOrFieldReference constructor,
@Nullable AnnotationMetadata annotationMetadata,
Expand All @@ -43,9 +52,68 @@ protected AbstractInitializableBeanDefinitionAndReference(Class<T> beanType,
@Nullable ExecutableMethodsDefinition<T> executableMethodsDefinition,
@Nullable Map<String, Argument<?>[]> typeArgumentsMap,
@NonNull PrecalculatedInfo precalculatedInfo) {
this(beanType, constructor, annotationMetadata, methodInjection, fieldInjection, annotationInjection, executableMethodsDefinition, typeArgumentsMap, precalculatedInfo, null, null, null);
}

protected AbstractInitializableBeanDefinitionAndReference(Class<T> beanType,
@Nullable MethodOrFieldReference constructor,
@Nullable AnnotationMetadata annotationMetadata,
@Nullable MethodReference[] methodInjection,
@Nullable FieldReference[] fieldInjection,
@Nullable AnnotationReference[] annotationInjection,
@Nullable ExecutableMethodsDefinition<T> executableMethodsDefinition,
@Nullable Map<String, Argument<?>[]> typeArgumentsMap,
@NonNull PrecalculatedInfo precalculatedInfo,
@Nullable Condition[] preLoadConditions,
@Nullable Condition[] postLoadConditions,
@Nullable Throwable failedInitialization) {
super(beanType, constructor, annotationMetadata, methodInjection, fieldInjection, annotationInjection, executableMethodsDefinition, typeArgumentsMap, precalculatedInfo);
this.failedInitialization = failedInitialization;
this.preLoadConditions = preLoadConditions;
this.postLoadConditions = postLoadConditions;
}

/**
* Is enabled bean definition.
*
* @param context The bean context
* @param resolutionContext The resolution context
* @param preCheck if it's a pre-load / post-load
* @return true if enabled
*/
public final boolean isEnabled(BeanContext context, BeanResolutionContext resolutionContext, boolean preCheck) {
dstepanov marked this conversation as resolved.
Show resolved Hide resolved
if (preLoadConditions != null && postLoadConditions != null) {
DefaultBeanContext defaultBeanContext = (DefaultBeanContext) context;
DefaultConditionContext<AbstractBeanContextConditional> conditionContext = new DefaultConditionContext<>(
defaultBeanContext,
this, resolutionContext);
boolean matches;
if (preCheck) {
matches = matches(conditionContext, preLoadConditions);
} else {
matches = matches(conditionContext, postLoadConditions);
}
if (matches) {
return true;
}
onFail(conditionContext, defaultBeanContext);
return false;
}
if (preCheck) {
// new implementation of definition and reference always did all checks in post checks
return true;
}
return isEnabled(context, resolutionContext);
}

private static boolean matches(ConditionContext<?> conditionContext, Condition[] conditions) {
for (Condition condition : conditions) {
if (!condition.matches(conditionContext)) {
return false;
}
}
return true;
}

/**
* Represents {@link BeanDefinitionReference#getBeanDefinitionName()} when the class implements {@link BeanDefinitionReference}.
Expand All @@ -59,6 +127,9 @@ public final String getBeanDefinitionName() {

@Override
public final BeanDefinition<T> load(BeanContext context) {
if (failedInitialization != null) {
throw new BeanInstantiationException("Failed to initialize the bean [" + getBeanType() + "]: " + failedInitialization.getMessage(), failedInitialization);
}
BeanDefinition<T> definition = load();
if (definition instanceof EnvironmentConfigurable environmentConfigurable) {
if (context instanceof DefaultApplicationContext applicationContext) {
Expand All @@ -71,6 +142,22 @@ public final BeanDefinition<T> load(BeanContext context) {
if (definition instanceof BeanContextConfigurable ctxConfigurable) {
ctxConfigurable.configure(context);
}
if (postLoadConditions != null) {
for (int i = 0; i < postLoadConditions.length; i++) {
Condition postStartCondition = postLoadConditions[i];
if (postStartCondition instanceof MatchesDynamicCondition matchesDynamicCondition) {
AnnotationMetadata annotationMetadata = EvaluatedAnnotationMetadata.wrapIfNecessary(matchesDynamicCondition.annotationMetadata());
if (annotationMetadata instanceof EvaluatedAnnotationMetadata eam) {
eam.configure(context);
eam.setBeanDefinition(this);
}
postLoadConditions[i] = new MatchesDynamicCondition(
annotationMetadata
);
}
}

}
return definition;
}

Expand Down
24 changes: 18 additions & 6 deletions inject/src/main/java/io/micronaut/context/DefaultBeanContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,8 @@ protected void initializeContext(
* @return The candidates
*/
@NonNull
protected <T> Collection<BeanDefinition<T>> findBeanCandidates(@Nullable BeanResolutionContext resolutionContext,
@Internal
public final <T> Collection<BeanDefinition<T>> findBeanCandidates(@Nullable BeanResolutionContext resolutionContext,
@NonNull Argument<T> beanType,
@Nullable BeanDefinition<?> filter) {
Predicate<BeanDefinition<T>> predicate = filter == null ? null : definition -> !definition.equals(filter);
Expand Down Expand Up @@ -4206,9 +4207,6 @@ static final class BeanDefinitionProducer {

BeanDefinitionProducer(@NonNull BeanDefinitionReference reference) {
this.reference = reference;
if (reference instanceof AbstractInitializableBeanDefinitionAndReference<?>) {
referenceEnabled = true; // Postpone validation check
}
}

public boolean isReferenceEnabled(DefaultBeanContext context) {
Expand All @@ -4222,7 +4220,7 @@ public boolean isReferenceEnabled(DefaultBeanContext context, @Nullable BeanReso
return false;
}
if (referenceEnabled == null) {
if (ref.isEnabled(context, resolutionContext)) {
if (isReferenceEnabled(ref, context, resolutionContext)) {
referenceEnabled = true;
} else {
referenceEnabled = false;
Expand All @@ -4232,6 +4230,13 @@ public boolean isReferenceEnabled(DefaultBeanContext context, @Nullable BeanReso
return referenceEnabled;
}

private boolean isReferenceEnabled(BeanDefinitionReference<?> ref, DefaultBeanContext context, BeanResolutionContext resolutionContext) {
if (ref instanceof io.micronaut.context.AbstractInitializableBeanDefinitionAndReference<?> referenceAndDefinition) {
return referenceAndDefinition.isEnabled(context, resolutionContext, true);
}
return reference.isEnabled(context);
}

public boolean isDisabled() {
if (reference == null) {
return true;
Expand All @@ -4254,7 +4259,7 @@ public boolean isDefinitionEnabled(DefaultBeanContext context, @Nullable BeanRes
if (definitionEnabled == null) {
if (isReferenceEnabled(context, resolutionContext)) {
BeanDefinition <?> def = getDefinition(context);
if (def.isEnabled(context, resolutionContext)) {
if (isDefinitionEnabled(context, resolutionContext, def)) {
definition = def;
definitionEnabled = true;
} else {
Expand All @@ -4267,6 +4272,13 @@ public boolean isDefinitionEnabled(DefaultBeanContext context, @Nullable BeanRes
return definitionEnabled;
}

private boolean isDefinitionEnabled(DefaultBeanContext context, BeanResolutionContext resolutionContext, BeanDefinition<?> def) {
if (def instanceof io.micronaut.context.AbstractInitializableBeanDefinitionAndReference<?> definitionAndReference) {
return definitionAndReference.isEnabled(context, resolutionContext, false);
}
return def.isEnabled(context, resolutionContext);
}

public <T> BeanDefinitionReference<T> getReference() {
// The reference needs to be assigned to a new variable as it can change between checks
Boolean refEnabled = referenceEnabled;
Expand Down
Loading
Loading