Skip to content

Commit

Permalink
#1113 Initialize application provider late to allow integrated binder…
Browse files Browse the repository at this point in the history
… processing
  • Loading branch information
GuusLieben committed Nov 12, 2024
1 parent 332f3a3 commit bb19afd
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.dockbox.hartshorn.inject;

import org.dockbox.hartshorn.inject.component.ComponentRegistry;

public interface ManagedComponentEnvironment extends InjectorEnvironment {

/**
* @return the {@link ComponentRegistry} that is used by this {@link ManagedComponentEnvironment} to locate components
*/
ComponentRegistry componentRegistry();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@

import org.checkerframework.checker.nullness.qual.NonNull;
import org.dockbox.hartshorn.inject.InjectionCapableApplication;
import org.dockbox.hartshorn.inject.ManagedComponentEnvironment;
import org.dockbox.hartshorn.inject.binding.DefaultBindingConfigurerContext;
import org.dockbox.hartshorn.inject.component.ComponentRegistry;
import org.dockbox.hartshorn.inject.annotations.configuration.Configuration;
import org.dockbox.hartshorn.inject.condition.ConditionMatcher;
import org.dockbox.hartshorn.inject.annotations.configuration.Binds;
import org.dockbox.hartshorn.inject.graph.AbstractContainerDependencyResolver;
import org.dockbox.hartshorn.inject.graph.ComponentConfigurationException;
import org.dockbox.hartshorn.inject.graph.DependencyResolver;
import org.dockbox.hartshorn.inject.graph.declaration.DependencyContext;
import org.dockbox.hartshorn.inject.graph.declaration.DependencyDeclarationContext;
Expand All @@ -37,6 +39,7 @@
import org.dockbox.hartshorn.inject.graph.strategy.MethodAwareBindingStrategyContext;
import org.dockbox.hartshorn.inject.graph.strategy.MethodInstanceBindingStrategy;
import org.dockbox.hartshorn.inject.graph.strategy.SimpleBindingStrategyRegistry;
import org.dockbox.hartshorn.inject.provider.ComponentProviderOrchestrator;
import org.dockbox.hartshorn.util.ContextualInitializer;
import org.dockbox.hartshorn.util.Customizer;
import org.dockbox.hartshorn.util.LazyStreamableConfigurer;
Expand Down Expand Up @@ -125,7 +128,16 @@ public static ContextualInitializer<InjectionCapableApplication, DependencyResol
binder.bind(ConditionMatcher.class).singleton(conditionMatcher);
});

ComponentRegistry componentRegistry = application.defaultProvider().get(ComponentRegistry.class);
final ComponentRegistry componentRegistry;
if (application.environment() instanceof ManagedComponentEnvironment environment) {
componentRegistry = environment.componentRegistry();
}
else if (application.defaultProvider() instanceof ComponentProviderOrchestrator orchestrator) {
componentRegistry = orchestrator.componentRegistry();
}
else {
throw new ComponentConfigurationException("Could not resolve component registry from current application");
}
return new BindsMethodDependencyResolver(conditionMatcher, componentRegistry, registry);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.dockbox.hartshorn.inject.component.ComponentRegistry;
import org.dockbox.hartshorn.inject.processing.HierarchicalBinderProcessorRegistry;
import org.dockbox.hartshorn.inject.scope.Scope;

