# AbstractDataSourceServiceImporterMixin.java

    /*
     * Copyright (c) 2011, Rickard Öberg. All Rights Reserved.
     * Copyright (c) 2012, Paul Merlin. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
     * 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 org.qi4j.library.sql.datasource;

    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.Map;
    import javax.sql.DataSource;
    import org.qi4j.api.composite.PropertyMapper;
    import org.qi4j.api.entity.EntityBuilder;
    import org.qi4j.api.injection.scope.Service;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.service.ImportedServiceDescriptor;
    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.qi4j.library.circuitbreaker.CircuitBreaker;
    import org.qi4j.library.conversion.values.EntityToValue;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public abstract class AbstractDataSourceServiceImporterMixin<PooledDataSourceType extends DataSource>
            implements ServiceImporter<DataSource>, DataSourceServiceImporterActivation
    {

        protected static final Logger LOGGER = LoggerFactory.getLogger( AbstractDataSourceServiceImporterMixin.class );

        private final Map<String, DataSourceConfigurationValue> configs = new HashMap<String, DataSourceConfigurationValue>();

        private final Map<String, PooledDataSourceType> pools = new HashMap<String, PooledDataSourceType>();

        private final Map<DataSource, CircuitBreaker> circuitBreakers = new HashMap<DataSource, CircuitBreaker>();

        @Structure
        protected Module module;

        @Service
        private EntityToValue entityToValue;

        @Override
        public final void passivateDataSourceService()
                throws Exception
        {
            for ( PooledDataSourceType pool : pools.values() ) {
                passivateDataSourcePool( pool );
            }
            pools.clear();
            configs.clear();
            circuitBreakers.clear();
        }

        @Override
        public final synchronized DataSource importService( final ImportedServiceDescriptor importedServiceDescriptor )
                throws ServiceImporterException
        {
            PooledDataSourceType pool = pools.get( importedServiceDescriptor.identity() );
            if ( pool == null ) {

                try {

                    DataSourceConfigurationValue config = getConfiguration( importedServiceDescriptor.identity() );
                    if ( !config.enabled().get() ) {
                        // Not started
                        throw new ServiceImporterException( "DataSource not enabled" );
                    }

                    // Instantiate pool
                    pool = setupDataSourcePool( config );
                    pools.put( importedServiceDescriptor.identity(), pool );

                    LOGGER.info( "Starting up DataSource '" + importedServiceDescriptor.identity() + "' for: {}@{}", config.username().get(), config.url().get() );

                } catch ( Exception e ) {
                    throw new ServiceImporterException( e );
                }

                // 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 );
                }
            }

            // Check if circuitbreaker is used
            final CircuitBreaker circuitBreaker = importedServiceDescriptor.metaInfo( CircuitBreaker.class );
            if ( circuitBreaker != null ) {

                DataSource wrappedDataSource = DataSources.wrapWithCircuitBreaker( importedServiceDescriptor.identity(), pool, circuitBreaker );
                circuitBreakers.put( pool, circuitBreaker );
                return wrappedDataSource;

            } else {

                return pool;

            }
        }

        private DataSourceConfigurationValue getConfiguration( String identity )
                throws InstantiationException
        {
            DataSourceConfigurationValue config = configs.get( identity );
            if ( config == null ) {
                UnitOfWork uow = module.newUnitOfWork( UsecaseBuilder.newUsecase( "Create DataSource pool configuration" ) );

                try {
                    DataSourceConfiguration configEntity = uow.get( DataSourceConfiguration.class, identity );
                    config = entityToValue.convert( DataSourceConfigurationValue.class, configEntity );
                } 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;
                        }
                    }

                    DataSourceConfiguration configEntity = configBuilder.newInstance();
                    config = entityToValue.convert( DataSourceConfigurationValue.class, configEntity );

                    // save
                    try {
                        uow.complete();
                    } catch ( UnitOfWorkCompletionException e2 ) {
                        InstantiationException exception = new InstantiationException( "Could not save configuration in JavaPreferences." );
                        exception.initCause( e2 );
                        throw exception;
                    }

                }

                configs.put( identity, config );
            }

            return config;
        }

        @Override
        public final boolean isAvailable( DataSource instance )
        {
            if ( pools.containsValue( instance ) ) {
                CircuitBreaker circuitBreaker = circuitBreakers.get( instance );
                if ( circuitBreaker != null ) {
                    return circuitBreaker.isOn();
                } else {
                    return true;
                }
            } else {
                return false;
            }
        }

        protected abstract PooledDataSourceType setupDataSourcePool( DataSourceConfigurationValue configuration )
                throws Exception;

        protected abstract void passivateDataSourcePool( PooledDataSourceType dataSourcePool )
                throws Exception;

    }