001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing.factory.epsg;
018:
019: // J2SE dependencies
020: import java.sql.Connection;
021: import java.sql.DatabaseMetaData;
022: import java.sql.SQLException;
023: import javax.sql.DataSource;
024: import java.util.Iterator;
025: import java.util.Comparator;
026: import java.util.Collections;
027: import java.util.logging.Level;
028: import java.util.logging.LogRecord;
029: import javax.imageio.spi.ServiceRegistry;
030: import javax.naming.InitialContext;
031: import javax.naming.NameNotFoundException;
032: import javax.naming.NamingException;
033: import javax.naming.NoInitialContextException;
034:
035: // OpenGIS dependencies
036: import org.opengis.metadata.citation.Citation;
037: import org.opengis.referencing.FactoryException;
038: import org.opengis.referencing.cs.CSAuthorityFactory;
039: import org.opengis.referencing.crs.CRSAuthorityFactory;
040: import org.opengis.referencing.datum.DatumAuthorityFactory;
041: import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
042:
043: // Geotools dependencies
044: import org.geotools.factory.GeoTools;
045: import org.geotools.factory.Hints;
046: import org.geotools.factory.FactoryRegistry;
047: import org.geotools.metadata.iso.citation.Citations;
048: import org.geotools.referencing.ReferencingFactoryFinder;
049: import org.geotools.referencing.factory.FactoryGroup;
050: import org.geotools.referencing.factory.AbstractAuthorityFactory;
051: import org.geotools.referencing.factory.DeferredAuthorityFactory;
052: import org.geotools.referencing.factory.FactoryNotFoundException;
053: import org.geotools.resources.i18n.Errors;
054: import org.geotools.resources.i18n.ErrorKeys;
055: import org.geotools.resources.i18n.Logging;
056: import org.geotools.resources.i18n.LoggingKeys;
057: import org.geotools.resources.i18n.Vocabulary;
058: import org.geotools.resources.i18n.VocabularyKeys;
059:
060: /**
061: * Base class for EPSG factories to be registered in {@link ReferencingFactoryFinder}.
062: * Various subclasses are defined for different database backends: Access, PostgreSQL,
063: * HSQL, <cite>etc.</cite>.
064: * <p>
065: * Users should not creates instance of this class directly. They should invoke one of
066: * <code>{@linkplain ReferencingFactoryFinder}.getFooAuthorityFactory("EPSG")</code>
067: * methods instead.
068: * <p>
069: * This class will change in Geotools 2.5; avoid direct dependence if possible. The current
070: * {@code ThreadedEpsgFactory} name does not reflect its actual behavior in Geotools 2.4.
071: * It should reflect the behavior expected in Geotools 2.5.
072: *
073: * @since 2.4
074: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/epsg/ThreadedEpsgFactory.java $
075: * @version $Id: ThreadedEpsgFactory.java 26328 2007-07-24 16:57:19Z desruisseaux $
076: * @author Martin Desruisseaux
077: */
078: public class ThreadedEpsgFactory extends DeferredAuthorityFactory
079: implements CRSAuthorityFactory, CSAuthorityFactory,
080: DatumAuthorityFactory, CoordinateOperationAuthorityFactory {
081: /**
082: * The default JDBC {@linkplain DataSource data source} name in JNDI.
083: * This is the name used if no other name were specified through the
084: * {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE} hint.
085: *
086: * @see #createDataSource
087: */
088: public static final String DATASOURCE_NAME = "jdbc/EPSG"; //TODO: default should be provided by the Hint
089:
090: /**
091: * {@code true} if automatic registration of {@link #datasourceName} is allowed.
092: * Set to {@code false} for now because the registration has not been correctly
093: * tested in JEE environment.
094: *
095: * @todo Consider removing completly the code related to JNDI binding. In such
096: * case, this field and the {@link #registerInto} field would be removed.
097: */
098: private static final boolean ALLOW_REGISTRATION = false;
099:
100: /**
101: * The factory registry for EPSG data sources. Will be created only when first needed.
102: *
103: * @deprecated To remove when other deprecated methods will have been removed.
104: */
105: private static FactoryRegistry datasources;
106:
107: /**
108: * The default priority level for this factory.
109: */
110: static final int PRIORITY = MAXIMUM_PRIORITY - 10;
111:
112: /**
113: * The factories to be given to the backing store.
114: */
115: private final FactoryGroup factories;
116:
117: /**
118: * The context where to register {@link #datasource}, or {@code null} if it should
119: * not be registered. This is used only as a way to pass "hiden" return value between
120: * {@link #createDataSource()} and {@link #createBackingStore()}.
121: */
122: private transient InitialContext registerInto;
123:
124: /**
125: * The data source name. If it was not specified by the {@link Hints#EPSG_DATA_SOURCE
126: * EPSG_DATA_SOURCE} hint, then this is the {@value #DATASOURCE_NAME} value.
127: */
128: private String datasourceName;
129:
130: /**
131: * The data source, or {@code null} if the connection has not yet been etablished.
132: */
133: private DataSource datasource;
134:
135: /**
136: * The shutdown hook, or {@code null} if none.
137: */
138: private Thread shutdown;
139:
140: /**
141: * Constructs an authority factory using the default set of factories.
142: */
143: public ThreadedEpsgFactory() {
144: this (null);
145: }
146:
147: /**
148: * Constructs an authority factory with the default priority.
149: */
150: public ThreadedEpsgFactory(final Hints userHints) {
151: this (userHints, PRIORITY);
152: }
153:
154: /**
155: * Constructs an authority factory using a set of factories created from the specified hints.
156: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
157: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
158: * {@code FACTORY} hints, in addition of {@link Hints#EPSG_DATA_SOURCE EPSG_DATA_SOURCE}.
159: *
160: * @param userHints An optional set of hints, or {@code null} if none.
161: * @param priority The priority for this factory, as a number between
162: * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
163: * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
164: */
165: public ThreadedEpsgFactory(final Hints userHints, final int priority) {
166: super (userHints, priority);
167: if (userHints != null) {
168: datasourceName = (String) userHints
169: .get(Hints.EPSG_DATA_SOURCE);
170: }
171: if (datasourceName == null) {
172: datasourceName = DATASOURCE_NAME;
173: }
174: hints.put(Hints.EPSG_DATA_SOURCE, datasourceName);
175: factories = FactoryGroup.createInstance(userHints);
176: setTimeout(30 * 60 * 1000L); // Close the connection after at least 30 minutes of inactivity.
177: }
178:
179: /**
180: * Returns the authority for this EPSG database.
181: * This authority will contains the database version in the {@linkplain Citation#getEdition
182: * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
183: */
184: public Citation getAuthority() {
185: final Citation authority = super .getAuthority();
186: return (authority != null) ? authority : Citations.EPSG;
187: }
188:
189: /**
190: * Returns the data source for the EPSG database. If no data source has been previously
191: * {@linkplain #setDataSource set}, then this method invokes {@link #createDataSource}.
192: * <strong>Note:</strong> invoking this method may force immediate connection to the EPSG
193: * database.
194: *
195: * @return The data source.
196: * @throws SQLException if the connection to the EPSG database failed.
197: *
198: * @see #setDataSource
199: * @see #createDataSource
200: */
201: public final synchronized DataSource getDataSource()
202: throws SQLException {
203: if (datasource == null) {
204: // Force the creation of the underlying backing store. It will invokes
205: // (indirectly) createBackingStore, which will fetch the DataSource.
206: if (!super .isAvailable()) {
207: // Connection failed, but the exception is not available.
208: datasource = null;
209: throw new SQLException(Errors
210: .format(ErrorKeys.NO_DATA_SOURCE));
211: }
212: }
213: return datasource;
214: }
215:
216: /**
217: * Set the data source for the EPSG database. If an other EPSG database was already in use,
218: * it will be disconnected. Users should not invoke this method on the factory returned by
219: * {@link ReferencingFactoryFinder}, since it could have a system-wide effect.
220: *
221: * @param datasource The new datasource.
222: * @throws SQLException if an error occured.
223: */
224: public synchronized void setDataSource(final DataSource datasource)
225: throws SQLException {
226: if (datasource != this .datasource) {
227: try {
228: dispose();
229: } catch (FactoryException exception) {
230: final Throwable cause = exception.getCause();
231: if (cause instanceof SQLException) {
232: throw (SQLException) cause;
233: }
234: if (cause instanceof RuntimeException) {
235: throw (RuntimeException) cause;
236: }
237: // Not really an SQL exception, but we should not reach this point anyway.
238: final SQLException e = new SQLException(exception
239: .getLocalizedMessage());
240: e.initCause(exception);
241: throw e;
242: }
243: this .datasource = datasource;
244: }
245: }
246:
247: /**
248: * Returns a data source from the factory registry. This method is invoked by
249: * {@link #createBackingStore()} only if no EPSG data source was found in the
250: * JNDI (Java Naming and Directory). This method scans for plugins the first
251: * time it is invoked.
252: *
253: * @return All registered data sources.
254: *
255: * @deprecated To be deleted after we removed the {@code org.geotools...DataSource} interface.
256: */
257: private static synchronized Iterator getDataSources() {
258: final Class category = org.geotools.referencing.factory.epsg.DataSource.class;
259: if (datasources == null) {
260: datasources = new FactoryRegistry(Collections
261: .singleton(category));
262: datasources.scanForPlugins();
263: datasources.setOrdering(category, new Comparator() {
264: public int compare(final Object f1, final Object f2) {
265: return ((org.geotools.referencing.factory.epsg.DataSource) f1)
266: .getPriority()
267: - ((org.geotools.referencing.factory.epsg.DataSource) f2)
268: .getPriority();
269: }
270: });
271: }
272: return datasources.getServiceProviders(category, true);
273: }
274:
275: /**
276: * Setup a data source for a connection to the EPSG database. This method is invoked by
277: * {@link #getDataSource()} when no data source has been {@linkplain #setDataSource
278: * explicitly set}. The default implementation searchs for a {@link DataSource} instance
279: * binded to the {@link Hints#EPSG_DATA_SOURCE} name
280: * (<code>{@value #DATASOURCE_NAME}</code> by default) using <cite>Java Naming and
281: * Directory Interfaces</cite> (JNDI). If no data source were found, then this method
282: * returns {@code null}.
283: * <p>
284: * Subclasses override this method in order to initialize a default data source when none were
285: * found with JNDI. For example {@code plugin/epsg-access} defines a default data source using
286: * the JDBC-ODBC bridge, which expects an "{@code EPSG}" database registered as an ODBC data
287: * source (see the {@linkplain org.geotools.referencing.factory.epsg package javadoc} for
288: * installation instructions). Example for a PostgreSQL data source:
289: *
290: * <blockquote><pre>
291: * protected DataSource createDataSource() throws SQLException {
292: * DataSource candidate = super.createDataSource();
293: * if (candidate instanceof Jdbc3SimpleDataSource) {
294: * return candidate;
295: * }
296: * Jdbc3SimpleDataSource ds = new Jdbc3SimpleDataSource();
297: * ds.setServerName("localhost");
298: * ds.setDatabaseName("EPSG");
299: * ds.setUser("postgre");
300: * return ds;
301: * }
302: * </pre></blockquote>
303: *
304: * @return The EPSG data source, or {@code null} if none where found.
305: * @throws SQLException if an error occured while creating the data source.
306: */
307: protected DataSource createDataSource() throws SQLException {
308: InitialContext context = null;
309: DataSource source = null;
310: try {
311: context = GeoTools.getInitialContext(new Hints(hints));
312: source = (DataSource) context.lookup(datasourceName);
313: } catch (NoInitialContextException exception) {
314: // Fall back on 'return null' below.
315: } catch (NameNotFoundException exception) {
316: registerInto = context;
317: // Fall back on 'return null' below.
318: } catch (NamingException exception) {
319: SQLException e = new SQLException(Errors.format(
320: ErrorKeys.CANT_GET_DATASOURCE_$1, datasourceName));
321: e.initCause(exception);
322: throw e;
323: }
324: return source;
325: }
326:
327: /**
328: * Creates the backing store for the specified data source. This method usually returns
329: * a new instance of {@link AccessDialectEpsgFactory} or {@link AnsiDialectEpsgFactory}.
330: * Subclasses may override this method in order to returns an instance tuned for the
331: * SQL syntax of the underlying database. Example for a PostgreSQL data source:
332: *
333: * <blockquote><pre>
334: * protected AbstractAuthorityFactory createBackingStore(Hints hints) throws SQLException {
335: * return new AnsiDialectEpsgFactory(hints, getDataSource().getConnection());
336: * }
337: * </pre></blockquote>
338: *
339: * @param hints A map of hints, including the low-level factories to use for CRS creation.
340: * This argument should be given unchanged to {@code DirectEpsgFactory} constructor.
341: * @return The {@linkplain DirectEpsgFactory EPSG factory} using SQL queries appropriate
342: * for this data source.
343: * @throws SQLException if connection to the database failed.
344: */
345: protected AbstractAuthorityFactory createBackingStore(
346: final Hints hints) throws SQLException {
347: final DataSource source = getDataSource();
348: // The next two lines will be removed after org.geotools...DataSource interface removal.
349: if (source instanceof org.geotools.referencing.factory.epsg.DataSource) {
350: return ((org.geotools.referencing.factory.epsg.DataSource) source)
351: .createFactory(hints);
352: }
353: final Connection connection = source.getConnection();
354: final String quote = connection.getMetaData()
355: .getIdentifierQuoteString();
356: if (quote.equals("\"")) {
357: /*
358: * PostgreSQL quotes the indentifiers with "..." while MS-Access quotes the
359: * identifiers with [...], so we use the identifier quote string metadata as
360: * a way to distinguish the two cases. However I'm not sure that it is a robust
361: * criterion. Subclasses should always override as a safety.
362: */
363: return new FactoryUsingAnsiSQL(hints, connection);
364: }
365: return new FactoryUsingSQL(hints, connection);
366: }
367:
368: /**
369: * Gets the EPSG factory implementation connected to the database. This method is invoked
370: * automatically by {@link #createBackingStore()}.
371: *
372: * @return The connection to the EPSG database.
373: * @throws FactoryException if no data source were found.
374: * @throws SQLException if this method failed to etablish a connection.
375: *
376: * @todo Inline this method into {@link #createBackingStore()} after we removed the
377: * deprecated code.
378: */
379: private AbstractAuthorityFactory createBackingStore0()
380: throws FactoryException, SQLException {
381: assert Thread.holdsLock(this );
382: final Hints sourceHints = new Hints(hints);
383: sourceHints.putAll(factories.getImplementationHints());
384: if (datasource != null) {
385: return createBackingStore(sourceHints);
386: }
387: /*
388: * Try to gets the DataSource from JNDI. In case of success, it will be tried
389: * for a connection before any DataSource declared in META-INF/services/.
390: */
391: DataSource source;
392: final InitialContext context;
393: try {
394: source = createDataSource();
395: context = registerInto;
396: } finally {
397: registerInto = null;
398: }
399: /*
400: * Iterate through all DataSources, begining with the one found in JNDI (if any).
401: * We will retain the first successfull connection. If all DataSource fails, the
402: * exception thrown by the first DataSource will be retrown, since it is usually
403: * the main DataSource.
404: */
405: if (false) {
406: /*
407: * TODO: All the block after // --- Begin deprecated code ----
408: * should be replaced by this code after we deleted the
409: * deprecated org.geotools...DataSource interface:
410: */
411: if (source == null) {
412: throw new FactoryNotFoundException(Errors
413: .format(ErrorKeys.NO_DATA_SOURCE));
414: }
415: final AbstractAuthorityFactory factory;
416: try {
417: datasource = source;
418: factory = createBackingStore(sourceHints);
419: } finally {
420: datasource = null;
421: }
422: }
423: // ---- Begin deprecated code to delete (up to the next block comment) --------------------
424: Iterator sources = null;
425: AbstractAuthorityFactory factory = null;
426: SQLException failure = null;
427: while (true) {
428: if (source != null)
429: try {
430: try {
431: datasource = source;
432: factory = createBackingStore(sourceHints);
433: } finally {
434: datasource = null;
435: }
436: break; // Found a successfull connection: stop the loop.
437: } catch (SQLException exception) {
438: // Keep only the exception from the first data source.
439: if (failure == null) {
440: failure = exception;
441: }
442: }
443: // Setup the iterator (if not already done) and test next DataSources.
444: if (sources == null) {
445: sources = getDataSources();
446: }
447: if (!sources.hasNext()) {
448: if (failure != null) {
449: throw failure;
450: }
451: throw new FactoryNotFoundException(Errors
452: .format(ErrorKeys.NO_DATA_SOURCE));
453: }
454: source = (DataSource) sources.next();
455: }
456: ;
457: /*
458: * We now have a working connection. If a naming directory is running but didn't contains
459: * the "jdbc/EPSG" entry, add it now. In such case, a message is prepared and logged.
460: */
461: LogRecord record;
462: if (ALLOW_REGISTRATION && context != null) {
463: try {
464: context.bind(datasourceName, source);
465: record = Logging.format(Level.INFO,
466: LoggingKeys.CREATED_DATASOURCE_ENTRY_$1,
467: datasourceName);
468: } catch (NamingException exception) {
469: record = Logging.format(Level.WARNING,
470: LoggingKeys.CANT_BIND_DATASOURCE_$1,
471: datasourceName);
472: record.setThrown(exception);
473: }
474: log(record);
475: }
476: this .datasource = source; // Stores the data source only after success.
477: return factory;
478: }
479:
480: /**
481: * Creates the backing store authority factory.
482: *
483: * @return The backing store to uses in {@code createXXX(...)} methods.
484: * @throws FactoryException if the constructor failed to connect to the EPSG database.
485: * This exception usually has a {@link SQLException} as its cause.
486: */
487: protected AbstractAuthorityFactory createBackingStore()
488: throws FactoryException {
489: final AbstractAuthorityFactory factory;
490: String product = '<' + Vocabulary.format(VocabularyKeys.UNKNOW) + '>';
491: String url = product;
492: try {
493: factory = createBackingStore0();
494: if (factory instanceof DirectEpsgFactory) {
495: final DatabaseMetaData info = ((DirectEpsgFactory) factory).connection
496: .getMetaData();
497: product = info.getDatabaseProductName();
498: url = info.getURL();
499: }
500: } catch (SQLException exception) {
501: throw new FactoryException(Errors.format(
502: ErrorKeys.CANT_CONNECT_DATABASE_$1, "EPSG"),
503: exception);
504: }
505: log(Logging.format(Level.CONFIG,
506: LoggingKeys.CONNECTED_EPSG_DATABASE_$2, url, product));
507: if (factory instanceof DirectEpsgFactory) {
508: ((DirectEpsgFactory) factory).buffered = this ;
509: }
510: return factory;
511: }
512:
513: /**
514: * For internal use by {@link #createFactory()} and {@link #createBackingStore()} only.
515: */
516: private static void log(final LogRecord record) {
517: record.setSourceClassName(ThreadedEpsgFactory.class.getName());
518: record.setSourceMethodName("createBackingStore"); // The public caller.
519: LOGGER.log(record);
520: }
521:
522: /**
523: * Returns {@code true} if the backing store can be disposed now. This method is invoked
524: * automatically after the amount of time specified by {@link #setTimeout} if the factory
525: * were not used during that time.
526: *
527: * @param backingStore The backing store in process of being disposed.
528: */
529: protected boolean canDisposeBackingStore(
530: final AbstractAuthorityFactory backingStore) {
531: if (backingStore instanceof DirectEpsgFactory) {
532: return ((DirectEpsgFactory) backingStore).canDispose();
533: }
534: return super .canDisposeBackingStore(backingStore);
535: }
536:
537: /**
538: * Ensures that the database connection will be closed on JVM exit. This code will be executed
539: * even if the JVM is terminated because of an exception or with [Ctrl-C]. Note: we create this
540: * shutdown hook only if this factory is registered as a service because it will prevent this
541: * instance to be garbage collected until it is deregistered.
542: */
543: private final class ShutdownHook extends Thread {
544: public ShutdownHook() {
545: super (DirectEpsgFactory.SHUTDOWN_THREAD);
546: }
547:
548: public void run() {
549: synchronized (ThreadedEpsgFactory.this ) {
550: try {
551: dispose();
552: } catch (Throwable exception) {
553: // Too late for logging, since the JVM is
554: // in process of shutting down. Ignore...
555: }
556: }
557: }
558: }
559:
560: /**
561: * Called when this factory is added to the given {@code category} of the given
562: * {@code registry}. The object may already be registered under another category.
563: */
564: public synchronized void onRegistration(
565: final ServiceRegistry registry, final Class category) {
566: super .onRegistration(registry, category);
567: if (shutdown == null) {
568: shutdown = new ShutdownHook();
569: Runtime.getRuntime().addShutdownHook(shutdown);
570: }
571: }
572:
573: /**
574: * Called when this factory is removed from the given {@code category} of the given
575: * {@code registry}. The object may still be registered under another category.
576: */
577: public synchronized void onDeregistration(
578: final ServiceRegistry registry, final Class category) {
579: if (shutdown != null) {
580: if (registry.getServiceProviderByClass(getClass()) == null) {
581: // Remove the shutdown hook only if this instance is not
582: // anymore registered as a service under any category.
583: Runtime.getRuntime().removeShutdownHook(shutdown);
584: shutdown = null;
585: }
586: }
587: super .onDeregistration(registry, category);
588: }
589:
590: /**
591: * Constructs an object from the EPSG database and print its WKT (Well Know Text) to
592: * the {@linkplain System#out standard output stream}.
593: *
594: * @deprecated Replaced in a more generic way by {@link org.geotools.referencing.CRS#main}
595: * with a {@code "-authority=EPSG"} optional argument.
596: */
597: public static void main(String[] args) {
598: args = (String[]) org.geotools.resources.XArray.insert(args, 0,
599: 1);
600: args[0] = "-authority=EPSG";
601: org.geotools.referencing.CRS.main(args);
602: }
603: }
|