org.hibernate.search.engine.service.impl.StandardServiceManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-search-engine Show documentation
Show all versions of hibernate-search-engine Show documentation
Core of the Object/Lucene mapper, query engine and index management
/*
* 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.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.search.cfg.spi.SearchConfiguration;
import org.hibernate.search.engine.service.beanresolver.spi.ReflectionBeanResolver;
import org.hibernate.search.engine.service.beanresolver.impl.ReflectionFallbackBeanResolver;
import org.hibernate.search.engine.service.beanresolver.spi.BeanResolver;
import org.hibernate.search.engine.service.classloading.spi.ClassLoaderService;
import org.hibernate.search.engine.service.spi.Service;
import org.hibernate.search.engine.service.spi.ServiceManager;
import org.hibernate.search.engine.service.spi.ServiceReference;
import org.hibernate.search.engine.service.spi.Startable;
import org.hibernate.search.engine.service.spi.Stoppable;
import org.hibernate.search.exception.AssertionFailure;
import org.hibernate.search.spi.BuildContext;
import org.hibernate.search.util.StringHelper;
import org.hibernate.search.util.impl.ClassLoaderHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import java.lang.invoke.MethodHandles;
/**
* Default implementation of the {@code ServiceManager} interface.
*
* @author Emmanuel Bernard
* @author Sanne Grinovero
* @author Hardy Ferentschik
*/
public class StandardServiceManager implements ServiceManager {
private static final Log log = LoggerFactory.make( MethodHandles.lookup() );
private final Properties properties;
private final BuildContext buildContext;
private final ConcurrentHashMap, ServiceWrapper>> cachedServices = new ConcurrentHashMap, ServiceWrapper>>();
private final Map, Object> providedServices;
private final Map, String> defaultServices;
private final ClassLoaderService classloaderService;
private final BeanResolver beanResolver;
private final boolean failOnUnreleasedService;
private volatile boolean allServicesReleased = false;
public StandardServiceManager(SearchConfiguration searchConfiguration, BuildContext buildContext) {
this( searchConfiguration, buildContext, Collections., String>emptyMap() );
}
public StandardServiceManager(SearchConfiguration searchConfiguration,
BuildContext buildContext,
Map, String> defaultServices) {
this.buildContext = buildContext;
this.properties = searchConfiguration.getProperties();
this.defaultServices = defaultServices;
this.classloaderService = searchConfiguration.getClassLoaderService();
BeanResolver configuredBeanResolver = searchConfiguration.getBeanResolver();
ReflectionBeanResolver reflectionBeanResolver = new ReflectionBeanResolver();
if ( configuredBeanResolver != null ) {
this.beanResolver = new ReflectionFallbackBeanResolver( configuredBeanResolver, reflectionBeanResolver );
}
else {
this.beanResolver = reflectionBeanResolver;
}
this.providedServices = createProvidedServices( searchConfiguration ); // Requires beanResolver and classloaderService to be set
this.failOnUnreleasedService = Boolean.getBoolean( "org.hibernate.search.fail_on_unreleased_service" );
}
@Override
@SuppressWarnings("unchecked")
public S requestService(Class serviceRole) {
if ( serviceRole == null ) {
throw new IllegalArgumentException( "'null' is not a valid service role" );
}
if ( allServicesReleased ) {
throw log.serviceRequestedAfterReleasedAllWasCalled();
}
// provided services have priority over managed services
final Object providedService = providedServices.get( serviceRole );
if ( providedService != null ) {
return (S) providedService;
}
ServiceWrapper wrapper = (ServiceWrapper) cachedServices.get( serviceRole );
if ( wrapper == null ) {
wrapper = createAndCacheWrapper( serviceRole );
}
wrapper.startVirtual();
return wrapper.getService();
}
@Override
public ServiceReference requestReference(Class serviceRole) {
return new ServiceReference<>( this, serviceRole );
}
@Override
public void releaseService(Class serviceRole) {
if ( serviceRole == null ) {
throw new IllegalArgumentException( "'null' is not a valid service role" );
}
if ( providedServices.containsKey( serviceRole ) ) {
return;
}
ServiceWrapper wrapper = cachedServices.get( serviceRole );
if ( wrapper != null ) {
wrapper.stopVirtual();
}
}
@Override
public synchronized void releaseAllServices() {
for ( ServiceWrapper> wrapper : cachedServices.values() ) {
/*
* Perform an additional stopVirtual, to remove the extra usage token granted at first initialization,
* which keeps the service to be really stopped when it's released by the service client, yet we're not shutting down
* the Search engine yet.
*/
wrapper.stopVirtual();
}
/*
* If everything went well, the previous pass should have brought every
* user count to 0 for every service, so every service should have been stopped.
* This should also have chained-released services used by services, which
* ultimately should have stopped everything.
*/
/*
* Second pass to check for still-running services and forcefully stop them.
*/
List unreleasedServicesToReport = failOnUnreleasedService ? new ArrayList() : null;
for ( ServiceWrapper> wrapper : cachedServices.values() ) {
synchronized ( wrapper ) {
if ( wrapper.status != ServiceStatus.STOPPED ) {
log.serviceProviderNotReleased( wrapper.serviceClass );
wrapper.stopReal();
if ( unreleasedServicesToReport != null ) {
unreleasedServicesToReport.add( wrapper.serviceClass.getName() );
}
}
}
}
cachedServices.clear();
allServicesReleased = true;
beanResolver.stop();
if ( failOnUnreleasedService && !unreleasedServicesToReport.isEmpty() ) {
throw new AssertionFailure( "The following services have been used but not released: "
+ unreleasedServicesToReport );
}
}
private Map, Object> createProvidedServices(SearchConfiguration searchConfiguration) {
Map, Object> tmpServices = new HashMap, Object>(
searchConfiguration.getProvidedServices()
);
if ( tmpServices.containsKey( ClassLoaderService.class ) ) {
throw log.classLoaderServiceContainedInProvidedServicesException();
}
else {
tmpServices.put( ClassLoaderService.class, this.classloaderService );
}
if ( tmpServices.containsKey( BeanResolver.class ) ) {
throw log.beanResolverContainedInProvidedServicesException();
}
else {
tmpServices.put( BeanResolver.class, this.beanResolver );
}
return Collections.unmodifiableMap( tmpServices );
}
/**
* The 'synchronized' is necessary to avoid loading the same service in parallel: enumerating service
* implementations is not threadsafe when delegating to Hibernate ORM's org.hibernate.boot.registry.classloading.spi.ClassLoaderService
*/
private synchronized ServiceWrapper createAndCacheWrapper(Class serviceRole) {
//Check again, for concurrent usage:
ServiceWrapper existingWrapper = (ServiceWrapper) cachedServices.get( serviceRole );
if ( existingWrapper != null ) {
return existingWrapper;
}
Set services = new HashSet<>();
for ( S service : requestService( ClassLoaderService.class ).loadJavaServices( serviceRole ) ) {
services.add( service );
}
if ( services.size() == 0 ) {
tryLoadingDefaultService( serviceRole, services );
}
else if ( services.size() > 1 ) {
throw log.getMultipleServiceImplementationsException(
serviceRole.toString(),
StringHelper.join( services, "," )
);
}
S service = services.iterator().next();
ServiceWrapper wrapper = new ServiceWrapper( service, serviceRole, buildContext );
@SuppressWarnings("unchecked")
ServiceWrapper previousWrapper = (ServiceWrapper) cachedServices.putIfAbsent( serviceRole, wrapper );
if ( previousWrapper != null ) {
wrapper = previousWrapper;
}
else {
//Initialize the service usage counter with an additional usage token, on top of the one granted by the service request:
wrapper.startVirtual();
}
return wrapper;
}
private void tryLoadingDefaultService(Class serviceRole, Set services) {
// there is no loadable service. Check whether we have a default one we can instantiate
if ( defaultServices.containsKey( serviceRole ) ) {
S service = ClassLoaderHelper.instanceFromName(
serviceRole,
defaultServices.get( serviceRole ),
"default service",
this
);
services.add( service );
}
else {
throw log.getNoServiceImplementationFoundException( serviceRole.toString() );
}
}
private class ServiceWrapper {
private final S service;
private final BuildContext context;
private final Class serviceClass;
private int userCounter = 0;
private ServiceStatus status = ServiceStatus.STOPPED;
ServiceWrapper(S service, Class serviceClass, BuildContext context) {
this.service = service;
this.context = context;
this.serviceClass = serviceClass;
}
synchronized S getService() {
if ( status != ServiceStatus.RUNNING ) {
stateExpectedFailure();
}
return service;
}
/**
* Virtual call to the start method: only actually starts the
* service when bumping up the counter of start requests from
* zero. Subsequent start requests will simply increment the
* counter, so that we can eventually tear down the services
* in reverse order to make dependency graphs happy.
*
* Make sure to invoke startVirtual() both on service request,
* and on creation of the wrapper so that the first request
* accounts for two usage tokens rather than one.
* The shutdown process will similarly invoke stopVirtual() an
* additional time; this is to prevent services from starting
* and stopping frequently at runtime.
*/
synchronized void startVirtual() {
int previousValue = userCounter;
userCounter++;
if ( previousValue == 0 ) {
if ( status != ServiceStatus.STOPPED ) {
stateExpectedFailure();
}
startService( service );
}
if ( status != ServiceStatus.RUNNING ) {
//Could happen on circular dependencies
stateExpectedFailure();
}
}
synchronized void stopVirtual() {
userCounter--;
if ( userCounter == 0 ) {
//Do not check for the expected status in this case: we don't want a previous service start failure to
//prevent us to further attempt to stop services.
stopReal();
}
}
synchronized void stopReal() {
status = ServiceStatus.STOPPING;
try {
if ( service instanceof Stoppable ) {
( (Stoppable) service ).stop();
}
}
catch (Exception e) {
log.stopServiceFailed( serviceClass, e );
}
finally {
status = ServiceStatus.STOPPED;
}
}
private void startService(final S service) {
status = ServiceStatus.STARTING;
if ( service instanceof Startable ) {
( (Startable) service ).start( properties, context );
}
status = ServiceStatus.RUNNING;
}
private void stateExpectedFailure() {
throw log.getUnexpectedServiceStatusException( status.name(), service.toString() );
}
}
private enum ServiceStatus {
RUNNING, STOPPED, STARTING, STOPPING
}
@Override
public ClassLoaderService getClassLoaderService() {
return classloaderService;
}
@Override
public BeanResolver getBeanResolver() {
return beanResolver;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy