Skip to content

Commit

Permalink
Improve type discovery in Logback AOT contribution
Browse files Browse the repository at this point in the history
Logback can infer the Java class to which an XML tag should be mapped
by looking for a setter method on the class to which the parent tag
was mapped. This commits ensures that reflection hints are added for
such classes.

Fixes gh-32839
  • Loading branch information
wilkinsona committed Oct 21, 2022
1 parent eebe23a commit b0b2818
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 17 deletions.
Expand Up @@ -31,13 +31,15 @@
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.joran.spi.RuleStore;
import ch.qos.logback.core.joran.util.PropertySetter;
import ch.qos.logback.core.joran.util.beans.BeanDescription;
import ch.qos.logback.core.model.ComponentModel;
import ch.qos.logback.core.model.Model;
Expand All @@ -46,6 +48,7 @@
import ch.qos.logback.core.model.processor.ModelInterpretationContext;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.AggregationType;

import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
Expand All @@ -62,6 +65,7 @@
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.SingletonSupplier;

/**
* Extended version of the Logback {@link JoranConfigurator} that adds additional Spring
Expand Down Expand Up @@ -201,32 +205,64 @@ private Set<Class<? extends Serializable>> serializationTypes(Model model) {
}

private Set<String> reflectionTypes(Model model) {
return reflectionTypes(model, () -> null);
}

private Set<String> reflectionTypes(Model model, Supplier<Object> parent) {
Set<String> reflectionTypes = new HashSet<>();
if (model instanceof ComponentModel) {
String className = ((ComponentModel) model).getClassName();
processComponent(className, reflectionTypes);
}
String tag = model.getTag();
if (tag != null) {
String componentType = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
Class<?> componentType = determineType(model, parent);
if (componentType != null) {
processComponent(componentType, reflectionTypes);
}
Supplier<Object> componentSupplier = SingletonSupplier.ofNullable(() -> instantiate(componentType));
for (Model submodel : model.getSubModels()) {
reflectionTypes.addAll(reflectionTypes(submodel));
reflectionTypes.addAll(reflectionTypes(submodel, componentSupplier));
}
return reflectionTypes;
}

private void processComponent(String componentTypeName, Set<String> reflectionTypes) {
if (componentTypeName != null) {
componentTypeName = this.modelInterpretationContext.getImport(componentTypeName);
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
.getBeanDescription(loadComponentType(componentTypeName));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
reflectionTypes.add(componentTypeName);
private Class<?> determineType(Model model, Supplier<Object> parentSupplier) {
String className = null;
if (model instanceof ComponentModel) {
className = ((ComponentModel) model).getClassName();
}
if (className == null) {
String tag = model.getTag();
if (tag != null) {
className = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
if (className == null) {
Class<?> type = inferTypeFromParent(parentSupplier, tag);
if (type != null) {
return type;
}
}
}
}
if (className != null) {
className = this.modelInterpretationContext.getImport(className);
return loadComponentType(className);
}
return null;
}

private Class<?> inferTypeFromParent(Supplier<Object> parentSupplier, String tag) {
Object parent = parentSupplier.get();
if (parent != null) {
try {
Class<?> typeFromSetter = new PropertySetter(
this.modelInterpretationContext.getBeanDescriptionCache(), parent)
.getClassNameViaImplicitRules(tag, AggregationType.AS_COMPLEX_PROPERTY,
this.modelInterpretationContext.getDefaultNestedComponentRegistry());
if (typeFromSetter != null) {
return typeFromSetter;
}
}
catch (Exception ex) {
// Continue
}
}
return null;
}

private Class<?> loadComponentType(String componentType) {
Expand All @@ -238,6 +274,23 @@ private Class<?> loadComponentType(String componentType) {
}
}

private Object instantiate(Class<?> type) {
try {
return type.getConstructor().newInstance();
}
catch (Exception ex) {
return null;
}
}

private void processComponent(Class<?> componentType, Set<String> reflectionTypes) {
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
.getBeanDescription(componentType);
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
reflectionTypes.add(componentType.getCanonicalName());
}

private Collection<String> parameterTypesNames(Collection<Method> methods) {
return methods.stream()
.filter((method) -> !method.getDeclaringClass().equals(ContextAware.class)
Expand Down
Expand Up @@ -31,6 +31,7 @@
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.joran.spi.DefaultClass;
import ch.qos.logback.core.model.ComponentModel;
import ch.qos.logback.core.model.ImplicitModel;
import ch.qos.logback.core.model.ImportModel;
Expand Down Expand Up @@ -149,6 +150,34 @@ void componentModelReferencingImportedClassNameIsRegisteredForReflection() {
.accepts(generationContext.getRuntimeHints());
}

@Test
void typeFromParentsSetterIsRegisteredForReflection() {
ImplicitModel implementation = new ImplicitModel();
implementation.setTag("implementation");
ComponentModel component = new ComponentModel();
component.setClassName(Outer.class.getName());
component.getSubModels().add(implementation);
TestGenerationContext generationContext = applyContribution(component);
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Outer.class))
.accepts(generationContext.getRuntimeHints());
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
.accepts(generationContext.getRuntimeHints());
}

@Test
void typeFromParentsDefaultClassAnnotatedSetterIsRegisteredForReflection() {
ImplicitModel contract = new ImplicitModel();
contract.setTag("contract");
ComponentModel component = new ComponentModel();
component.setClassName(OuterWithDefaultClass.class.getName());
component.getSubModels().add(contract);
TestGenerationContext generationContext = applyContribution(component);
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(OuterWithDefaultClass.class))
.accepts(generationContext.getRuntimeHints());
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
.accepts(generationContext.getRuntimeHints());
}

private Predicate<RuntimeHints> invokePublicConstructorsOf(String name) {
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(name))
.withMemberCategory(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
Expand Down Expand Up @@ -202,4 +231,29 @@ private void withSystemProperty(String name, String value, Runnable action) {
}
}

public static class Outer {

public void setImplementation(Implementation implementation) {

}

}

public static class OuterWithDefaultClass {

@DefaultClass(Implementation.class)
public void setContract(Contract contract) {

}

}

public static class Implementation implements Contract {

}

public interface Contract {

}

}

0 comments on commit b0b2818

Please sign in to comment.