Package org.geotools.referencing.factory.epsg

Source Code of org.geotools.referencing.factory.epsg.ThreadedEpsgFactory

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
*    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;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import java.util.Iterator;
import java.util.Comparator;
import java.util.Collections;
import java.util.jar.Attributes.Name;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.imageio.spi.ServiceRegistry;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;

import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.datum.DatumAuthorityFactory;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;

import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.factory.FactoryRegistry;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.factory.AbstractAuthorityFactory;
import org.geotools.referencing.factory.DeferredAuthorityFactory;
import org.geotools.referencing.factory.FactoryNotFoundException;
import org.geotools.referencing.factory.ReferencingFactoryContainer;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;


/**
* Base class for EPSG factories to be registered in {@link ReferencingFactoryFinder}.
* Various subclasses are defined for different database backends: Access, PostgreSQL,
* HSQL, <cite>etc.</cite>.
* <p>
* This class has the following responsibilities:
* <ul>
*   <li>aquire a DataSource (using JNDI or otherwise)</li>
*   <li>specify a worker class that will talk to the database in
*     the event of a cache miss. The class will be specific to the delect of SQL
*     used by the database hosting the EPSG tables.</li>
* </ul>
* Please note we are working with <b>the same</b> tables as defined by EPSG. The only
* thing that changes is the database used to host these tables.
* <p>
* Subclasses should override the following methods:
* <ul>
*   <li>{@linkplain #createDataSource} used to aquire a DataSource</li>
*   <li>{@link #createBackingStore} instance capable to speak that database syntax</li>
* </ul>
* <p>
* Users should not creates instance of this class directly. They should invoke one of
* <code>{@linkplain ReferencingFactoryFinder}.getFooAuthorityFactory("EPSG")</code> methods instead.
*
* @since 2.4
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ThreadedEpsgFactory extends DeferredAuthorityFactory
        implements CRSAuthorityFactory, CSAuthorityFactory, DatumAuthorityFactory,
                   CoordinateOperationAuthorityFactory
{
    /**
     * The default JDBC {@linkplain DataSource data source} name in JNDI.
     * This is the name used if no other name were specified through the
     * {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE} hint.
     *
     * @see #createDataSource
     */
    public static final String DATASOURCE_NAME = "java:comp/env/jdbc/EPSG";

    /**
     * {@code true} if automatic registration of {@link #datasourceName} is allowed.
     * Set to {@code false} for now because the registration has not been correctly
     * tested in JEE environment.
     *
     * @todo Consider removing completly the code related to JNDI binding. In such
     *       case, this field and the {@link #registerInto} field would be removed.
     */
    private static final boolean ALLOW_REGISTRATION = false;

    /**
     * The default priority level for this factory.
     */
    static final int PRIORITY = MAXIMUM_PRIORITY - 10;

    /**
     * The factories to be given to the backing store.
     */
    private final ReferencingFactoryContainer factories;

    /**
     * The context where to register {@link #datasource}, or {@code null} if it should
     * not be registered. This is used only as a way to pass "hiden" return value between
     * {@link #createDataSource()} and {@link #createBackingStore()}.
     */
    private transient InitialContext registerInto;

    /**
     * The data source name. If it was not specified by the {@link Hints#EPSG_DATA_SOURCE
     * EPSG_DATA_SOURCE} hint, then this is the {@value #DATASOURCE_NAME} value.
     */
    private String datasourceName;

    /**
     * The data source, or {@code null} if the connection has not yet been etablished.
     */
    protected DataSource datasource;
   
    /**
     * Whether the DataSource is created along with the backing store, or it's a stable, long lived one
     */
    protected boolean dynamicDataSource = true;

    /**
     * Constructs an authority factory using the default set of factories.
     */
    public ThreadedEpsgFactory() {
        this(null);
    }

    /**
     * Constructs an authority factory with the default priority.
     */
    public ThreadedEpsgFactory(final Hints userHints) {
        this(userHints, PRIORITY);
    }

    /**
     * Constructs an authority factory using a set of factories created from the specified hints.
     * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
     * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
     * {@code FACTORY} hints, in addition of {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE}.
     *
     * @param userHints An optional set of hints, or {@code null} if none.
     * @param priority The priority for this factory, as a number between
     *        {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
     *        {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
     */
    public ThreadedEpsgFactory(final Hints userHints, final int priority) {
        super(userHints, priority);

        Object hint = (userHints == null) ? null : userHints.get(Hints.EPSG_DATA_SOURCE);

        if (hint == null) {
          datasourceName = DATASOURCE_NAME;
          //datasourceName = GeoTools.fixName(DATASOURCE_NAME);
          hints.put(Hints.EPSG_DATA_SOURCE, datasourceName);
        } else if (hint instanceof String) {
            datasourceName = (String) hint;
            //datasourceName = GeoTools.fixName(datasourceName);
            hints.put(Hints.EPSG_DATA_SOURCE, datasourceName);
        } else if (hint instanceof Name) {
            Name name = (Name) hint;
            hints.put(Hints.EPSG_DATA_SOURCE, name);
            datasourceName = name.toString();
            //datasourceName = GeoTools.fixName(name.toString());
        } else if (hint instanceof DataSource) {
            datasource = (DataSource) hint;
            hints.put(Hints.EPSG_DATA_SOURCE, datasource);
            datasourceName = DATASOURCE_NAME;
            dynamicDataSource = false;
        }
        factories = ReferencingFactoryContainer.instance(userHints);
        long timeout = 30 * 60 * 1000;
        String defaultTimeout = System.getProperty("org.geotools.epsg.factory.timeout", String.valueOf(30 * 60 * 1000));
        try {
            timeout = Long.valueOf(defaultTimeout);
        } catch(NumberFormatException e) {
            LOGGER.log(Level.WARNING, "Invalid value for org.geotools.epsg.factory.timeout, " +
                "using the default (30 minutes) instead");
        }
        // in case of negative timeout, we don't release the data source and backing store
        if(timeout > 0) {
            LOGGER.log(Level.FINE, "Setting the EPSG factory " + getClass().getName() + " to a " + timeout + "ms timeout");
            setTimeout(timeout); // Close the connection after 1 second of inactivity.
        } else {
            LOGGER.log(Level.FINE, "The EPSG factory " + getClass().getName() + " will not timeout");
        }
    }

    /**
     * Returns the authority for this EPSG database.
     * This authority will contains the database version in the {@linkplain Citation#getEdition
     * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
     */
    @Override
    public Citation getAuthority() {
        final Citation authority = super.getAuthority();
        return (authority!=null) ? authority : Citations.EPSG;
    }

    /**
     * Returns the data source for the EPSG database. If no data source has been previously
     * {@linkplain #setDataSource set}, then this method invokes {@link #createDataSource}.
     * <strong>Note:</strong> invoking this method may force immediate connection to the EPSG
     * database.
     *
     * @return The data source.
     * @throws SQLException if the connection to the EPSG database failed.
     *
     * @see #setDataSource
     * @see #createDataSource
     */
    public final synchronized DataSource getDataSource() throws SQLException {
        if (datasource == null) {
            // Force the creation of the underlying backing store. It will invokes
            // (indirectly) createBackingStore, which will fetch the DataSource.
            if (!super.isAvailable()) {
                // Connection failed, but the exception is not available.
                datasource = null;
                throw new SQLException(Errors.format(ErrorKeys.NO_DATA_SOURCE));
            }
        }
        return datasource;
    }

    /**
     * Set the data source for the EPSG database. If an other EPSG database was already in use,
     * it will be disconnected. Users should not invoke this method on the factory returned by
     * {@link ReferencingFactoryFinder}, since it could have a system-wide effect.
     *
     * @param  datasource The new datasource.
     * @throws SQLException if an error occured.
     */
    public synchronized void setDataSource(final DataSource datasource) throws SQLException {
        if (datasource != this.datasource) {
            try {
                dispose();
            } catch (FactoryException exception) {
                final Throwable cause = exception.getCause();
                if (cause instanceof SQLException) {
                    throw (SQLException) cause;
                }
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                }
                // Not really an SQL exception, but we should not reach this point anyway.
                final SQLException e = new SQLException(exception.getLocalizedMessage());
                e.initCause(exception); // TODO: inline when we will be allowed to target Java 6.
                throw e;
            }
            this.datasource = datasource;
        }
    }

    /**
     * Setup a data source for a connection to the EPSG database. This method is invoked by
     * {@link #getDataSource()} when no data source has been {@linkplain #setDataSource
     * explicitly set}. The default implementation searchs for a {@link DataSource} instance
     * binded to the {@link Hints#EPSG_DATA_SOURCE} name
     * (<code>{@value #DATASOURCE_NAME}</code> by default) using <cite>Java Naming and
     * Directory Interfaces</cite> (JNDI). If no data source were found, then this method
     * returns {@code null}.
     * <p>
     * Subclasses override this method in order to initialize a default data source when none were
     * found with JNDI. For example {@code plugin/epsg-access} defines a default data source using
     * the JDBC-ODBC bridge, which expects an "{@code EPSG}" database registered as an ODBC data
     * source (see the {@linkplain org.geotools.referencing.factory.epsg package javadoc} for
     * installation instructions). Example for a PostgreSQL data source:
     *
     * <blockquote><pre>
     * protected DataSource createDataSource() throws SQLException {
     *     DataSource candidate = super.createDataSource();
     *     if (candidate instanceof Jdbc3SimpleDataSource) {
     *         return candidate;
     *     }
     *     Jdbc3SimpleDataSource ds = new Jdbc3SimpleDataSource();
     *     ds.setServerName("localhost");
     *     ds.setDatabaseName("EPSG");
     *     ds.setUser("postgre");
     *     return ds;
     * }
     * </pre></blockquote>
     *
     * @return The EPSG data source, or {@code null} if none where found.
     * @throws SQLException if an error occured while creating the data source.
     */
    protected DataSource createDataSource() throws SQLException {
        InitialContext context = null;
        DataSource     source  = null;
        try {
            context = GeoTools.getInitialContext(new Hints(hints));
            source = (DataSource) context.lookup(datasourceName);
        } catch (IllegalArgumentException exception) {
         // Fall back on 'return null' below.
        } catch (NoInitialContextException exception) {
            // Fall back on 'return null' below.
        } catch (NamingException exception) {
            registerInto = context;
            // Fall back on 'return null' below.
        }
        return source;
    }

    /**
     * Creates the backing store for the specified data source. This method usually returns
     * a new instance of {@link AccessDialectEpsgFactory} or {@link AnsiDialectEpsgFactory}.
     * Subclasses may override this method in order to returns an instance tuned for the
     * SQL syntax of the underlying database. Example for a PostgreSQL data source:
     *
     * <blockquote><pre>
     * protected AbstractAuthorityFactory createBackingStore(Hints hints) throws SQLException {
     *     return new AnsiDialectEpsgFactory(hints, getDataSource().getConnection());
     * }
     * </pre></blockquote>
     *
     * @param  hints A map of hints, including the low-level factories to use for CRS creation.
     *         This argument should be given unchanged to {@code DirectEpsgFactory} constructor.
     * @return The {@linkplain DirectEpsgFactory EPSG factory} using SQL queries appropriate
     *         for this data source.
     * @throws SQLException if connection to the database failed.
     */
    protected AbstractAuthorityFactory createBackingStore(final Hints hints) throws SQLException {
        final DataSource source = getDataSource();
        final Connection connection = source.getConnection();
        final String quote = connection.getMetaData().getIdentifierQuoteString();
        if (quote.equals("\"")) {
            /*
             * PostgreSQL quotes the indentifiers with "..." while MS-Access quotes the
             * identifiers with [...], so we use the identifier quote string metadata as
             * a way to distinguish the two cases. However I'm not sure that it is a robust
             * criterion. Subclasses should always override as a safety.
             */
            return new FactoryUsingAnsiSQL(hints, connection);
        }
        return new FactoryUsingSQL(hints, connection);
    }

    /**
     * Gets the EPSG factory implementation connected to the database. This method is invoked
     * automatically by {@link #createBackingStore()}.
     *
     * @return The connection to the EPSG database.
     * @throws FactoryException if no data source were found.
     * @throws SQLException if this method failed to etablish a connection.
     *
     * @todo Inline this method into {@link #createBackingStore()} after we removed the
     *       deprecated code.
     */
    private AbstractAuthorityFactory createBackingStore0() throws FactoryException, SQLException {
        assert Thread.holdsLock(this);
        final Hints sourceHints = new Hints(hints);
        sourceHints.putAll(factories.getImplementationHints());
        if (datasource != null) {
            return createBackingStore(sourceHints);
        }
        /*
         * Try to gets the DataSource from JNDI. In case of success, it will be tried
         * for a connection before any DataSource declared in META-INF/services/.
         */
        DataSource source;
        final InitialContext context;
        try {
            source  = createDataSource();
            context = registerInto;
        } finally {
            registerInto = null;
        }
        if (source == null) {
            throw new FactoryNotFoundException(Errors.format(ErrorKeys.NO_DATA_SOURCE));
        }
        final AbstractAuthorityFactory factory;
        try {
            datasource = source;
            factory = createBackingStore(sourceHints);
        } finally {
            datasource = null;
        }
        /*
         * We now have a working connection. If a naming directory is running but didn't contains
         * the "jdbc/EPSG" entry, add it now. In such case, a message is prepared and logged.
         */
        LogRecord record;
        if (ALLOW_REGISTRATION && context != null) {
            try {
                context.bind(datasourceName, source);
                record = Loggings.format(Level.FINE,
                        LoggingKeys.CREATED_DATASOURCE_ENTRY_$1, datasourceName);
            } catch (NamingException exception) {
                record = Loggings.format(Level.WARNING,
                        LoggingKeys.CANT_BIND_DATASOURCE_$1, datasourceName);
                record.setThrown(exception);
            }
            log(record);
        }
        this.datasource = source;  // Stores the data source only after success.
        return factory;
    }

    /**
     * Creates the backing store authority factory.
     *
     * @return The backing store to uses in {@code createXXX(...)} methods.
     * @throws FactoryException if the constructor failed to connect to the EPSG database.
     *         This exception usually has a {@link SQLException} as its cause.
     */
    protected AbstractAuthorityFactory createBackingStore() throws FactoryException {
        final AbstractAuthorityFactory factory;
        String product = '<' + Vocabulary.format(VocabularyKeys.UNKNOWN) + '>';
        String url = product;
        try {
            factory = createBackingStore0();
            if (factory instanceof DirectEpsgFactory) {
                final DatabaseMetaData info = ((DirectEpsgFactory) factory).getConnection().getMetaData();
                product = info.getDatabaseProductName();
                url     = info.getURL();
            }
        } catch (SQLException exception) {
            throw new FactoryException(Errors.format(ErrorKeys.CANT_CONNECT_DATABASE_$1, "EPSG"),
                                       exception);
        }
        log(Loggings.format(Level.CONFIG, LoggingKeys.CONNECTED_EPSG_DATABASE_$2, url, product));
        if (factory instanceof DirectEpsgFactory) {
            ((DirectEpsgFactory) factory).buffered = this;
        }
        return factory;
    }

    /**
     * For internal use by {@link #createFactory()} and {@link #createBackingStore()} only.
     */
    private static void log(final LogRecord record) {
        record.setSourceClassName(ThreadedEpsgFactory.class.getName());
        record.setSourceMethodName("createBackingStore"); // The public caller.
        record.setLoggerName(LOGGER.getName());
        LOGGER.log(record);
    }

    /**
     * Returns {@code true} if the backing store can be disposed now. This method is invoked
     * automatically after the amount of time specified by {@link #setTimeout} if the factory
     * were not used during that time.
     *
     * @param backingStore The backing store in process of being disposed.
     */
    @Override
    protected boolean canDisposeBackingStore(final AbstractAuthorityFactory backingStore) {
        if (backingStore instanceof DirectEpsgFactory) {
            return ((DirectEpsgFactory) backingStore).canDispose();
        }
        return super.canDisposeBackingStore(backingStore);
    }
   
    @Override
    protected void disposeBackingStore() {
        super.disposeBackingStore();
        if(dynamicDataSource) {
            datasource = null;
        }
    }
   
    @Override
    public synchronized void dispose() throws FactoryException {
        super.dispose();
        datasource = null;
    }

}
TOP

Related Classes of org.geotools.referencing.factory.epsg.ThreadedEpsgFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.