From f001ea4649081799adf7d188b45f95a031dbccdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 29 Nov 2018 10:49:58 +0100 Subject: [PATCH] HSEARCH-3101 Add a way to define named beans from integrations (backends, ...) --- .../impl/SearchIntegrationBuilderImpl.java | 2 +- .../impl/BeanConfigurationContextImpl.java | 33 ++++++++++ .../bean/impl/BeanCreationContextImpl.java | 23 +++++++ .../bean/impl/BeanProviderImpl.java | 64 +++++++++++++++++-- .../bean/impl/ConfiguredBeanKey.java | 34 ++++++++++ .../bean/spi/BeanConfigurationContext.java | 13 ++++ .../environment/bean/spi/BeanConfigurer.java | 17 +++++ .../bean/spi/BeanCreationContext.java | 15 +++++ .../environment/bean/spi/BeanFactory.java | 13 ++++ 9 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanConfigurationContextImpl.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanCreationContextImpl.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/ConfiguredBeanKey.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurationContext.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurer.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanCreationContext.java create mode 100644 engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanFactory.java diff --git a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilderImpl.java b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilderImpl.java index 8f02cc2d8b5..30bf720271f 100644 --- a/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilderImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/common/impl/SearchIntegrationBuilderImpl.java @@ -156,7 +156,7 @@ public SearchIntegration build() { beanResolver = new ReflectionBeanResolver( classResolver ); } - BeanProvider beanProvider = new BeanProviderImpl( beanResolver ); + BeanProvider beanProvider = new BeanProviderImpl( classResolver, beanResolver ); ServiceManager serviceManager = new ServiceManagerImpl( classResolver, resourceResolver, beanProvider ); RootBuildContext rootBuildContext = new RootBuildContext( serviceManager, failureCollector ); diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanConfigurationContextImpl.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanConfigurationContextImpl.java new file mode 100644 index 00000000000..9829b73d348 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanConfigurationContextImpl.java @@ -0,0 +1,33 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.search.engine.environment.bean.spi.BeanConfigurationContext; +import org.hibernate.search.engine.environment.bean.spi.BeanFactory; +import org.hibernate.search.util.impl.common.Contracts; + +final class BeanConfigurationContextImpl implements BeanConfigurationContext { + + private Map, BeanFactory> explicitlyConfiguredBeans = new HashMap<>(); + + @Override + public void define(Class exposedType, String name, BeanFactory factory) { + Contracts.assertNotNull( exposedType, "exposedType" ); + Contracts.assertNotNull( name, "name" ); + Contracts.assertNotNull( factory, "factory" ); + explicitlyConfiguredBeans.put( new ConfiguredBeanKey<>( exposedType, name ), factory ); + } + + Map, BeanFactory> getConfiguredBeans() { + return Collections.unmodifiableMap( explicitlyConfiguredBeans ); + } + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanCreationContextImpl.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanCreationContextImpl.java new file mode 100644 index 00000000000..39af3f84cd8 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanCreationContextImpl.java @@ -0,0 +1,23 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.impl; + +import org.hibernate.search.engine.environment.bean.BeanProvider; +import org.hibernate.search.engine.environment.bean.spi.BeanCreationContext; + +final class BeanCreationContextImpl implements BeanCreationContext { + private final BeanProvider beanProvider; + + BeanCreationContextImpl(BeanProvider beanProvider) { + this.beanProvider = beanProvider; + } + + @Override + public BeanProvider getBeanProvider() { + return beanProvider; + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanProviderImpl.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanProviderImpl.java index b9b02bb410c..02a5430ef4c 100644 --- a/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanProviderImpl.java +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/BeanProviderImpl.java @@ -7,22 +7,40 @@ package org.hibernate.search.engine.environment.bean.impl; import java.lang.invoke.MethodHandles; +import java.util.Map; import org.hibernate.search.engine.environment.bean.BeanProvider; import org.hibernate.search.engine.environment.bean.BeanReference; +import org.hibernate.search.engine.environment.bean.spi.BeanConfigurer; +import org.hibernate.search.engine.environment.bean.spi.BeanCreationContext; +import org.hibernate.search.engine.environment.bean.spi.BeanFactory; import org.hibernate.search.engine.environment.bean.spi.BeanResolver; +import org.hibernate.search.engine.environment.classpath.spi.ClassResolver; import org.hibernate.search.engine.logging.impl.Log; +import org.hibernate.search.util.SearchException; import org.hibernate.search.util.impl.common.LoggerFactory; import org.hibernate.search.util.impl.common.StringHelper; -public class BeanProviderImpl implements BeanProvider { +public final class BeanProviderImpl implements BeanProvider { private static final Log log = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final BeanResolver beanResolver; + private final Map, BeanFactory> explicitlyConfiguredBeans; - public BeanProviderImpl(BeanResolver beanResolver) { + private final BeanCreationContext beanCreationContext; + + public BeanProviderImpl(ClassResolver classResolver, BeanResolver beanResolver) { this.beanResolver = beanResolver; + + // TODO maybe also add a way to pass configurer through a configuration property? + BeanConfigurationContextImpl configurationContext = new BeanConfigurationContextImpl(); + for ( BeanConfigurer beanConfigurer : classResolver.loadJavaServices( BeanConfigurer.class ) ) { + beanConfigurer.configure( configurationContext ); + } + this.explicitlyConfiguredBeans = configurationContext.getConfiguredBeans(); + + this.beanCreationContext = new BeanCreationContextImpl( this ); } @Override @@ -38,7 +56,7 @@ public T getBean(Class expectedClass, String nameReference) { if ( StringHelper.isEmpty( nameReference ) ) { throw log.emptyBeanReferenceNameNullOrEmpty(); } - return beanResolver.resolve( expectedClass, nameReference ); + return getBeanFromBeanResolverOrConfiguredBeans( expectedClass, nameReference ); } @Override @@ -56,7 +74,8 @@ public T getBean(Class expectedClass, BeanReference reference) { return beanResolver.resolve( expectedClass, nameReference, typeReference ); } else if ( nameProvided ) { - return beanResolver.resolve( expectedClass, nameReference ); + // This is the only situation where querying configured beans make sense + return getBeanFromBeanResolverOrConfiguredBeans( expectedClass, nameReference ); } else if ( typeProvided ) { return beanResolver.resolve( expectedClass, typeReference ); @@ -65,4 +84,41 @@ else if ( typeProvided ) { throw log.emptyBeanReferenceNoNameNoType(); } } + + private T getBeanFromBeanResolverOrConfiguredBeans(Class expectedClass, String nameReference) { + try { + return beanResolver.resolve( expectedClass, nameReference ); + } + catch (SearchException e) { + /* + * Fall back to an explicitly configured bean. + * It's important to do this *after* trying the bean resolver, + * so that adding explicitly configured beans in a new version of Hibernate Search + * doesn't break existing user's configuration. + */ + try { + T explicitlyConfiguredBean = getExplicitlyConfiguredBean( expectedClass, nameReference ); + if ( explicitlyConfiguredBean != null ) { + return explicitlyConfiguredBean; + } + } + catch (RuntimeException e2) { + e.addSuppressed( e2 ); + } + throw e; + } + } + + private T getExplicitlyConfiguredBean(Class exposedType, String name) { + ConfiguredBeanKey key = new ConfiguredBeanKey<>( exposedType, name ); + @SuppressWarnings("unchecked") // We know the factory has the correct type, see BeanConfigurationContextImpl + BeanFactory factory = (BeanFactory) explicitlyConfiguredBeans.get( key ); + if ( factory == null ) { + return null; + } + else { + return factory.create( beanCreationContext ); + } + } + } diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/ConfiguredBeanKey.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/ConfiguredBeanKey.java new file mode 100644 index 00000000000..871b5aabed3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/impl/ConfiguredBeanKey.java @@ -0,0 +1,34 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.impl; + +import java.util.Objects; + +final class ConfiguredBeanKey { + private final Class exposedType; + private final String name; + + ConfiguredBeanKey(Class exposedType, String name) { + this.exposedType = exposedType; + this.name = name; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null || getClass() != obj.getClass() ) { + return false; + } + ConfiguredBeanKey other = (ConfiguredBeanKey) obj; + return Objects.equals( exposedType, other.exposedType ) + && Objects.equals( name, other.name ); + } + + @Override + public int hashCode() { + return Objects.hash( exposedType, name ); + } +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurationContext.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurationContext.java new file mode 100644 index 00000000000..f1b8fc41a5a --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurationContext.java @@ -0,0 +1,13 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.spi; + +public interface BeanConfigurationContext { + + void define(Class exposedType, String name, BeanFactory factory); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurer.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurer.java new file mode 100644 index 00000000000..d44c0d556c0 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanConfigurer.java @@ -0,0 +1,17 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.spi; + +public interface BeanConfigurer { + + /** + * Configure beans as necessary using the given {@code context}. + * @param context A context exposing methods to configure beans. + */ + void configure(BeanConfigurationContext context); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanCreationContext.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanCreationContext.java new file mode 100644 index 00000000000..bcf5026577b --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanCreationContext.java @@ -0,0 +1,15 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.spi; + +import org.hibernate.search.engine.environment.bean.BeanProvider; + +public interface BeanCreationContext { + + BeanProvider getBeanProvider(); + +} diff --git a/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanFactory.java b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanFactory.java new file mode 100644 index 00000000000..0221a3492b9 --- /dev/null +++ b/engine/src/main/java/org/hibernate/search/engine/environment/bean/spi/BeanFactory.java @@ -0,0 +1,13 @@ +/* + * Hibernate Search, full-text search for your domain model + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.search.engine.environment.bean.spi; + +public interface BeanFactory { + + T create(BeanCreationContext context); + +}