/**
* TODO: #1060 Add documentation
Expand All @@ -34,4 +35,5 @@ public interface ComponentProviderOrchestrator extends PostProcessingComponentPr

HierarchicalComponentProvider applicationProvider();

boolean containsScope(Scope scope);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;
import java.util.WeakHashMap;

import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.dockbox.hartshorn.inject.ContextKey;
import org.dockbox.hartshorn.inject.InjectionCapableApplication;
Expand Down Expand Up @@ -79,18 +80,6 @@ protected HierarchicalComponentProviderOrchestrator(InjectionCapableApplication
this.applicationScope = ScopeAdapter.of(this);
this.componentProcessorRegistry = new MultiMapComponentProcessorRegistry();
this.binderProcessorRegistry = new MultiMapHierarchicalBinderProcessorRegistry();

// Eagerly initialize the application provider
this.getOrCreateProvider(this.applicationScope);
}

private HierarchicalBinderAwareComponentProvider getOrCreateProvider(Scope scope) {
if (scope == null) {
scope = this.applicationScope;
}
synchronized (this.scopedProviders) {
return this.scopedProviders.computeIfAbsent(scope, this::createComponentProvider);
}
}

@NonNull
Expand All @@ -112,21 +101,31 @@ private HierarchicalBinderAwareComponentProvider createComponentProvider(Scope s
}
}

// Cache provider before processing, in case of recursive calls
this.scopedProviders.put(scope, provider);

HierarchicalBinderPostProcessor binderPostProcessor = new CompositeHierarchicalBinderPostProcessor(this.binderProcessorRegistry()::processors);
binderPostProcessor.process(this.application, provider.scope(), provider.binder());
return provider;
}

private HierarchicalComponentProvider getOrDefaultProvider(Scope scope) {
private HierarchicalBinderAwareComponentProvider getOrDefaultProvider(Scope scope) {
return this.tryGetProvider(scope, s -> this.getOrCreateProvider(this.applicationScope));
}

private HierarchicalBinderAwareComponentProvider getOrCreateProvider(Scope scope) {
return this.tryGetProvider(scope, this::createComponentProvider);
}

private HierarchicalBinderAwareComponentProvider tryGetProvider(Scope scope, Function<Scope, HierarchicalBinderAwareComponentProvider> fallbackValue) {
if (scope == null) {
scope = this.applicationScope;
}
synchronized (this.scopedProviders) {
HierarchicalComponentProvider provider = this.scopedProviders.get(scope);
if (provider == null) {
return this.scopedProviders.get(this.applicationScope);
if (this.scopedProviders.containsKey(scope)) {
return this.scopedProviders.get(scope);
}
return provider;
return fallbackValue.apply(scope);
}
}

Expand Down Expand Up @@ -194,6 +193,11 @@ public HierarchicalComponentProvider applicationProvider() {
return this.getOrCreateProvider(this.applicationScope);
}

@Override
public boolean containsScope(Scope scopeKey) {
return this.scopedProviders.containsKey(scopeKey);
}

public static ContextualInitializer<ComponentRegistry, ComponentProviderOrchestrator> create(Customizer<Configurer> customizer) {
return context -> {
InjectionCapableApplication application = context.firstContext(InjectionCapableApplication.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public Scope scope() {
}

@Override
public PostProcessingComponentProvider defaultProvider() {
public ComponentProviderOrchestrator defaultProvider() {
return this.componentProvider;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.dockbox.hartshorn.launchpad.activation;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -56,9 +57,15 @@ protected Set<Annotation> collectServiceActivatorsOnType(Class<?> type) {
}

public Set<ServiceActivator> collectDeclarationsOnActivator(Annotation annotation) {
return Stream.of(annotation.annotationType().getAnnotations())
Class<? extends Annotation> annotationType = annotation.annotationType();
Set<ServiceActivator> activators = new HashSet<>();
if (annotationType.isAnnotationPresent(ServiceActivator.class)) {
activators.add(annotationType.getAnnotation(ServiceActivator.class));
}
Arrays.stream(annotationType.getAnnotations())
.filter(ann -> ann.annotationType().isAnnotationPresent(ServiceActivator.class))
.map(ann -> ann.annotationType().getAnnotation(ServiceActivator.class))
.collect(Collectors.toSet());
.forEach(activators::add);
return activators;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import java.util.Properties;
import org.dockbox.hartshorn.inject.ExceptionHandler;
import org.dockbox.hartshorn.inject.ComponentKey;
import org.dockbox.hartshorn.inject.InjectorEnvironment;
import org.dockbox.hartshorn.inject.component.ComponentRegistry;
import org.dockbox.hartshorn.inject.ManagedComponentEnvironment;
import org.dockbox.hartshorn.launchpad.ApplicationContext;
import org.dockbox.hartshorn.launchpad.HartshornApplication;
import org.dockbox.hartshorn.launchpad.context.ApplicationContextCarrier;
Expand All @@ -33,7 +32,7 @@
*
* @author Guus Lieben
*/
public interface ApplicationEnvironment extends ApplicationContextCarrier, ExceptionHandler, InjectorEnvironment {
public interface ApplicationEnvironment extends ApplicationContextCarrier, ExceptionHandler, ManagedComponentEnvironment {

/**
* Gets the {@link FileSystemProvider file system provider} for the current environment. The provider is
Expand Down Expand Up @@ -61,11 +60,6 @@ public interface ApplicationEnvironment extends ApplicationContextCarrier, Excep
*/
EnvironmentTypeResolver typeResolver();

/**
* @return the {@link ComponentRegistry} that is used by this {@link ApplicationContext} to locate components
*/
ComponentRegistry componentRegistry();

/**
* Indicates whether the current environment exists within a Continuous Integration environment. If this returns
* {@code true} this indicates the application is not active in a production environment. For example, the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.dockbox.hartshorn.launchpad.launch;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
Expand Down Expand Up @@ -80,21 +79,16 @@ public void withAdditionalBinderProcessors(Collection<? extends HierarchicalBind


/**
* Registers component processors to the application context. This method will collect all activators from the given
* set of annotations, and then collect all processors from the activators. The processors will then be registered
* to the application context.
* Registers component processors to the application context. All processors declared on the given {@link ServiceActivator}s
* will be registered to the application context.
*
* @param registry the application context
* @param introspector the introspector to use for constructor lookup
* @param activators the set of annotations to collect activators from
* @param activators the set of {@link ServiceActivator}s
*/
public void registerComponentProcessors(ComponentProcessorRegistry registry, Introspector introspector, Set<Annotation> activators) {
Set<ServiceActivator> serviceActivatorAnnotations = activators.stream()
.flatMap(activator -> this.activatorCollector.collectDeclarationsOnActivator(activator).stream())
.collect(Collectors.toSet());

Set<Class<? extends ComponentPreProcessor>> preProcessorTypes = this.resolveComponentPreProcessorTypes(serviceActivatorAnnotations);
Set<Class<? extends ComponentPostProcessor>> postProcessorTypes = this.resolveComponentPostProcessorTypes(serviceActivatorAnnotations);
public void registerComponentProcessors(ComponentProcessorRegistry registry, Introspector introspector, Set<ServiceActivator> activators) {
Set<Class<? extends ComponentPreProcessor>> preProcessorTypes = this.resolveComponentPreProcessorTypes(activators);
Set<Class<? extends ComponentPostProcessor>> postProcessorTypes = this.resolveComponentPostProcessorTypes(activators);

if (this.buildContext.logger().isDebugEnabled()) {
int totalProcessors = preProcessorTypes.size() + postProcessorTypes.size();
Expand All @@ -106,20 +100,15 @@ public void registerComponentProcessors(ComponentProcessorRegistry registry, Int
}

/**
* Registers binder processors to the application context. This method will collect all activators from the given
* set of annotations, and then collect all processors from the activators. The processors will then be registered
* to the application context.
* Registers binder processors to the application context. All processors declared on the given {@link ServiceActivator}s
* will be registered to the application context.
*
* @param registry the registry to register the binder processors to
* @param introspector the introspector to use for constructor lookup
* @param activators the set of annotations to collect activators from
* @param activators the set of {@link ServiceActivator}s
*/
public void registerBinderProcessors(HierarchicalBinderProcessorRegistry registry, Introspector introspector, Set<Annotation> activators) {
Set<ServiceActivator> serviceActivatorAnnotations = activators.stream()
.flatMap(activator -> this.activatorCollector.collectDeclarationsOnActivator(activator).stream())
.collect(Collectors.toSet());

Set<Class<? extends HierarchicalBinderPostProcessor>> binderPostProcessorTypes = this.resolveBinderPostProcessorTypes(serviceActivatorAnnotations);
public void registerBinderProcessors(HierarchicalBinderProcessorRegistry registry, Introspector introspector, Set<ServiceActivator> activators) {
Set<Class<? extends HierarchicalBinderPostProcessor>> binderPostProcessorTypes = this.resolveBinderPostProcessorTypes(activators);

if (this.buildContext.logger().isDebugEnabled()) {
this.buildContext.logger().debug("Registering {} binder processors to application context", binderPostProcessorTypes.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.dockbox.hartshorn.inject.processing.ComponentProcessorRegistry;
import org.dockbox.hartshorn.inject.processing.CompositeHierarchicalBinderPostProcessor;
import org.dockbox.hartshorn.inject.processing.ContainerAwareComponentPopulatorPostProcessor;
import org.dockbox.hartshorn.inject.processing.HierarchicalBinderPostProcessor;
import org.dockbox.hartshorn.inject.processing.HierarchicalBinderProcessorRegistry;
import org.dockbox.hartshorn.inject.provider.ComponentProviderOrchestrator;
import org.dockbox.hartshorn.inject.provider.PostProcessingComponentProvider;
import org.dockbox.hartshorn.inject.scope.Scope;
import org.dockbox.hartshorn.launchpad.ApplicationContext;
import org.dockbox.hartshorn.launchpad.Hartshorn;
import org.dockbox.hartshorn.launchpad.ProcessableApplicationContext;
Expand Down Expand Up @@ -148,22 +147,24 @@ private void registerComponentProcessors(ApplicationContext applicationContext,
this.componentProcessorRegistrar.withAdditionalComponentProcessors(this.configurer.componentPostProcessors.initialize(context));
this.componentProcessorRegistrar.withAdditionalBinderProcessors(this.configurer.binderPostProcessors.initialize(context));

Set<ServiceActivator> serviceActivators = activators.stream()
.flatMap(activator -> this.activatorCollector.collectDeclarationsOnActivator(activator).stream())
.collect(Collectors.toSet());

if (applicationContext.defaultProvider() instanceof PostProcessingComponentProvider processingComponentProvider) {
ComponentProcessorRegistry registry = processingComponentProvider.processorRegistry();
this.componentProcessorRegistrar.registerComponentProcessors(registry, applicationContext.environment().introspector(), activators);
this.componentProcessorRegistrar.registerComponentProcessors(registry, applicationContext.environment().introspector(), serviceActivators);
}
else {
this.buildContext.logger().warn("Default component provider is not processable, component processors will not be registered");
}

if (applicationContext.defaultProvider() instanceof ComponentProviderOrchestrator orchestrator) {
if (orchestrator.containsScope(applicationContext.scope())) {
throw new IllegalStateException("Application context scope is already present in the component provider orchestrator, cannot process after release");
}
HierarchicalBinderProcessorRegistry registry = orchestrator.binderProcessorRegistry();
this.componentProcessorRegistrar.registerBinderProcessors(registry, applicationContext.environment().introspector(), activators);
// Global binder is already initialized (albeit unused until this point), so need to ensure that it is processed
// TODO #1113: Inspect if we can move this to the initialization of the global binder
Scope applicationScope = applicationContext.scope();
HierarchicalBinderPostProcessor processor = new CompositeHierarchicalBinderPostProcessor(registry::processors);
processor.process(applicationContext, applicationScope, applicationContext.defaultBinder());
this.componentProcessorRegistrar.registerBinderProcessors(registry, applicationContext.environment().introspector(), serviceActivators);
}
else {
this.buildContext.logger().warn("Default component provider is not orchestrating binders, binder processors will not be registered");
Expand Down

0 comments on commit bb19afd

Please sign in to comment.