/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, GeoTools Project Managment Committee (PMC)
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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.
 */
package org.geotools.data.jdbc.datasource;

import java.io.IOException;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.geotools.factory.FactoryCreator;
import org.geotools.factory.FactoryRegistry;

/**
 * Enable programs to find all available {@link DataSourceFactorySpi} implementations.
 * 
 * <p>
 * In addition to implementing this interface data souces should have a services file:<br/><code>META-INF/services/org.geotools.data.jdbc.DataSourceFactorySpi</code>
 * </p>
 * 
 * <p>
 * The file should contain a single line which gives the full name of the implementing class.
 * </p>
 * 
 * <p>
 * Example:<br/><code>org.geotools.data.jdbc.DBCPDataSourceFactory</code>
 * </p>
 * 
 * @source $URL:
 *         http://svn.geotools.org/geotools/trunk/gt/modules/unsupported/h2/src/main/java/org/geotools/data/jdbc/ds/DataSourceFinder.java $
 */
public final class DataSourceFinder {
    /** The logger for the filter module. */
    protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.data.jdbc");

    /**
     * The service registry for this manager. Will be initialized only when first needed.
     */
    private static FactoryRegistry registry;

    // Singleton pattern
    private DataSourceFinder() {
    }

    /**
     * Checks each available datasource implementation in turn and returns the first one which
     * claims to support the resource identified by the params object.
     * 
     * @param params
     *            A Map object which contains a defenition of the resource to connect to. for file
     *            based resources the property 'url' should be set within this Map.
     * 
     * @return The first datasource which claims to process the required resource, returns null if
     *         none can be found.
     * 
     * @throws IOException
     *             If a suitable loader can be found, but it can not be attached to the specified
     *             resource without errors.
     */
    public static synchronized DataSource getDataSource(Map params) throws IOException {
        Iterator ps = getAvailableDataSources();
        DataSourceFactorySpi fac;
        while (ps.hasNext()) {
            fac = (DataSourceFactorySpi) ps.next();

            try {
                if (fac.canProcess(params)) {
                    return fac.createDataSource(params);
                }
            } catch (Throwable t) {
                /** The logger for the filter module. */
                LOGGER.log(Level.WARNING, "Could not acquire " + fac.getDescription() + ":" + t, t);
                // Protect against DataStores that don't carefully
                // code canProcess

            }
        }

        return null;
    }

    /**
     * Checks each available datasource implementation in turn and returns the first one which
     * claims to support the resource identified by the params object.
     * 
     * @param params
     *            A Map object which contains a defenition of the resource to connect to. for file
     *            based resources the property 'url' should be set within this Map.
     * 
     * @return The first datasource which claims to process the required resource, returns null if
     *         none can be found.
     * 
     * @throws IOException
     *             If a suitable loader can be found, but it can not be attached to the specified
     *             resource without errors.
     */
    public static synchronized UnWrapper getUnWrapper(Connection conn) throws IOException {
        Iterator ps = getUnWrappers();
        UnWrapper uw;
        while (ps.hasNext()) {
            uw = (UnWrapper) ps.next();

            try {
                if (uw.canUnwrap(conn)) {
                    return uw;
                }
            } catch (Throwable t) {
                /** The logger for the filter module. */
                LOGGER.log(Level.WARNING, "Could not test  " + uw
                        + " for unwrapping abilities agaist " + conn, t);
                // Protect against DataStores that don't carefully
                // code canProcess

            }
        }

        return null;
    }
    
    /**
     * Checks each available {@link UnWrapper} implementation in turn and returns the first one which
     * claims to support the resource identified by the params object.
     * 
     * @param params
     *            A Map object which contains a defenition of the resource to connect to. for file
     *            based resources the property 'url' should be set within this Map.
     * 
     * @return The first datasource which claims to process the required resource, returns null if
     *         none can be found.
     * 
     * @throws IOException
     *             If a suitable loader can be found, but it can not be attached to the specified
     *             resource without errors.
     */
    public static synchronized UnWrapper getUnWrapper(Statement st) throws IOException {
        Iterator ps = getUnWrappers();
        UnWrapper uw;
        while (ps.hasNext()) {
            uw = (UnWrapper) ps.next();

            try {
                if (uw.canUnwrap(st)) {
                    return uw;
                }
            } catch (Throwable t) {
                /** The logger for the filter module. */
                LOGGER.log(Level.WARNING, "Could not test  " + uw
                        + " for unwrapping abilities agaist " + st, t);
                // Protect against DataStores that don't carefully
                // code canProcess

            }
        }

        return null;
    }

    /**
     * Finds all implemtaions of DataStoreFactory which have registered using the services
     * mechanism, and that have the appropriate libraries on the classpath.
     * 
     * @return An iterator over all discovered datastores which have registered factories, and whose
     *         available method returns true.
     */
    public static synchronized Iterator getAvailableDataSources() {
        Set availableDS = new HashSet();
        Iterator it = getServiceRegistry().getServiceProviders(DataSourceFactorySpi.class);
        DataSourceFactorySpi dsFactory;
        while (it.hasNext()) {
            dsFactory = (DataSourceFactorySpi) it.next();

            if (dsFactory.isAvailable()) {
                availableDS.add(dsFactory);
            }
        }

        return availableDS.iterator();
    }

    /**
     * Finds all implemtaions of DataStoreFactory which have registered using the services
     * mechanism, and that have the appropriate libraries on the classpath.
     * 
     * @return An iterator over all discovered datastores which have registered factories, and whose
     *         available method returns true.
     */
    public static synchronized Iterator getUnWrappers() {
        Set availableDS = new HashSet();
        return getServiceRegistry().getServiceProviders(UnWrapper.class);
    }

    /**
     * Returns the service registry. The registry will be created the first time this method is
     * invoked.
     */
    private static FactoryRegistry getServiceRegistry() {
        assert Thread.holdsLock(DataSourceFinder.class);
        if (registry == null) {
            registry = new FactoryCreator(Arrays.asList(new Class[] { DataSourceFactorySpi.class,
                    UnWrapper.class }));
        }
        return registry;
    }

    /**
     * Scans for factory plug-ins on the application class path. This method is needed because the
     * application class path can theoretically change, or additional plug-ins may become available.
     * Rather than re-scanning the classpath on every invocation of the API, the class path is
     * scanned automatically only on the first invocation. Clients can call this method to prompt a
     * re-scan. Thus this method need only be invoked by sophisticated applications which
     * dynamically make new plug-ins available at runtime.
     */
    public static synchronized void scanForPlugins() {

        getServiceRegistry().scanForPlugins();

    }
}
