/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2005-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.referencing.factory.epsg;

// J2SE dependencies
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;
import java.util.Properties;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

// Geotools dependencies
import org.geotools.util.logging.Logging;
import org.geotools.factory.Hints;
import org.geotools.referencing.factory.AbstractAuthorityFactory;

// PostgreSQL dependencies
import org.postgresql.ds.common.BaseDataSource;
import org.postgresql.jdbc3.Jdbc3SimpleDataSource;


/**
 * Connection to the EPSG database in PostgreSQL database engine using JDBC. The EPSG
 * database can be downloaded from <A HREF="http://www.epsg.org">http://www.epsg.org</A>.
 * It should have been imported into a PostgreSQL database, which doesn't need to be on
 * the local machine.
 * <p>
 * <h3>Connection parameters</h3>
 * The preferred way to specify connection parameters is through the JNDI interface.
 * However, this datasource provides the following alternative as a convenience: if a
 * {@value #CONFIGURATION_FILE} file is found in current directory or in the user's home
 * directory, then the following properties are fetch. Note that the default value may change
 * in a future version if a public server become available.
 * <P>
 * <TABLE BORDER="1">
 * <TR>
 *   <TH>Property</TH>
 *   <TH>Type</TH>
 *   <TH>Description</TH>
 *   <TH>Geotools Default</TH>
 * </TR>
 * <TR>
 *   <TD>{@code serverName}</TD>
 *   <TD>String</TD>
 *   <TD>PostgreSQL database server host name</TD>
 *   <TD>{@code localhost}</TD>
 * </TR>
 * <TR>
 *   <TD>{@code databaseName}</TD>
 *   <TD>String</TD>
 *   <TD>PostgreSQL database name</TD>
 *   <TD>{@code EPSG}</TD>
 * </TR>
 * <TR>
 *   <TD>{@code schema}</TD>
 *   <TD>String</TD>
 *   <TD>The schema for the EPSG tables</TD>
 *   <TD></TD>
 * </TR>
 * <TR>
 *   <TD>{@code portNumber}</TD>
 *   <TD>int</TD>
 *   <TD>TCP port which the PostgreSQL database server is listening on</TD>
 *   <TD>{@code 5432}</TD>
 * </TR>
 * <TR>
 *   <TD>{@code user}</TD>
 *   <TD>String</TD>
 *   <TD>User used to make database connections</TD>
 *   <TD>{@code GeoTools}</TD>
 * </TR>
 * <TR>
 *   <TD>{@code password}</TD>
 *   <TD>String</TD>
 *   <TD>Password used to make database connections</TD>
 *   <TD>{@code GeoTools}</TD></TR>
 * </TABLE>
 * <P>
 * The database version is given in the
 * {@linkplain org.opengis.metadata.citation.Citation#getEdition edition attribute}
 * of the {@linkplain org.opengis.referencing.AuthorityFactory#getAuthority authority}.
 * The postgreSQL database should be read only.
 * <P>
 * Just having this class accessible in the classpath, together with the registration in
 * the {@code META-INF/services/} directory, is suffisient to get a working EPSG authority
 * factory backed by this database. Vendors can create a copy of this class, modify it and
 * bundle it with their own distribution if they want to connect their users to an other
 * database.
 *
 * @since 2.4
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/epsg-postgresql/src/main/java/org/geotools/referencing/factory/epsg/ThreadedPostgreSQLEpsgFactory.java $
 * @version $Id: ThreadedPostgreSQLEpsgFactory.java 27848 2007-11-12 13:10:32Z desruisseaux $
 * @author Didier Richard
 * @author Martin Desruisseaux
 *
 * @tutorial http://docs.codehaus.org/display/GEOTOOLS/How+to+install+the+EPSG+database+in+PostgreSQL
 */
public class ThreadedPostgreSQLEpsgFactory extends ThreadedEpsgFactory {
    /**
     * The user configuration file. This class search first for the first file found in the
     * following directories:
     * <ul>
     *   <li>The current directory</li>
     *   <li>The user's home directory</li>
     * </ul>
     */
    public static final String CONFIGURATION_FILE = "EPSG-DataSource.properties";

    /**
     * The schema name, or {@code null} if none.
     */
    private String schema;

    /**
     * Creates a new instance of this factory.
     */
    public ThreadedPostgreSQLEpsgFactory() {
        this(null);
    }

    /**
     * Creates a new instance of this factory with the specified hints.
     * The priority is set to a lower value than the {@linkplain FactoryOnAccess}'s one
     * in order to give the priority to any "official" database installed locally by the
     * user, when available.
     */
    public ThreadedPostgreSQLEpsgFactory(final Hints hints) {
        super(hints, PRIORITY + 5);
    }

    /**
     * Loads the {@linkplain #CONFIGURATION_FILE configuration file}.
     */
    private static Properties load() {
        final Properties p = new Properties();
        File file = new File(CONFIGURATION_FILE);
        if (!file.isFile()) {
            file = new File(System.getProperty("user.home", "."), CONFIGURATION_FILE);
            if (!file.isFile()) {
                // Returns an empty set of properties.
                return p;
            }
        }
        try {
            final InputStream in = new FileInputStream(file);
            p.load(in);
            in.close();
        } catch (IOException exception) {
            Logging.unexpectedException("org.geotools.referencing.factory",
                    ThreadedPostgreSQLEpsgFactory.class, "<init>", exception);
            // Continue. We will try to work with whatever properties are available.
        }
        return p;
    }

    /**
     * Returns a data source for the PostgreSQL database.
     */
    protected DataSource createDataSource() throws SQLException {
        DataSource candidate = super.createDataSource();
        if (candidate instanceof BaseDataSource) {
            // Any kind of DataSource from the PostgreSQL driver.
            return candidate;
        }
        final Jdbc3SimpleDataSource source = new Jdbc3SimpleDataSource();
        final Properties p = load();
        int portNumber;
        try {
            portNumber = Integer.parseInt(p.getProperty("portNumber", "5432"));
        } catch (NumberFormatException exception) {
            portNumber = 5432;
            Logging.unexpectedException("org.geotools.referencing.factory",
                    ThreadedPostgreSQLEpsgFactory.class, "<init>", exception);
        }
        source.setPortNumber  (portNumber);
        source.setServerName  (p.getProperty("serverName",   "localhost"));
        source.setDatabaseName(p.getProperty("databaseName", "EPSG"     ));
        source.setUser        (p.getProperty("user",         "Geotools" ));
        source.setPassword    (p.getProperty("password",     "Geotools" ));
        schema = p.getProperty("schema", null);
        return source;
    }

    /**
     * Returns the backing-store factory for PostgreSQL syntax.
     *
     * @param  hints A map of hints, including the low-level factories to use for CRS creation.
     * @return The EPSG factory using PostgreSQL syntax.
     * @throws SQLException if connection to the database failed.
     */
    protected AbstractAuthorityFactory createBackingStore(final Hints hints) throws SQLException {
        final Connection connection = getDataSource().getConnection();
        final FactoryUsingAnsiSQL factory = new FactoryUsingAnsiSQL(hints, connection);
        if (schema != null) {
            factory.setSchema(schema);
        }
        return factory;
    }
}
