From bb19afd88ea33a2e05af7973b8597fb1c23351b3 Mon Sep 17 00:00:00 2001 From: Guus Lieben Date: Wed, 16 Oct 2024 20:22:45 +0200 Subject: [PATCH] #1113 Initialize application provider late to allow integrated binder processing --- .../inject/ManagedComponentEnvironment.java | 27 +++++++++++++ .../BindsMethodDependencyResolver.java | 14 ++++++- .../ComponentProviderOrchestrator.java | 2 + ...archicalComponentProviderOrchestrator.java | 38 ++++++++++--------- .../DelegatingApplicationContext.java | 2 +- .../activation/ServiceActivatorCollector.java | 11 +++++- .../environment/ApplicationEnvironment.java | 10 +---- .../launch/ComponentProcessorRegistrar.java | 33 ++++++---------- .../StandardApplicationContextFactory.java | 19 +++++----- 9 files changed, 96 insertions(+), 60 deletions(-) create mode 100644 hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/ManagedComponentEnvironment.java diff --git a/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/ManagedComponentEnvironment.java b/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/ManagedComponentEnvironment.java new file mode 100644 index 000000000..9f22fd0f8 --- /dev/null +++ b/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/ManagedComponentEnvironment.java @@ -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(); +} diff --git a/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/graph/resolve/BindsMethodDependencyResolver.java b/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/graph/resolve/BindsMethodDependencyResolver.java index 0ef0ed2a1..db2fb5bce 100644 --- a/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/graph/resolve/BindsMethodDependencyResolver.java +++ b/hartshorn-inject-configurations/src/main/java/org/dockbox/hartshorn/inject/graph/resolve/BindsMethodDependencyResolver.java @@ -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; @@ -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; @@ -125,7 +128,16 @@ public static ContextualInitializer this.getOrCreateProvider(this.applicationScope)); + } + + private HierarchicalBinderAwareComponentProvider getOrCreateProvider(Scope scope) { + return this.tryGetProvider(scope, this::createComponentProvider); + } + + private HierarchicalBinderAwareComponentProvider tryGetProvider(Scope scope, Function 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); } } @@ -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 create(Customizer customizer) { return context -> { InjectionCapableApplication application = context.firstContext(InjectionCapableApplication.class) diff --git a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/DelegatingApplicationContext.java b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/DelegatingApplicationContext.java index 3e0a5d8b9..1c6f161d8 100644 --- a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/DelegatingApplicationContext.java +++ b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/DelegatingApplicationContext.java @@ -159,7 +159,7 @@ public Scope scope() { } @Override - public PostProcessingComponentProvider defaultProvider() { + public ComponentProviderOrchestrator defaultProvider() { return this.componentProvider; } diff --git a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/activation/ServiceActivatorCollector.java b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/activation/ServiceActivatorCollector.java index f32680a07..70f3b33a5 100644 --- a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/activation/ServiceActivatorCollector.java +++ b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/activation/ServiceActivatorCollector.java @@ -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; @@ -56,9 +57,15 @@ protected Set collectServiceActivatorsOnType(Class type) { } public Set collectDeclarationsOnActivator(Annotation annotation) { - return Stream.of(annotation.annotationType().getAnnotations()) + Class annotationType = annotation.annotationType(); + Set 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; } } diff --git a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/environment/ApplicationEnvironment.java b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/environment/ApplicationEnvironment.java index c23942193..854285bb1 100644 --- a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/environment/ApplicationEnvironment.java +++ b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/environment/ApplicationEnvironment.java @@ -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; @@ -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 @@ -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 diff --git a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/ComponentProcessorRegistrar.java b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/ComponentProcessorRegistrar.java index 4e7d037e7..881aaaa36 100644 --- a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/ComponentProcessorRegistrar.java +++ b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/ComponentProcessorRegistrar.java @@ -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; @@ -80,21 +79,16 @@ public void withAdditionalBinderProcessors(Collection activators) { - Set serviceActivatorAnnotations = activators.stream() - .flatMap(activator -> this.activatorCollector.collectDeclarationsOnActivator(activator).stream()) - .collect(Collectors.toSet()); - - Set> preProcessorTypes = this.resolveComponentPreProcessorTypes(serviceActivatorAnnotations); - Set> postProcessorTypes = this.resolveComponentPostProcessorTypes(serviceActivatorAnnotations); + public void registerComponentProcessors(ComponentProcessorRegistry registry, Introspector introspector, Set activators) { + Set> preProcessorTypes = this.resolveComponentPreProcessorTypes(activators); + Set> postProcessorTypes = this.resolveComponentPostProcessorTypes(activators); if (this.buildContext.logger().isDebugEnabled()) { int totalProcessors = preProcessorTypes.size() + postProcessorTypes.size(); @@ -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 activators) { - Set serviceActivatorAnnotations = activators.stream() - .flatMap(activator -> this.activatorCollector.collectDeclarationsOnActivator(activator).stream()) - .collect(Collectors.toSet()); - - Set> binderPostProcessorTypes = this.resolveBinderPostProcessorTypes(serviceActivatorAnnotations); + public void registerBinderProcessors(HierarchicalBinderProcessorRegistry registry, Introspector introspector, Set activators) { + Set> binderPostProcessorTypes = this.resolveBinderPostProcessorTypes(activators); if (this.buildContext.logger().isDebugEnabled()) { this.buildContext.logger().debug("Registering {} binder processors to application context", binderPostProcessorTypes.size()); diff --git a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/StandardApplicationContextFactory.java b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/StandardApplicationContextFactory.java index 717e72479..8739afc1d 100644 --- a/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/StandardApplicationContextFactory.java +++ b/hartshorn-launchpad/src/main/java/org/dockbox/hartshorn/launchpad/launch/StandardApplicationContextFactory.java @@ -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; @@ -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 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");