/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.engine.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.search.SearchException;
import org.hibernate.search.cfg.spi.SearchConfiguration;
import org.hibernate.search.engine.ServiceManager;
import org.hibernate.search.spi.BuildContext;
import org.hibernate.search.spi.ServiceProvider;
import org.hibernate.search.util.impl.ClassLoaderHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* @author Emmanuel Bernard
* @author Sanne Grinovero
*/
public class StandardServiceManager implements ServiceManager {
private static final String SERVICES_FILE = "META-INF/services/" + ServiceProvider.class.getName();
private static final Log log = LoggerFactory.make();
//barrier protected by the Hibernate Search instantiation
private final HashSet<Class<?>> availableProviders = new HashSet<Class<?>>();
private final ConcurrentHashMap<Class<?>, ServiceProviderWrapper<?>> managedProviders = new ConcurrentHashMap<Class<?>, ServiceProviderWrapper<?>>();
private final Map<Class<? extends ServiceProvider<?>>, Object> providedProviders = new HashMap<Class<? extends ServiceProvider<?>>, Object>();
private final Properties properties;
public StandardServiceManager(SearchConfiguration cfg) {
this.properties = cfg.getProperties();
this.providedProviders.putAll( cfg.getProvidedServices() );
listAndInstantiateServiceProviders();
}
private void listAndInstantiateServiceProviders() {
//get list of services available
final Enumeration<URL> resources = ClassLoaderHelper.getResources( SERVICES_FILE, StandardServiceManager.class );
String name;
try {
while ( resources.hasMoreElements() ) {
URL url = resources.nextElement();
InputStream stream = url.openStream();
try {
BufferedReader reader = new BufferedReader( new InputStreamReader( stream ), 100 );
name = reader.readLine();
while ( name != null ) {
name = name.trim();
if ( !name.startsWith( "#" ) ) {
final Class<?> serviceProviderClass =
ClassLoaderHelper.classForName( name, StandardServiceManager.class.getClassLoader(), "service provider" );
availableProviders.add( serviceProviderClass );
}
name = reader.readLine();
}
}
finally {
stream.close();
}
}
}
catch ( IOException e ) {
throw new SearchException( "Unable to read " + SERVICES_FILE, e );
}
}
@SuppressWarnings("unchecked")
public <T> T requestService(Class<? extends ServiceProvider<T>> serviceProviderClass, BuildContext context) {
//provided services have priority over managed services
if ( providedProviders.containsKey( serviceProviderClass ) ) {
//we use containsKey as the service itself might be null
//TODO be safer and throw a cleaner exception
return (T) providedProviders.get( serviceProviderClass );
}
ServiceProviderWrapper<T> wrapper = (ServiceProviderWrapper<T>) managedProviders.get( serviceProviderClass );
if ( wrapper == null ) {
if ( availableProviders.contains( serviceProviderClass ) ) {
ServiceProvider<T> serviceProvider = ClassLoaderHelper.instanceFromClass(
ServiceProvider.class,
serviceProviderClass,
"service provider"
);
wrapper = new ServiceProviderWrapper<T>( serviceProvider, context, serviceProviderClass );
managedProviders.putIfAbsent( serviceProviderClass, wrapper );
}
else {
throw new SearchException( "Unable to find service related to " + serviceProviderClass );
}
}
wrapper = (ServiceProviderWrapper<T>) managedProviders.get( serviceProviderClass );
wrapper.startVirtual();
return wrapper.getService();
}
public void releaseService(Class<? extends ServiceProvider<?>> serviceProviderClass) {
//provided services have priority over managed services
if ( providedProviders.containsKey( serviceProviderClass ) ) {
return;
}
final ServiceProviderWrapper wrapper = managedProviders.get( serviceProviderClass );
if ( wrapper == null ) {
throw new AssertionFailure( "Unable to find service related to " + serviceProviderClass);
}
wrapper.stopVirtual();
}
public void stopServices() {
for ( ServiceProviderWrapper wrapper : managedProviders.values() ) {
wrapper.ensureStopped();
}
}
private class ServiceProviderWrapper<S> {
private final ServiceProvider<S> serviceProvider;
private final BuildContext context;
private final Class<? extends ServiceProvider<S>> serviceProviderClass;
private int userCounter = 0;
private ServiceStatus status = ServiceStatus.STOPPED;
ServiceProviderWrapper(ServiceProvider<S> serviceProvider, BuildContext context, Class<? extends ServiceProvider<S>> serviceProviderClass) {
this.serviceProvider = serviceProvider;
this.context = context;
this.serviceProviderClass = serviceProviderClass;
}
synchronized S getService() {
if ( status != ServiceStatus.RUNNING ) {
stateExpectedFailure();
}
return serviceProvider.getService();
}
synchronized void startVirtual() {
int previousValue = userCounter;
userCounter++;
if ( previousValue == 0 ) {
if ( status != ServiceStatus.STOPPED ) {
stateExpectedFailure();
}
status = ServiceStatus.STARTING;
serviceProvider.start( properties, context );
status = ServiceStatus.RUNNING;
}
if ( status != ServiceStatus.RUNNING ) {
//Could happen on circular dependencies
stateExpectedFailure();
}
}
synchronized void stopVirtual() {
userCounter--;
if ( userCounter == 0 ) {
if ( status != ServiceStatus.RUNNING ) {
stateExpectedFailure();
}
status = ServiceStatus.STOPPING;
forceStop();
status = ServiceStatus.STOPPED;
managedProviders.remove( serviceProviderClass );
}
else if ( status != ServiceStatus.RUNNING ) {
//Could happen on circular dependencies
stateExpectedFailure();
}
}
synchronized void ensureStopped() {
if ( status != ServiceStatus.STOPPED ) {
log.serviceProviderNotReleased( serviceProviderClass );
forceStop();
}
}
private void forceStop() {
try {
serviceProvider.stop();
}
catch ( Exception e ) {
log.stopServiceFailed( serviceProviderClass, e );
}
}
private void stateExpectedFailure() {
throw new AssertionFailure( "Unexpected status '" + status + "' for serviceProvider '" + serviceProvider + "'." +
" Check for circular dependencies or unreleased resources in your services." );
}
}
private enum ServiceStatus {
RUNNING, STOPPED, STARTING, STOPPING
}
}