/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/agpl.txt
*
* 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 se.streamsource.infrastructure.database;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.qi4j.api.composite.PropertyMapper;
import org.qi4j.api.entity.EntityBuilder;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.service.Activatable;
import org.qi4j.api.service.ImportedServiceDescriptor;
import org.qi4j.api.service.ServiceComposite;
import org.qi4j.api.service.ServiceImporter;
import org.qi4j.api.service.ServiceImporterException;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.NoSuchEntityException;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.api.usecase.UsecaseBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
/**
* DataSource service implemented as a ServiceImporter. Sets up and exposes DataSources that can be used in the application.
*/
@Mixins(DataSourceService.Mixin.class)
public interface DataSourceService
extends ServiceImporter, Activatable, ServiceComposite
{
public DataSourceConfiguration getConfiguration( String identity ) throws InstantiationException;
abstract class Mixin
implements DataSourceService, Activatable, ServiceImporter
{
@Structure
Module module;
Map<String, ComboPooledDataSource> pools = new HashMap<String, ComboPooledDataSource>();
Map<String, DataSourceConfiguration> configs = new HashMap<String, DataSourceConfiguration>();
Logger logger = LoggerFactory.getLogger( DataSourceService.class );
public void activate() throws Exception
{
for (DataSourceConfiguration config : configs.values())
{
module.unitOfWorkFactory().getUnitOfWork(config).discard();
}
for (Entry<String, ComboPooledDataSource> entry : pools.entrySet())
{
configurePool( entry.getValue(), entry.getKey() );
}
}
public void passivate() throws Exception
{
for (ComboPooledDataSource pool : pools.values())
{
pool.resetPoolManager();
}
}
public synchronized Object importService( ImportedServiceDescriptor importedServiceDescriptor ) throws ServiceImporterException
{
ComboPooledDataSource pool = pools.get( importedServiceDescriptor.identity() );
if (pool == null)
{
// Instantiate pool
pool = new ComboPooledDataSource();
configurePool( pool, importedServiceDescriptor.identity() );
pools.put( importedServiceDescriptor.identity(), pool );
// Test the pool
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader( null );
try
{
pool.getConnection().close();
logger.info( "Database for DataSource is up!" );
} catch (SQLException e)
{
logger.warn( "Database for DataSource " + importedServiceDescriptor.identity() + " is not currently available" );
throw new ServiceImporterException( "Database for DataSource " + importedServiceDescriptor.identity() + " is not currently available", e );
} finally
{
Thread.currentThread().setContextClassLoader( cl );
}
}
return pool;
}
private void configurePool(ComboPooledDataSource pool, String identity)
{
try
{
DataSourceConfiguration config = getConfiguration( identity );
if (config.enabled().get())
{
Class.forName( config.driver().get() );
pool.setDriverClass( config.driver().get() );
pool.setJdbcUrl( config.url().get() );
String props = config.properties().get();
String[] properties = props.split( "," );
Properties poolProperties = new Properties();
for (String property : properties)
{
if (property.trim().length() > 0)
{
String[] keyvalue = property.trim().split( "=" );
poolProperties.setProperty( keyvalue[0], keyvalue[1] );
}
}
pool.setProperties( poolProperties );
pool.setUser( config.username().get() );
pool.setPassword( config.password().get() );
pool.setMaxConnectionAge( 60 * 60 ); // One hour max age
logger.info( "Starting up DataSource '" + identity + "' for:{}", pool.getUser() + "@" + pool.getJdbcUrl() );
} else
{
// Not started
throw new ServiceImporterException( "DataSource not enabled" );
}
} catch (Exception e)
{
throw new ServiceImporterException( e );
}
}
public synchronized DataSourceConfiguration getConfiguration( String identity ) throws InstantiationException
{
DataSourceConfiguration config = configs.get( identity );
if (config == null)
{
UnitOfWork uow = module.unitOfWorkFactory().newUnitOfWork(UsecaseBuilder.newUsecase("Create DataSource pool configuration"));
try
{
config = uow.get( DataSourceConfiguration.class, identity );
} catch (NoSuchEntityException e)
{
EntityBuilder<DataSourceConfiguration> configBuilder = uow.newEntityBuilder( DataSourceConfiguration.class, identity );
// Check for defaults
String s = identity + ".properties";
InputStream asStream = DataSourceConfiguration.class.getClassLoader().getResourceAsStream( s );
if (asStream != null)
{
try
{
PropertyMapper.map( asStream, configBuilder.instance() );
} catch (IOException e1)
{
uow.discard();
InstantiationException exception = new InstantiationException( "Could not read underlying Properties file." );
exception.initCause( e1 );
throw exception;
}
}
config = configBuilder.newInstance();
try
{
// save
uow.complete();
// create new uow and fetch entity
uow = module.unitOfWorkFactory().newUnitOfWork(UsecaseBuilder.newUsecase("Create DataSource pool configuration"));
config = uow.get( DataSourceConfiguration.class, identity );
} catch (UnitOfWorkCompletionException e2)
{
InstantiationException exception = new InstantiationException( "Could not save configuration in JavaPreferences." );
exception.initCause( e2 );
throw exception;
}
}
configs.put( identity, config );
}
return config;
}
public boolean isActive( Object instance )
{
return pools.containsValue( instance );
}
public boolean isAvailable( Object instance )
{
return pools.containsValue( instance );
}
}
}