/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.engine.jdbc.connections.internal; import java.beans.BeanInfo; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.sql.Connection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.hibernate.HibernateException; import org.hibernate.MultiTenancyStrategy; import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.beans.BeanInfoHelper; import org.hibernate.service.spi.ServiceRegistryImplementor; /** * Instantiates and configures an appropriate {@link ConnectionProvider}. * * @author Gavin King * @author Steve Ebersole * @author Brett Meyer */ public class ConnectionProviderInitiator implements StandardServiceInitiator<ConnectionProvider> { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( ConnectionProviderInitiator.class ); /** * Singleton access */ public static final ConnectionProviderInitiator INSTANCE = new ConnectionProviderInitiator(); /** * The strategy for c3p0 connection pooling */ public static final String C3P0_STRATEGY = "c3p0"; /** * The strategy for proxool connection pooling */ public static final String PROXOOL_STRATEGY = "proxool"; /** * The strategy for proxool connection pooling */ public static final String HIKARI_STRATEGY = "hikari"; /** * No idea. Is this even still used? */ public static final String INJECTION_DATA = "hibernate.connection_provider.injection_data"; // mapping from legacy connection provider name to actual // connection provider that will be used private static final Map<String,String> LEGACY_CONNECTION_PROVIDER_MAPPING; static { LEGACY_CONNECTION_PROVIDER_MAPPING = new HashMap<String,String>( 5 ); LEGACY_CONNECTION_PROVIDER_MAPPING.put( "org.hibernate.connection.DatasourceConnectionProvider", DatasourceConnectionProviderImpl.class.getName() ); LEGACY_CONNECTION_PROVIDER_MAPPING.put( "org.hibernate.connection.DriverManagerConnectionProvider", DriverManagerConnectionProviderImpl.class.getName() ); LEGACY_CONNECTION_PROVIDER_MAPPING.put( "org.hibernate.connection.UserSuppliedConnectionProvider", UserSuppliedConnectionProviderImpl.class.getName() ); } @Override public Class<ConnectionProvider> getServiceInitiated() { return ConnectionProvider.class; } @Override public ConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { final MultiTenancyStrategy strategy = MultiTenancyStrategy.determineMultiTenancyStrategy( configurationValues ); if ( strategy == MultiTenancyStrategy.DATABASE || strategy == MultiTenancyStrategy.SCHEMA ) { // nothing to do, but given the separate hierarchies have to handle this here. return null; } final StrategySelector strategySelector = registry.getService( StrategySelector.class ); final Object explicitSetting = configurationValues.get( AvailableSettings.CONNECTION_PROVIDER ); if ( explicitSetting != null ) { // if we are explicitly supplied a ConnectionProvider to use (in some form) -> use it.. if ( explicitSetting instanceof ConnectionProvider ) { return (ConnectionProvider) explicitSetting; } else if ( explicitSetting instanceof Class ) { final Class providerClass = (Class) explicitSetting; LOG.instantiatingExplicitConnectionProvider( providerClass.getName() ); return instantiateExplicitConnectionProvider( providerClass ); } else { String providerName = explicitSetting.toString(); if ( LEGACY_CONNECTION_PROVIDER_MAPPING.containsKey( providerName ) ) { final String actualProviderName = LEGACY_CONNECTION_PROVIDER_MAPPING.get( providerName ); DeprecationLogger.DEPRECATION_LOGGER.connectionProviderClassDeprecated( providerName, actualProviderName ); providerName = actualProviderName; } LOG.instantiatingExplicitConnectionProvider( providerName ); final Class providerClass = strategySelector.selectStrategyImplementor( ConnectionProvider.class, providerName ); try { return instantiateExplicitConnectionProvider( providerClass ); } catch (Exception e) { throw new HibernateException( "Could not instantiate connection provider [" + providerName + "]", e ); } } } if ( configurationValues.get( AvailableSettings.DATASOURCE ) != null ) { return new DatasourceConnectionProviderImpl(); } ConnectionProvider connectionProvider = null; if ( connectionProvider == null ) { if ( c3p0ConfigDefined( configurationValues ) ) { connectionProvider = instantiateC3p0Provider( strategySelector ); } } if ( connectionProvider == null ) { if ( proxoolConfigDefined( configurationValues ) ) { connectionProvider = instantiateProxoolProvider( strategySelector ); } } if ( connectionProvider == null ) { if ( hikariConfigDefined( configurationValues ) ) { connectionProvider = instantiateHikariProvider( strategySelector ); } } if ( connectionProvider == null ) { if ( configurationValues.get( AvailableSettings.URL ) != null ) { connectionProvider = new DriverManagerConnectionProviderImpl(); } } if ( connectionProvider == null ) { LOG.noAppropriateConnectionProvider(); connectionProvider = new UserSuppliedConnectionProviderImpl(); } final Map injectionData = (Map) configurationValues.get( INJECTION_DATA ); if ( injectionData != null && injectionData.size() > 0 ) { final ConnectionProvider theConnectionProvider = connectionProvider; new BeanInfoHelper( connectionProvider.getClass() ).applyToBeanInfo( connectionProvider, new BeanInfoHelper.BeanInfoDelegate() { public void processBeanInfo(BeanInfo beanInfo) throws Exception { final PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); for ( PropertyDescriptor descriptor : descriptors ) { final String propertyName = descriptor.getName(); if ( injectionData.containsKey( propertyName ) ) { final Method method = descriptor.getWriteMethod(); method.invoke( theConnectionProvider, injectionData.get( propertyName ) ); } } } } ); } return connectionProvider; } private ConnectionProvider instantiateExplicitConnectionProvider(Class providerClass) { try { return (ConnectionProvider) providerClass.newInstance(); } catch (Exception e) { throw new HibernateException( "Could not instantiate connection provider [" + providerClass.getName() + "]", e ); } } private static boolean c3p0ConfigDefined(Map configValues) { for ( Object key : configValues.keySet() ) { if ( String.class.isInstance( key ) && ( (String) key ).startsWith( AvailableSettings.C3P0_CONFIG_PREFIX ) ) { return true; } } return false; } private ConnectionProvider instantiateC3p0Provider(StrategySelector strategySelector) { try { return strategySelector.selectStrategyImplementor( ConnectionProvider.class, C3P0_STRATEGY ).newInstance(); } catch ( Exception e ) { LOG.c3p0ProviderClassNotFound( C3P0_STRATEGY ); return null; } } private static boolean proxoolConfigDefined(Map configValues) { for ( Object key : configValues.keySet() ) { if ( String.class.isInstance( key ) && ( (String) key ).startsWith( AvailableSettings.PROXOOL_CONFIG_PREFIX ) ) { return true; } } return false; } private ConnectionProvider instantiateProxoolProvider(StrategySelector strategySelector) { try { return strategySelector.selectStrategyImplementor( ConnectionProvider.class, PROXOOL_STRATEGY ).newInstance(); } catch ( Exception e ) { LOG.proxoolProviderClassNotFound( PROXOOL_STRATEGY ); return null; } } private boolean hikariConfigDefined(Map configValues) { for ( Object key : configValues.keySet() ) { if ( !String.class.isInstance( key ) ) { continue; } if ( ( (String) key ).startsWith( "hibernate.hikari." ) ) { return true; } } return false; } private ConnectionProvider instantiateHikariProvider(StrategySelector strategySelector) { try { return strategySelector.selectStrategyImplementor( ConnectionProvider.class, HIKARI_STRATEGY ).newInstance(); } catch ( Exception e ) { LOG.hikariProviderClassNotFound(); return null; } } /** * Build the connection properties capable of being passed to the {@link java.sql.DriverManager#getConnection} * forms taking {@link Properties} argument. We seek out all keys in the passed map which start with * {@code hibernate.connection.}, using them to create a new {@link Properties} instance. The keys in this * new {@link Properties} have the {@code hibernate.connection.} prefix trimmed. * * @param properties The map from which to build the connection specific properties. * * @return The connection properties. */ public static Properties getConnectionProperties(Map<?,?> properties) { final Properties result = new Properties(); for ( Map.Entry entry : properties.entrySet() ) { if ( ! ( String.class.isInstance( entry.getKey() ) ) || ! String.class.isInstance( entry.getValue() ) ) { continue; } final String key = (String) entry.getKey(); final String value = (String) entry.getValue(); if ( key.startsWith( AvailableSettings.CONNECTION_PREFIX ) ) { if ( SPECIAL_PROPERTIES.contains( key ) ) { if ( AvailableSettings.USER.equals( key ) ) { result.setProperty( "user", value ); } } else { result.setProperty( key.substring( AvailableSettings.CONNECTION_PREFIX.length() + 1 ), value ); } } else if ( CONDITIONAL_PROPERTIES.containsKey( key ) ) { result.setProperty( CONDITIONAL_PROPERTIES.get( key ), value ); } } return result; } private static final Set<String> SPECIAL_PROPERTIES; private static final Map<String,Integer> ISOLATION_VALUE_MAP; private static final Map<Integer, String> ISOLATION_VALUE_CONSTANT_NAME_MAP; private static final Map<Integer, String> ISOLATION_VALUE_NICE_NAME_MAP; static { SPECIAL_PROPERTIES = new HashSet<String>(); SPECIAL_PROPERTIES.add( AvailableSettings.DATASOURCE ); SPECIAL_PROPERTIES.add( AvailableSettings.URL ); SPECIAL_PROPERTIES.add( AvailableSettings.CONNECTION_PROVIDER ); SPECIAL_PROPERTIES.add( AvailableSettings.POOL_SIZE ); SPECIAL_PROPERTIES.add( AvailableSettings.ISOLATION ); SPECIAL_PROPERTIES.add( AvailableSettings.DRIVER ); SPECIAL_PROPERTIES.add( AvailableSettings.USER ); ISOLATION_VALUE_MAP = new ConcurrentHashMap<String, Integer>(); ISOLATION_VALUE_MAP.put( "TRANSACTION_NONE", Connection.TRANSACTION_NONE ); ISOLATION_VALUE_MAP.put( "NONE", Connection.TRANSACTION_NONE ); ISOLATION_VALUE_MAP.put( "TRANSACTION_READ_UNCOMMITTED", Connection.TRANSACTION_READ_UNCOMMITTED ); ISOLATION_VALUE_MAP.put( "READ_UNCOMMITTED", Connection.TRANSACTION_READ_UNCOMMITTED ); ISOLATION_VALUE_MAP.put( "TRANSACTION_READ_COMMITTED", Connection.TRANSACTION_READ_COMMITTED ); ISOLATION_VALUE_MAP.put( "READ_COMMITTED", Connection.TRANSACTION_READ_COMMITTED ); ISOLATION_VALUE_MAP.put( "TRANSACTION_REPEATABLE_READ", Connection.TRANSACTION_REPEATABLE_READ ); ISOLATION_VALUE_MAP.put( "REPEATABLE_READ", Connection.TRANSACTION_REPEATABLE_READ ); ISOLATION_VALUE_MAP.put( "TRANSACTION_SERIALIZABLE", Connection.TRANSACTION_SERIALIZABLE ); ISOLATION_VALUE_MAP.put( "SERIALIZABLE", Connection.TRANSACTION_SERIALIZABLE ); ISOLATION_VALUE_CONSTANT_NAME_MAP = new ConcurrentHashMap<Integer, String>(); ISOLATION_VALUE_CONSTANT_NAME_MAP.put( Connection.TRANSACTION_NONE, "TRANSACTION_NONE" ); ISOLATION_VALUE_CONSTANT_NAME_MAP.put( Connection.TRANSACTION_READ_UNCOMMITTED, "TRANSACTION_READ_UNCOMMITTED" ); ISOLATION_VALUE_CONSTANT_NAME_MAP.put( Connection.TRANSACTION_READ_COMMITTED, "TRANSACTION_READ_COMMITTED" ); ISOLATION_VALUE_CONSTANT_NAME_MAP.put( Connection.TRANSACTION_REPEATABLE_READ, "TRANSACTION_REPEATABLE_READ" ); ISOLATION_VALUE_CONSTANT_NAME_MAP.put( Connection.TRANSACTION_SERIALIZABLE, "TRANSACTION_SERIALIZABLE" ); ISOLATION_VALUE_NICE_NAME_MAP = new ConcurrentHashMap<Integer, String>(); ISOLATION_VALUE_NICE_NAME_MAP.put( Connection.TRANSACTION_NONE, "NONE" ); ISOLATION_VALUE_NICE_NAME_MAP.put( Connection.TRANSACTION_READ_UNCOMMITTED, "READ_UNCOMMITTED" ); ISOLATION_VALUE_NICE_NAME_MAP.put( Connection.TRANSACTION_READ_COMMITTED, "READ_COMMITTED" ); ISOLATION_VALUE_NICE_NAME_MAP.put( Connection.TRANSACTION_REPEATABLE_READ, "REPEATABLE_READ" ); ISOLATION_VALUE_NICE_NAME_MAP.put( Connection.TRANSACTION_SERIALIZABLE, "SERIALIZABLE" ); } // Connection properties (map value) that automatically need set if the // Hibernate property (map key) is available. Makes the assumption that // both settings use the same value type. private static final Map<String, String> CONDITIONAL_PROPERTIES; static { CONDITIONAL_PROPERTIES = new HashMap<String, String>(); // Oracle requires that includeSynonyms=true in order for getColumns to work using a table synonym name. CONDITIONAL_PROPERTIES.put( AvailableSettings.ENABLE_SYNONYMS, "includeSynonyms" ); } public static Integer extractIsolation(Map settings) { return interpretIsolation( settings.get( AvailableSettings.ISOLATION ) ); } public static Integer interpretIsolation(Object setting) { if ( setting == null ) { return null; } if ( Number.class.isInstance( setting ) ) { return ( (Number) setting ).intValue(); } final String settingAsString = setting.toString(); if ( StringHelper.isEmpty( settingAsString ) ) { return null; } if ( ISOLATION_VALUE_MAP.containsKey( settingAsString ) ) { return ISOLATION_VALUE_MAP.get( settingAsString ); } // it could be a String representation of the isolation numeric value... try { return Integer.valueOf( settingAsString ); } catch (NumberFormatException ignore) { } throw new HibernateException( "Could not interpret transaction isolation setting [" + setting + "]" ); } /** * Gets the {@link Connection} constant name corresponding to the given isolation. * * @param isolation The transaction isolation numeric value. * * @return The corresponding Connection constant name. * * @throws HibernateException If the given isolation does not map to JDBC standard isolation * * @see #toIsolationNiceName */ public static String toIsolationConnectionConstantName(Integer isolation) { final String name = ISOLATION_VALUE_CONSTANT_NAME_MAP.get( isolation ); if ( name == null ) { throw new HibernateException( "Could not convert isolation value [" + isolation + "] to java.sql.Connection constant name" ); } return name; } /** * Get the name of a JDBC transaction isolation level * * @param isolation The transaction isolation numeric value. * * @return a nice human-readable name * * @see #toIsolationConnectionConstantName */ public static String toIsolationNiceName(Integer isolation) { String name = null; if ( isolation != null ) { name = ISOLATION_VALUE_NICE_NAME_MAP.get( isolation ); } if ( name == null ) { name = "<unknown>"; } return name; } }