0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
0005: * (C) 2005, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation;
0010: * version 2.1 of the License.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: */
0017: package org.geotools.referencing.factory.epsg;
0018:
0019: // J2SE dependencies and extensions
0020: import java.util.*;
0021: import java.io.File;
0022: import java.net.URI;
0023: import java.net.URISyntaxException;
0024: import java.sql.Date;
0025: import java.sql.Connection;
0026: import java.sql.PreparedStatement;
0027: import java.sql.Statement;
0028: import java.sql.ResultSet;
0029: import java.sql.DatabaseMetaData;
0030: import java.sql.ResultSetMetaData;
0031: import java.sql.SQLException;
0032: import java.lang.ref.Reference;
0033: import java.lang.ref.WeakReference;
0034: import java.lang.ref.SoftReference;
0035: import java.util.logging.Level;
0036: import javax.units.NonSI;
0037: import javax.units.Unit;
0038: import javax.units.SI;
0039:
0040: // OpenGIS dependencies
0041: import org.opengis.metadata.Identifier;
0042: import org.opengis.metadata.extent.Extent;
0043: import org.opengis.metadata.citation.Citation;
0044: import org.opengis.metadata.quality.EvaluationMethodType;
0045: import org.opengis.metadata.quality.PositionalAccuracy;
0046: import org.opengis.parameter.*;
0047: import org.opengis.referencing.*;
0048: import org.opengis.referencing.cs.*;
0049: import org.opengis.referencing.crs.*;
0050: import org.opengis.referencing.datum.*;
0051: import org.opengis.referencing.operation.*;
0052: import org.opengis.util.GenericName;
0053: import org.opengis.util.InternationalString;
0054:
0055: // Geotools dependencies
0056: import org.geotools.factory.Hints;
0057: import org.geotools.measure.Units;
0058: import org.geotools.metadata.iso.citation.Citations;
0059: import org.geotools.metadata.iso.citation.CitationImpl;
0060: import org.geotools.metadata.iso.extent.ExtentImpl;
0061: import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
0062: import org.geotools.metadata.iso.quality.QuantitativeResultImpl;
0063: import org.geotools.metadata.iso.quality.AbsoluteExternalPositionalAccuracyImpl;
0064: import org.geotools.parameter.DefaultParameterDescriptor;
0065: import org.geotools.parameter.DefaultParameterDescriptorGroup;
0066: import org.geotools.referencing.AbstractIdentifiedObject;
0067: import org.geotools.referencing.factory.AbstractAuthorityFactory;
0068: import org.geotools.referencing.factory.DirectAuthorityFactory;
0069: import org.geotools.referencing.factory.IdentifiedObjectFinder;
0070: import org.geotools.referencing.NamedIdentifier;
0071: import org.geotools.referencing.datum.DefaultGeodeticDatum;
0072: import org.geotools.referencing.datum.BursaWolfParameters;
0073: import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
0074: import org.geotools.referencing.operation.DefaultConcatenatedOperation;
0075: import org.geotools.referencing.operation.DefaultOperationMethod;
0076: import org.geotools.referencing.operation.DefaultOperation;
0077: import org.geotools.referencing.operation.DefiningConversion;
0078: import org.geotools.resources.Utilities;
0079: import org.geotools.resources.CRSUtilities;
0080: import org.geotools.resources.i18n.Errors;
0081: import org.geotools.resources.i18n.ErrorKeys;
0082: import org.geotools.resources.i18n.Logging;
0083: import org.geotools.resources.i18n.LoggingKeys;
0084: import org.geotools.resources.i18n.Vocabulary;
0085: import org.geotools.resources.i18n.VocabularyKeys;
0086: import org.geotools.io.TableWriter;
0087: import org.geotools.util.LocalName;
0088: import org.geotools.util.SimpleInternationalString;
0089: import org.geotools.util.ScopedName;
0090: import org.geotools.util.Version;
0091:
0092: /**
0093: * A coordinate reference system factory backed by the EPSG database tables.
0094: * <p>
0095: * The EPSG database is freely available at <A HREF="http://www.epsg.org">http://www.epsg.org</a>.
0096: * Current version of this class requires EPSG database version 6.6 or above.
0097: * <p>
0098: * This factory doesn't cache any result. Any call to a {@code createFoo} method will send a new
0099: * query to the EPSG database. For caching, this factory should be wrapped in some buffered factory
0100: * like {@link ThreadedEpsgFactory}.
0101: * <p>
0102: * This class is abstract - please see the subclasses for dialect specific implementations:
0103: * <ul>
0104: * <li>{@link AccessDialectEpsgFactory}</li>
0105: * <li>{@link AnsiDialectEpsgFactory}</li>
0106: * <li>{@link OracleDialectEpsgFactory}</li>
0107: * </ul>
0108: *
0109: * These factories accepts names as well as numerical identifiers. For example
0110: * "<cite>NTF (Paris) / France I</cite>" and {@code "27581"} both fetchs the same object.
0111: * However, names may be ambiguous since the same name may be used for more than one object.
0112: * This is the case of "WGS 84" for example. If such an ambiguity is found, an exception
0113: * will be thrown. If names are not wanted as a legal EPSG code, subclasses can override the
0114: * {@link #isPrimaryKey} method.
0115: *
0116: * @since 2.4
0117: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/epsg/DirectEpsgFactory.java $
0118: * @version $Id: DirectEpsgFactory.java 28264 2007-12-05 21:53:08Z desruisseaux $
0119: * @author Yann Cézard
0120: * @author Martin Desruisseaux
0121: * @author Rueben Schulz
0122: * @author Matthias Basler
0123: * @author Andrea Aime
0124: */
0125: public abstract class DirectEpsgFactory extends DirectAuthorityFactory
0126: implements CRSAuthorityFactory, CSAuthorityFactory,
0127: DatumAuthorityFactory, CoordinateOperationAuthorityFactory {
0128: //////////////////////////////////////////////////////////////////////////////////////////////
0129: ////// ///////
0130: ////// HARD CODED VALUES (other than SQL statements) RELATIVE TO THE EPSG DATABASE ///////
0131: ////// ///////
0132: //////////////////////////////////////////////////////////////////////////////////////////////
0133: /**
0134: * Returns a hard-coded unit from an EPSG code. We do not need to provide all units here,
0135: * but we must at least provide all base units declared in the [TARGET_UOM_CODE] column
0136: * of table [Unit of Measure]. Other units will be derived automatically if they are not
0137: * listed here.
0138: *
0139: * @param code The code.
0140: * @return The unit, or {@code null} if the code is unrecognized.
0141: */
0142: private static Unit getUnit(final int code) {
0143: switch (code) {
0144: case 9001:
0145: return SI.METER;
0146: case 9002:
0147: return NonSI.FOOT;
0148: case 9030:
0149: return NonSI.NAUTICAL_MILE;
0150: case 9036:
0151: return SI.KILO(SI.METER);
0152: case 9101:
0153: return SI.RADIAN;
0154: case 9122: // Fall through
0155: case 9102:
0156: return NonSI.DEGREE_ANGLE;
0157: case 9103:
0158: return NonSI.MINUTE_ANGLE;
0159: case 9104:
0160: return NonSI.SECOND_ANGLE;
0161: case 9105:
0162: return NonSI.GRADE;
0163: case 9107:
0164: return Units.DEGREE_MINUTE_SECOND;
0165: case 9108:
0166: return Units.DEGREE_MINUTE_SECOND;
0167: case 9109:
0168: return SI.MICRO(SI.RADIAN);
0169: case 9110:
0170: return Units.SEXAGESIMAL_DMS;
0171: //TODO case 9111: return NonSI.SEXAGESIMAL_DM;
0172: case 9203: // Fall through
0173: case 9201:
0174: return Unit.ONE;
0175: case 9202:
0176: return Units.PPM;
0177: default:
0178: return null;
0179: }
0180: }
0181:
0182: /**
0183: * Set a Bursa-Wolf parameter from an EPSG parameter.
0184: *
0185: * @param parameters The Bursa-Wolf parameters to modify.
0186: * @param code The EPSG code for a parameter from [PARAMETER_CODE] column.
0187: * @param value The value of the parameter from [PARAMETER_VALUE] column.
0188: * @param unit The unit of the parameter value from [UOM_CODE] column.
0189: * @throws FactoryException if the code is unrecognized.
0190: */
0191: private static void setBursaWolfParameter(
0192: final BursaWolfParameters parameters, final int code,
0193: double value, final Unit unit) throws FactoryException {
0194: Unit target = unit;
0195: if (code >= 8605) {
0196: if (code <= 8607)
0197: target = SI.METER;
0198: else if (code <= 8710)
0199: target = NonSI.SECOND_ANGLE;
0200: else if (code == 8611)
0201: target = Units.PPM;
0202: }
0203: if (target != unit) {
0204: value = unit.getConverterTo(target).convert(value);
0205: }
0206: switch (code) {
0207: case 8605:
0208: parameters.dx = value;
0209: break;
0210: case 8606:
0211: parameters.dy = value;
0212: break;
0213: case 8607:
0214: parameters.dz = value;
0215: break;
0216: case 8608:
0217: parameters.ex = value;
0218: break;
0219: case 8609:
0220: parameters.ey = value;
0221: break;
0222: case 8610:
0223: parameters.ez = value;
0224: break;
0225: case 8611:
0226: parameters.ppm = value;
0227: break;
0228: default:
0229: throw new FactoryException(Errors.format(
0230: ErrorKeys.UNEXPECTED_PARAMETER_$1,
0231: new Integer(code)));
0232: }
0233: }
0234:
0235: /// Datum shift operation methods
0236: /** First Bursa-Wolf method. */
0237: private static final int BURSA_WOLF_MIN_CODE = 9603;
0238: /** Last Bursa-Wolf method. */
0239: private static final int BURSA_WOLF_MAX_CODE = 9607;
0240: /** Rotation frame method. */
0241: private static final int ROTATION_FRAME_CODE = 9607;
0242: /** Dummy operation to ignore. */
0243: private static final int DUMMY_OPERATION = 1;
0244:
0245: /**
0246: * List of tables and columns to test for codes values.
0247: * This table is used by the {@link #createObject} method in order to detect
0248: * which of the following methods should be invoked for a given code:
0249: *
0250: * {@link #createCoordinateReferenceSystem}
0251: * {@link #createCoordinateSystem}
0252: * {@link #createDatum}
0253: * {@link #createEllipsoid}
0254: * {@link #createUnit}
0255: *
0256: * The order is significant: it is the key for a {@code switch} statement.
0257: *
0258: * @see #createObject
0259: * @see #lastObjectType
0260: */
0261: private static final TableInfo[] TABLES_INFO = {
0262: new TableInfo(CoordinateReferenceSystem.class,
0263: "[Coordinate Reference System]",
0264: "COORD_REF_SYS_CODE", "COORD_REF_SYS_NAME",
0265: "COORD_REF_SYS_KIND", new Class[] {
0266: ProjectedCRS.class, GeographicCRS.class,
0267: GeocentricCRS.class }, new String[] {
0268: "projected", "geographic", "geocentric" }),
0269:
0270: new TableInfo(CoordinateSystem.class,
0271: "[Coordinate System]", "COORD_SYS_CODE",
0272: "COORD_SYS_NAME", "COORD_SYS_TYPE", new Class[] {
0273: CartesianCS.class, EllipsoidalCS.class,
0274: SphericalCS.class, VerticalCS.class },
0275: new String[] { "Cartesian", "ellipsoidal",
0276: "spherical", "vertical" }),
0277:
0278: new TableInfo(
0279: CoordinateSystemAxis.class,
0280: "[Coordinate Axis] AS CA INNER JOIN [Coordinate Axis Name] AS CAN"
0281: + " ON CA.COORD_AXIS_NAME_CODE=CAN.COORD_AXIS_NAME_CODE",
0282: "COORD_AXIS_CODE", "COORD_AXIS_NAME"),
0283:
0284: new TableInfo(Datum.class, "[Datum]", "DATUM_CODE",
0285: "DATUM_NAME", "DATUM_TYPE", new Class[] {
0286: GeodeticDatum.class, VerticalDatum.class,
0287: EngineeringDatum.class }, new String[] {
0288: "geodetic", "vertical", "engineering" }),
0289:
0290: new TableInfo(Ellipsoid.class, "[Ellipsoid]",
0291: "ELLIPSOID_CODE", "ELLIPSOID_NAME"),
0292:
0293: new TableInfo(PrimeMeridian.class, "[Prime Meridian]",
0294: "PRIME_MERIDIAN_CODE", "PRIME_MERIDIAN_NAME"),
0295:
0296: new TableInfo(CoordinateOperation.class,
0297: "[Coordinate_Operation]", "COORD_OP_CODE",
0298: "COORD_OP_NAME", "COORD_OP_TYPE", new Class[] {
0299: Projection.class, Conversion.class,
0300: Transformation.class }, new String[] {
0301: "conversion", "conversion",
0302: "transformation" }),
0303: // Note: Projection is handle in a special way.
0304:
0305: new TableInfo(OperationMethod.class,
0306: "[Coordinate_Operation Method]",
0307: "COORD_OP_METHOD_CODE", "COORD_OP_METHOD_NAME"),
0308:
0309: new TableInfo(ParameterDescriptor.class,
0310: "[Coordinate_Operation Parameter]",
0311: "PARAMETER_CODE", "PARAMETER_NAME"),
0312:
0313: new TableInfo(Unit.class, "[Unit of Measure]", "UOM_CODE",
0314: "UNIT_OF_MEAS_NAME") };
0315:
0316: ///////////////////////////////////////////////////////////////////////////////
0317: //////// ////////
0318: //////// E N D O F H A R D C O D E D V A L U E S ////////
0319: //////// ////////
0320: //////// NOTE: 'createFoo(...)' methods may still have hard-coded ////////
0321: //////// values (others than SQL statements) in 'equalsIgnoreCase' ////////
0322: //////// expressions. ////////
0323: ///////////////////////////////////////////////////////////////////////////////
0324: /**
0325: * The name for the transformation accuracy metadata.
0326: */
0327: private static final InternationalString TRANSFORMATION_ACCURACY = Vocabulary
0328: .formatInternational(VocabularyKeys.TRANSFORMATION_ACCURACY);
0329:
0330: /**
0331: * The name of the thread to execute at JVM shutdown. This thread will be created
0332: * by {@link ThreadedEpsgFactory} on registration. It will be checked by {@link #dispose}
0333: * in order to determine if we are in the process for shutting down the database engine.
0334: */
0335: static final String SHUTDOWN_THREAD = "EPSG factory shutdown";
0336:
0337: /**
0338: * The authority for this database. Will be created only when first needed.
0339: * This authority will contains the database version in the {@linkplain Citation#getEdition
0340: * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
0341: */
0342: private transient Citation authority;
0343:
0344: /**
0345: * Last object type returned by {@link #createObject}, or -1 if none.
0346: * This type is an index in the {@link #TABLES_INFO} array and is
0347: * strictly for {@link #createObject} internal use.
0348: */
0349: private int lastObjectType = -1;
0350:
0351: /**
0352: * The last table in which object name were looked for. This is for internal use
0353: * by {@link #toPrimaryKey} only.
0354: */
0355: private transient String lastTableForName;
0356:
0357: /**
0358: * The calendar instance for creating {@link java.util.Date} objects from a year
0359: * (the "epoch" in datum definition). We use the local timezone, which may not be
0360: * quite accurate. But there is no obvious timezone for "epoch", and the "epoch"
0361: * is approximative anyway.
0362: */
0363: private final Calendar calendar = Calendar.getInstance();
0364:
0365: /**
0366: * A pool of prepared statements. Key are {@link String} object related to their
0367: * originating method name (for example "Ellipsoid" for {@link #createEllipsoid},
0368: * while values are {@link PreparedStatement} objects.
0369: * <p>
0370: * <strong>Note:</strong> It is okay to use {@link IdentityHashMap} instead of {@link HashMap}
0371: * because the keys will always be the exact same object, namely the hard-coded argument given
0372: * to calls to {@link #prepareStatement} in this class.
0373: */
0374: private final Map/*<String,PreparedStatement>*/statements = new IdentityHashMap();
0375:
0376: /**
0377: * The set of authority codes for different types. This map is used by the
0378: * {@link #getAuthorityCodes} method as a cache for returning the set created
0379: * in a previous call.
0380: * <p>
0381: * Note that this {@code DirectEpsgFactory} can not be disposed as long as this map is not
0382: * empty, sinces {@link AuthorityCodes} cache some SQL statements and concequently require
0383: * the {@linkplain #connection} to be open. This is why we use soft references rather than
0384: * hard ones, in order to know when no {@link AuthorityCodes} are still in use.
0385: * <p>
0386: * The {@link AuthorityCodes#finalize} methods take care of closing the stamenents used by
0387: * the sets. The {@link AuthorityCodes} reference in this map is then cleared by the garbage
0388: * collector. The {@link #canDispose} method checks if there is any remaining live reference
0389: * in this map, and returns {@code false} if some are found (thus blocking the call to
0390: * {@link #dispose} by the {@link ThreadedEpsgFactory} timer).
0391: */
0392: private final Map/*<Class,Reference<AuthorityCodes>>*/authorityCodes = new HashMap();
0393:
0394: /**
0395: * Cache for axis names. This service is not provided by {@link BufferedAuthorityFactory}
0396: * since {@link AxisName} object are particular to the EPSG database.
0397: *
0398: * @see #getAxisName
0399: */
0400: private final Map/*<String,AxisName>*/axisNames = new HashMap();
0401:
0402: /**
0403: * Cache for axis numbers. This service is not provided by {@link BufferedAuthorityFactory}
0404: * since the number of axis is used internally in this class.
0405: *
0406: * @see #getDimensionForCRS
0407: */
0408: private final Map/*<String,Short>*/axisCounts = new HashMap();
0409:
0410: /**
0411: * Cache for projection checks. This service is not provided by {@link BufferedAuthorityFactory}
0412: * since the check that a transformation is a projection is used internally in this class.
0413: *
0414: * @see #isProjection
0415: */
0416: private final Map/*<String,Boolean>*/codeProjection = new HashMap();
0417:
0418: /**
0419: * Pool of naming systems, used for caching.
0420: * There is usually few of them (about 15).
0421: */
0422: private final Map scopes = new HashMap();
0423:
0424: /**
0425: * The properties to be given the objects to construct.
0426: * Reused every time {@link #createProperties} is invoked.
0427: */
0428: private final Map properties = new HashMap();
0429:
0430: /**
0431: * A safety guard for preventing never-ending loops in recursive calls to
0432: * {@link #createDatum}. This is used by {@link #createBursaWolfParameters},
0433: * which need to create a target datum. The target datum could have its own
0434: * Bursa-Wolf parameters, with one of them pointing again to the source datum.
0435: */
0436: private final Set safetyGuard = new HashSet();
0437:
0438: /**
0439: * The buffered authority factory, or {@code this} if none. This field is set
0440: * to a different value by {@link ThreadedEpsgFactory} only, which will point toward a
0441: * buffered factory wrapping this {@code DirectEpsgFactory} for efficienty.
0442: */
0443: AbstractAuthorityFactory buffered = this ;
0444:
0445: /**
0446: * The connection to the EPSG database.
0447: */
0448: protected final Connection connection;
0449:
0450: /**
0451: * Constructs an authority factory using the specified connection.
0452: *
0453: * @param userHints The underlying factories used for objects creation.
0454: * @param connection The connection to the underlying EPSG database.
0455: */
0456: public DirectEpsgFactory(final Hints userHints,
0457: final Connection connection) {
0458: super (userHints, MAXIMUM_PRIORITY - 20);
0459: // The following hints have no effect on this class behaviour,
0460: // but tell to the user what this factory do about axis order.
0461: hints
0462: .put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
0463: Boolean.FALSE);
0464: hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS, Boolean.FALSE);
0465: hints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean.FALSE);
0466: this .connection = connection;
0467: ensureNonNull("connection", connection);
0468: }
0469:
0470: /**
0471: * Returns the authority for this EPSG database.
0472: * This authority will contains the database version in the {@linkplain Citation#getEdition
0473: * edition} attribute, together with the {@linkplain Citation#getEditionDate edition date}.
0474: */
0475: public synchronized Citation getAuthority() {
0476: if (authority == null)
0477: try {
0478: final String query = adaptSQL("SELECT VERSION_NUMBER, VERSION_DATE FROM [Version History]"
0479: + " ORDER BY VERSION_DATE DESC");
0480: final DatabaseMetaData metadata = connection
0481: .getMetaData();
0482: final Statement statement = connection
0483: .createStatement();
0484: final ResultSet result = statement.executeQuery(query);
0485: if (result.next()) {
0486: final String version = result.getString(1);
0487: final Date date = result.getDate(2);
0488: final String engine = metadata
0489: .getDatabaseProductName();
0490: final CitationImpl c = new CitationImpl(
0491: Citations.EPSG);
0492: c.getAlternateTitles().add(
0493: Vocabulary.formatInternational(
0494: VocabularyKeys.DATA_BASE_$3,
0495: "EPSG", version, engine));
0496: c
0497: .setEdition(new SimpleInternationalString(
0498: version));
0499: c.setEditionDate(date);
0500: authority = (Citation) c.unmodifiable();
0501: hints.put(Hints.VERSION, new Version(version)); // For getImplementationHints()
0502: } else {
0503: authority = Citations.EPSG;
0504: }
0505: result.close();
0506: statement.close();
0507: } catch (SQLException exception) {
0508: org.geotools.util.logging.Logging.unexpectedException(
0509: LOGGER.getName(), DirectEpsgFactory.class,
0510: "getAuthority", exception);
0511: return Citations.EPSG;
0512: }
0513: return authority;
0514: }
0515:
0516: /**
0517: * Returns a description of the database engine.
0518: *
0519: * @throws FactoryException if the database's metadata can't be fetched.
0520: */
0521: public synchronized String getBackingStoreDescription()
0522: throws FactoryException {
0523: final Citation authority = getAuthority();
0524: final TableWriter table = new TableWriter(null, " ");
0525: final Vocabulary resources = Vocabulary.getResources(null);
0526: CharSequence cs;
0527: if ((cs = authority.getEdition()) != null) {
0528: table.write(resources.getString(
0529: VocabularyKeys.VERSION_OF_$1, "EPSG"));
0530: table.write(':');
0531: table.nextColumn();
0532: table.write(cs.toString());
0533: table.nextLine();
0534: }
0535: try {
0536: String s;
0537: final DatabaseMetaData metadata = connection.getMetaData();
0538: if ((s = metadata.getDatabaseProductName()) != null) {
0539: table.write(resources
0540: .getLabel(VocabularyKeys.DATABASE_ENGINE));
0541: table.nextColumn();
0542: table.write(s);
0543: if ((s = metadata.getDatabaseProductVersion()) != null) {
0544: table.write(' ');
0545: table.write(resources.getString(
0546: VocabularyKeys.VERSION_$1, s));
0547: }
0548: table.nextLine();
0549: }
0550: if ((s = metadata.getURL()) != null) {
0551: table.write(resources
0552: .getLabel(VocabularyKeys.DATABASE_URL));
0553: table.nextColumn();
0554: table.write(s);
0555: table.nextLine();
0556: }
0557: } catch (SQLException exception) {
0558: throw new FactoryException(exception);
0559: }
0560: return table.toString();
0561: }
0562:
0563: /**
0564: * Returns the implementation hints for this factory. The returned map contains all the
0565: * values specified in {@linkplain DirectAuthorityFactory#getImplementationHints subclass},
0566: * with the addition of {@link Hints#VERSION VERSION}.
0567: */
0568: // @Override
0569: public Map getImplementationHints() {
0570: if (authority == null) {
0571: // For the computation of Hints.VERSION.
0572: getAuthority();
0573: }
0574: return super .getImplementationHints();
0575: }
0576:
0577: /**
0578: * Returns the set of authority codes of the given type.
0579: * <p>
0580: * <strong>NOTE:</strong> This method returns a living connection to the underlying database.
0581: * This means that the returned set can executes efficiently idioms like the following one:
0582: *
0583: * <blockquote>
0584: * <pre>getAuthorityCodes(<var>type</var).containsAll(<var>others</var>)</pre>
0585: * </blockquote>
0586: *
0587: * But do not keep the returned reference for a long time. The returned set should stay valid
0588: * even if retained for a long time (as long as this factory has not been {@linkplain #dispose
0589: * disposed}), but the existence of those long-living connections may prevent this factory to
0590: * release some resources. If the set of codes is needed for a long time, copy their values in
0591: * an other collection object.
0592: *
0593: * @param type The spatial reference objects type (may be {@code Object.class}).
0594: * @return The set of authority codes for spatial reference objects of the given type.
0595: * If this factory doesn't contains any object of the given type, then this method
0596: * returns an {@linkplain java.util.Collections#EMPTY_SET empty set}.
0597: * @throws FactoryException if access to the underlying database failed.
0598: */
0599: public Set/*<String>*/getAuthorityCodes(final Class type)
0600: throws FactoryException {
0601: return getAuthorityCodes0(type);
0602: }
0603:
0604: /**
0605: * Implementation of {@link #getAuthorityCodes} as a private method, for protecting
0606: * {@link #getDescriptionText} from user overriding of {@link #getAuthorityCodes}.
0607: */
0608: private synchronized Set/*<String>*/getAuthorityCodes0(
0609: final Class type) throws FactoryException {
0610: /*
0611: * If the set were already requested previously for the given type, returns it.
0612: * Otherwise, a new one will be created (but will not use the database connection yet).
0613: */
0614: Reference reference = (Reference) authorityCodes.get(type);
0615: AuthorityCodes candidate = (reference != null) ? (AuthorityCodes) reference
0616: .get()
0617: : null;
0618: if (candidate != null) {
0619: return candidate;
0620: }
0621: Set result = Collections.EMPTY_SET;
0622: for (int i = 0; i < TABLES_INFO.length; i++) {
0623: final TableInfo table = TABLES_INFO[i];
0624: /*
0625: * We test 'isAssignableFrom' in the two ways, which may seems strange but try
0626: * to catch the following use cases:
0627: *
0628: * - table.type.isAssignableFrom(type)
0629: * is for the case where a table is for CoordinateReferenceSystem while the user
0630: * type is some subtype like GeographicCRS. The GeographicCRS need to be queried
0631: * into the CoordinateReferenceSystem table. An additional filter will be applied
0632: * inside the AuthorityCodes class implementation.
0633: *
0634: * - type.isAssignableFrom(table.type)
0635: * is for the case where the user type is IdentifiedObject or Object, in which
0636: * case we basically want to iterate through every tables.
0637: */
0638: if (table.type.isAssignableFrom(type)
0639: || type.isAssignableFrom(table.type)) {
0640: /*
0641: * Maybe an instance already existed but was not found above because the user
0642: * specified some implementation class instead of an interface class. Before
0643: * to return the newly created set, check again in the cached sets using the
0644: * type computed by AuthorityCodes itself.
0645: */
0646: final AuthorityCodes codes;
0647: codes = new AuthorityCodes(connection, TABLES_INFO[i],
0648: type, this );
0649: reference = (Reference) authorityCodes.get(codes.type);
0650: candidate = (reference != null) ? (AuthorityCodes) reference
0651: .get()
0652: : null;
0653: final boolean cache;
0654: if (candidate == null) {
0655: candidate = codes;
0656: cache = true;
0657: } else {
0658: // We will reuse the existing 'candidate' instead of the newly created 'codes'.
0659: assert candidate.sqlAll.equals(codes.sqlAll) : codes.type;
0660: cache = !(reference instanceof SoftReference);
0661: }
0662: if (cache) {
0663: reference = new SoftReference(candidate);
0664: authorityCodes.put(codes.type, reference);
0665: }
0666: /*
0667: * We now have the codes for a single type. Append with the codes of previous
0668: * types, if any. This usually happen only if the user asked for the Object or
0669: * IdentifiedObject type.
0670: */
0671: if (result.isEmpty()) {
0672: result = candidate;
0673: } else {
0674: if (result instanceof AuthorityCodes) {
0675: result = new LinkedHashSet(result);
0676: }
0677: result.addAll(candidate);
0678: }
0679: }
0680: }
0681: return result;
0682: }
0683:
0684: /**
0685: * Gets a description of the object corresponding to a code.
0686: *
0687: * @param code Value allocated by authority.
0688: * @return A description of the object, or {@code null} if the object
0689: * corresponding to the specified {@code code} has no description.
0690: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
0691: * @throws FactoryException if the query failed for some other reason.
0692: */
0693: public InternationalString getDescriptionText(final String code)
0694: throws FactoryException {
0695: final String primaryKey = trimAuthority(code);
0696: for (int i = 0; i < TABLES_INFO.length; i++) {
0697: final Set codes = getAuthorityCodes0(TABLES_INFO[i].type);
0698: if (codes instanceof AuthorityCodes) {
0699: final String text = (String) ((AuthorityCodes) codes)
0700: .asMap().get(primaryKey);
0701: if (text != null) {
0702: return new SimpleInternationalString(text);
0703: }
0704: }
0705: }
0706: /*
0707: * Maybe the user overridden some object creation
0708: * methods with a value for the supplied code.
0709: */
0710: final Identifier identifier = createObject(code).getName();
0711: if (identifier instanceof GenericName) {
0712: return ((GenericName) identifier).toInternationalString();
0713: }
0714: return new SimpleInternationalString(identifier.getCode());
0715: }
0716:
0717: /**
0718: * Returns a prepared statement for the specified name. Most {@link PreparedStatement}
0719: * creations are performed through this method, except {@link #getNumericalIdentifier}
0720: * and {@link #createObject}.
0721: *
0722: * @param key A key uniquely identifying the caller
0723: * (e.g. {@code "Ellipsoid"} for {@link #createEllipsoid}).
0724: * @param sql The SQL statement to use if for creating the {@link PreparedStatement}
0725: * object. Will be used only if no prepared statement was already created for
0726: * the specified key.
0727: * @return The prepared statement.
0728: * @throws SQLException if the prepared statement can't be created.
0729: */
0730: private PreparedStatement prepareStatement(final String key,
0731: final String sql) throws SQLException {
0732: assert Thread.holdsLock(this );
0733: PreparedStatement stmt = (PreparedStatement) statements
0734: .get(key);
0735: if (stmt == null) {
0736: stmt = connection.prepareStatement(adaptSQL(sql));
0737: statements.put(key, stmt);
0738: }
0739: return stmt;
0740: }
0741:
0742: /**
0743: * Gets the string from the specified {@link ResultSet}.
0744: * The string is required to be non-null. A null string
0745: * will throw an exception.
0746: *
0747: * @param result The result set to fetch value from.
0748: * @param columnIndex The column index (1-based).
0749: * @param code The identifier of the record where the string was found.
0750: * @return The string at the specified column.
0751: * @throws SQLException if a SQL error occured.
0752: * @throws FactoryException If a null value was found.
0753: */
0754: private static String getString(final ResultSet result,
0755: final int columnIndex, final String code)
0756: throws SQLException, FactoryException {
0757: final String value = result.getString(columnIndex);
0758: ensureNonNull(result, columnIndex, code);
0759: return value.trim();
0760: }
0761:
0762: /**
0763: * Same as {@link #getString(ResultSet,int,String)}, but report the fault on an alternative
0764: * column if the value is null.
0765: */
0766: private static String getString(final ResultSet result,
0767: final int columnIndex, final String code,
0768: final int columnFault) throws SQLException,
0769: FactoryException {
0770: final String str = result.getString(columnIndex);
0771: if (result.wasNull()) {
0772: final ResultSetMetaData metadata = result.getMetaData();
0773: final String column = metadata.getColumnName(columnFault);
0774: final String table = metadata.getTableName(columnFault);
0775: result.close();
0776: throw new FactoryException(Errors.format(
0777: ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column,
0778: table));
0779: }
0780: return str.trim();
0781: }
0782:
0783: /**
0784: * Gets the value from the specified {@link ResultSet}.
0785: * The value is required to be non-null. A null value
0786: * (i.e. blank) will throw an exception.
0787: *
0788: * @param result The result set to fetch value from.
0789: * @param columnIndex The column index (1-based).
0790: * @param code The identifier of the record where the string was found.
0791: * @return The double at the specified column.
0792: * @throws SQLException if a SQL error occured.
0793: * @throws FactoryException If a null value was found.
0794: */
0795: private static double getDouble(final ResultSet result,
0796: final int columnIndex, final String code)
0797: throws SQLException, FactoryException {
0798: final double value = result.getDouble(columnIndex);
0799: ensureNonNull(result, columnIndex, code);
0800: return value;
0801: }
0802:
0803: /**
0804: * Gets the value from the specified {@link ResultSet}.
0805: * The value is required to be non-null. A null value
0806: * (i.e. blank) will throw an exception.
0807: *
0808: * @param result The result set to fetch value from.
0809: * @param columnIndex The column index (1-based).
0810: * @param code The identifier of the record where the string was found.
0811: * @return The integer at the specified column.
0812: * @throws SQLException if a SQL error occured.
0813: * @throws FactoryException If a null value was found.
0814: */
0815: private static int getInt(final ResultSet result,
0816: final int columnIndex, final String code)
0817: throws SQLException, FactoryException {
0818: final int value = result.getInt(columnIndex);
0819: ensureNonNull(result, columnIndex, code);
0820: return value;
0821: }
0822:
0823: /**
0824: * Make sure that the last result was non-null. Used for {@code getString}, {@code getDouble}
0825: * and {@code getInt} methods only.
0826: */
0827: private static void ensureNonNull(final ResultSet result,
0828: final int columnIndex, final String code)
0829: throws SQLException, FactoryException {
0830: if (result.wasNull()) {
0831: final ResultSetMetaData metadata = result.getMetaData();
0832: final String column = metadata.getColumnName(columnIndex);
0833: final String table = metadata.getTableName(columnIndex);
0834: result.close();
0835: throw new FactoryException(Errors.format(
0836: ErrorKeys.NULL_VALUE_IN_TABLE_$3, code, column,
0837: table));
0838: }
0839: }
0840:
0841: /**
0842: * Converts a code from an arbitrary name to the numerical identifier (the primary key).
0843: * If the supplied code is already a numerical value, then it is returned unchanged.
0844: * If the code is not found in the name column, it is returned unchanged as well so that
0845: * the caller will produces an appropriate "Code not found" error message. If the code
0846: * is found more than once, then an exception is thrown.
0847: * <p>
0848: * Note that this method includes a call to {@link #trimAuthority}, so there is no need to
0849: * call it before or after this method.
0850: *
0851: * @param type The type of object to create.
0852: * @param code The code to check.
0853: * @param table The table where the code should appears.
0854: * @param codeColumn The column name for the code.
0855: * @param nameColumn The column name for the name.
0856: * @return The numerical identifier (i.e. the table primary key value).
0857: * @throws SQLException if an error occured while reading the database.
0858: */
0859: private String toPrimaryKey(final Class type, final String code,
0860: final String table, final String codeColumn,
0861: final String nameColumn) throws SQLException,
0862: FactoryException {
0863: assert Thread.holdsLock(this );
0864: String identifier = trimAuthority(code);
0865: if (!isPrimaryKey(identifier)) {
0866: /*
0867: * The character is not the numerical code. Search the value in the database.
0868: * If a prepared statement is already available, reuse it providing that it was
0869: * created for the current table. Otherwise, we will create a new statement.
0870: */
0871: final String KEY = "NumericalIdentifier";
0872: PreparedStatement statement = (PreparedStatement) statements
0873: .get(KEY);
0874: if (statement != null) {
0875: if (!table.equals(lastTableForName)) {
0876: statements.remove(KEY);
0877: statement.close();
0878: statement = null;
0879: lastTableForName = null;
0880: }
0881: }
0882: if (statement == null) {
0883: final String query = "SELECT " + codeColumn + " FROM "
0884: + table + " WHERE " + nameColumn + " = ?";
0885: statement = connection
0886: .prepareStatement(adaptSQL(query));
0887: statements.put(KEY, statement);
0888: }
0889: statement.setString(1, identifier);
0890: identifier = null;
0891: final ResultSet result = statement.executeQuery();
0892: while (result.next()) {
0893: identifier = (String) ensureSingleton(result
0894: .getString(1), identifier, code);
0895: }
0896: result.close();
0897: if (identifier == null) {
0898: throw noSuchAuthorityCode(type, code);
0899: }
0900: }
0901: return identifier;
0902: }
0903:
0904: /**
0905: * Make sure that an object constructed from the database is not incoherent.
0906: * If the code supplied to a {@code createFoo} method exists in the database,
0907: * then we should find only one record. However, we will do a paranoiac check and
0908: * verify if there is more records, using a {@code while (results.next())}
0909: * loop instead of {@code if (results.next())}. This method is invoked in
0910: * the loop for making sure that, if there is more than one record (which should
0911: * never happen), at least they have identical contents.
0912: *
0913: * @param newValue The newly constructed object.
0914: * @param oldValue The object previously constructed, or {@code null} if none.
0915: * @param code The EPSG code (for formatting error message).
0916: * @throws FactoryException if a duplication has been detected.
0917: *
0918: * @todo Use generic type when we will be allowed to compile for J2SE 1.5.
0919: */
0920: private static Object ensureSingleton(final Object newValue,
0921: final Object oldValue, final String code)
0922: throws FactoryException {
0923: if (oldValue == null) {
0924: return newValue;
0925: }
0926: if (oldValue.equals(newValue)) {
0927: return oldValue;
0928: }
0929: throw new FactoryException(Errors.format(
0930: ErrorKeys.DUPLICATED_VALUES_$1, code));
0931: }
0932:
0933: /**
0934: * Returns the name for the {@link IdentifiedObject} to construct.
0935: * This method also search for alias.
0936: *
0937: * @param name The name for the {@link IndentifiedObject} to construct.
0938: * @param code The EPSG code of the object to construct.
0939: * @param remarks Remarks, or {@code null} if none.
0940: * @return The name together with a set of properties.
0941: */
0942: private Map createProperties(final String name, final String code,
0943: String remarks) throws SQLException, FactoryException {
0944: properties.clear();
0945: final Citation authority = getAuthority();
0946: if (name != null) {
0947: properties.put(IdentifiedObject.NAME_KEY,
0948: new NamedIdentifier(authority, name.trim()));
0949: }
0950: if (code != null) {
0951: final InternationalString edition = authority.getEdition();
0952: final String version = (edition != null) ? edition
0953: .toString() : null;
0954: properties
0955: .put(IdentifiedObject.IDENTIFIERS_KEY,
0956: new NamedIdentifier(authority, code.trim(),
0957: version));
0958: }
0959: if (remarks != null && (remarks = remarks.trim()).length() != 0) {
0960: properties.put(IdentifiedObject.REMARKS_KEY, remarks);
0961: }
0962: /*
0963: * Search for alias.
0964: */
0965: List alias = null;
0966: final PreparedStatement stmt;
0967: stmt = prepareStatement("Alias",
0968: "SELECT NAMING_SYSTEM_NAME, ALIAS"
0969: + " FROM [Alias] INNER JOIN [Naming System]"
0970: + " ON [Alias].NAMING_SYSTEM_CODE ="
0971: + " [Naming System].NAMING_SYSTEM_CODE"
0972: + " WHERE OBJECT_CODE = ?");
0973: stmt.setString(1, code);
0974: final ResultSet result = stmt.executeQuery();
0975: while (result.next()) {
0976: final String scope = result.getString(1);
0977: final String local = getString(result, 2, code);
0978: final GenericName generic;
0979: if (scope == null) {
0980: generic = new LocalName(local);
0981: } else {
0982: LocalName cached = (LocalName) scopes.get(scope);
0983: if (cached == null) {
0984: cached = new LocalName(scope);
0985: scopes.put(scope, cached);
0986: }
0987: generic = new ScopedName(cached, local);
0988: }
0989: if (alias == null) {
0990: alias = new ArrayList();
0991: }
0992: alias.add(generic);
0993: }
0994: result.close();
0995: if (alias != null) {
0996: properties.put(IdentifiedObject.ALIAS_KEY,
0997: (GenericName[]) alias.toArray(new GenericName[alias
0998: .size()]));
0999: }
1000: return properties;
1001: }
1002:
1003: /**
1004: * Returns the name for the {@link IdentifiedObject} to construct.
1005: * This method also search for alias.
1006: *
1007: * @param name The name for the {@link IndentifiedObject} to construct.
1008: * @param code The EPSG code of the object to construct.
1009: * @param area The area of use, or {@code null} if none.
1010: * @param scope The scope, or {@code null} if none.
1011: * @param remarks Remarks, or {@code null} if none.
1012: * @return The name together with a set of properties.
1013: */
1014: private Map createProperties(final String name, final String code,
1015: String area, String scope, String remarks)
1016: throws SQLException, FactoryException {
1017: final Map properties = createProperties(name, code, remarks);
1018: if (area != null && (area = area.trim()).length() != 0) {
1019: final Extent extent = buffered.createExtent(area);
1020: properties.put(Datum.DOMAIN_OF_VALIDITY_KEY, extent);
1021: }
1022: if (scope != null && (scope = scope.trim()).length() != 0) {
1023: properties.put(Datum.SCOPE_KEY, scope);
1024: }
1025: return properties;
1026: }
1027:
1028: /**
1029: * Returns an arbitrary object from a code.
1030: * The default implementation invokes one of {@link #createCoordinateReferenceSystem},
1031: * {@link #createCoordinateSystem}, {@link #createDatum}, {@link #createEllipsoid}, or
1032: * {@link #createUnit} methods according the object type.
1033: *
1034: * @param code The EPSG value.
1035: * @return The object.
1036: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1037: * @throws FactoryException if some other kind of failure occured in the backing
1038: * store. This exception usually have {@link SQLException} as its cause.
1039: */
1040: public synchronized IdentifiedObject createObject(final String code)
1041: throws FactoryException {
1042: ensureNonNull("code", code);
1043: final String KEY = "IdentifiedObject";
1044: PreparedStatement stmt = (PreparedStatement) statements
1045: .get(KEY); // Null allowed.
1046: StringBuffer query = null; // Will be created only if the last statement doesn't suit.
1047: /*
1048: * Iterates through all tables listed in TABLES_INFO, starting with the table used during
1049: * the last call to 'createObject(code)'. This approach assumes that two consecutive calls
1050: * will often return the same type of object. If the object type changed, then this method
1051: * will have to discard the old prepared statement and prepare a new one, which may be a
1052: * costly operation. Only the last successful prepared statement is cached, in order to keep
1053: * the amount of statements low. Unsuccessful statements are immediately disposed.
1054: */
1055: final String epsg = trimAuthority(code);
1056: final boolean isPrimaryKey = isPrimaryKey(epsg);
1057: final int tupleToSkip = isPrimaryKey ? lastObjectType : -1;
1058: int index = -1;
1059: for (int i = -1; i < TABLES_INFO.length; i++) {
1060: if (i == tupleToSkip) {
1061: // Avoid to test the same table twice. Note that this test also avoid a
1062: // NullPointerException if 'stmt' is null, since 'lastObjectType' should
1063: // be -1 in this case.
1064: continue;
1065: }
1066: try {
1067: if (i >= 0) {
1068: final TableInfo table = TABLES_INFO[i];
1069: final String column = isPrimaryKey ? table.codeColumn
1070: : table.nameColumn;
1071: if (column == null) {
1072: continue;
1073: }
1074: if (query == null) {
1075: query = new StringBuffer("SELECT ");
1076: }
1077: query.setLength(7); // 7 is the length of "SELECT " in the line above.
1078: query.append(table.codeColumn);
1079: query.append(" FROM ");
1080: query.append(table.table);
1081: query.append(" WHERE ");
1082: query.append(column);
1083: query.append(" = ?");
1084: if (isPrimaryKey) {
1085: assert !statements.containsKey(KEY) : table;
1086: stmt = prepareStatement(KEY, query.toString());
1087: } else {
1088: // Do not cache the statement for names.
1089: stmt = connection
1090: .prepareStatement(adaptSQL(query
1091: .toString()));
1092: }
1093: }
1094: /*
1095: * Checks if at least one record is found for the code. If the code is the primary
1096: * key, then we will stop at the first table found since a well-formed EPSG database
1097: * should not contains any duplicate identifiers. In the code is a name, then search
1098: * in all tables since duplicate names exist.
1099: */
1100: stmt.setString(1, epsg);
1101: final ResultSet result = stmt.executeQuery();
1102: final boolean present = result.next();
1103: result.close();
1104: if (present) {
1105: if (index >= 0) {
1106: throw new FactoryException(Errors.format(
1107: ErrorKeys.DUPLICATED_VALUES_$1, code));
1108: }
1109: index = (i < 0) ? lastObjectType : i;
1110: if (isPrimaryKey) {
1111: // Don't scan other tables, since primary keys should be unique.
1112: // Note that names may be duplicated, so we don't stop for names.
1113: break;
1114: }
1115: }
1116: if (isPrimaryKey) {
1117: if (statements.remove(KEY) == null) {
1118: throw new AssertionError(code); // Should never happen.
1119: }
1120: }
1121: stmt.close();
1122: } catch (SQLException exception) {
1123: throw databaseFailure(IdentifiedObject.class, code,
1124: exception);
1125: }
1126: }
1127: /*
1128: * If a record has been found in one table, then delegates to the appropriate method.
1129: */
1130: if (isPrimaryKey) {
1131: lastObjectType = index;
1132: }
1133: if (index >= 0) {
1134: switch (index) {
1135: case 0:
1136: return buffered.createCoordinateReferenceSystem(code);
1137: case 1:
1138: return buffered.createCoordinateSystem(code);
1139: case 2:
1140: return buffered.createCoordinateSystemAxis(code);
1141: case 3:
1142: return buffered.createDatum(code);
1143: case 4:
1144: return buffered.createEllipsoid(code);
1145: case 5:
1146: return buffered.createPrimeMeridian(code);
1147: case 6:
1148: return buffered.createCoordinateOperation(code);
1149: case 7:
1150: return buffered.createOperationMethod(code);
1151: case 8:
1152: return buffered.createParameterDescriptor(code);
1153: case 9:
1154: break; // Can't cast Unit to IdentifiedObject
1155: default:
1156: throw new AssertionError(index); // Should not happen
1157: }
1158: }
1159: return super .createObject(code);
1160: }
1161:
1162: /**
1163: * Returns an unit from a code.
1164: *
1165: * @param code Value allocated by authority.
1166: * @return The unit object.
1167: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1168: * @throws FactoryException if some other kind of failure occured in the backing
1169: * store. This exception usually have {@link SQLException} as its cause.
1170: */
1171: public synchronized Unit createUnit(final String code)
1172: throws FactoryException {
1173: ensureNonNull("code", code);
1174: Unit returnValue = null;
1175: try {
1176: final String primaryKey = toPrimaryKey(Unit.class, code,
1177: "[Unit of Measure]", "UOM_CODE",
1178: "UNIT_OF_MEAS_NAME");
1179: final PreparedStatement stmt;
1180: stmt = prepareStatement("Unit", "SELECT UOM_CODE,"
1181: + " FACTOR_B," + " FACTOR_C," + " TARGET_UOM_CODE"
1182: + " FROM [Unit of Measure]" + " WHERE UOM_CODE = ?");
1183: stmt.setString(1, primaryKey);
1184: final ResultSet result = stmt.executeQuery();
1185: while (result.next()) {
1186: final int source = getInt(result, 1, code);
1187: final double b = result.getDouble(2);
1188: final double c = result.getDouble(3);
1189: final int target = getInt(result, 4, code);
1190: final Unit base = getUnit(target);
1191: if (base == null) {
1192: throw noSuchAuthorityCode(Unit.class, String
1193: .valueOf(target));
1194: }
1195: Unit unit = getUnit(source);
1196: if (unit != null) {
1197: // TODO: check unit consistency here.
1198: } else if (b != 0 && c != 0) {
1199: unit = (b == c) ? base : base.multiply(b / c);
1200: } else {
1201: // TODO: provide a localized message.
1202: throw new FactoryException("Unsupported unit: "
1203: + code);
1204: }
1205: returnValue = (Unit) ensureSingleton(unit, returnValue,
1206: code);
1207: }
1208: result.close();
1209: } catch (SQLException exception) {
1210: throw databaseFailure(Unit.class, code, exception);
1211: }
1212: if (returnValue == null) {
1213: throw noSuchAuthorityCode(Unit.class, code);
1214: }
1215: return returnValue;
1216: }
1217:
1218: /**
1219: * Returns an ellipsoid from a code.
1220: *
1221: * @param code The EPSG value.
1222: * @return The ellipsoid object.
1223: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1224: * @throws FactoryException if some other kind of failure occured in the backing
1225: * store. This exception usually have {@link SQLException} as its cause.
1226: */
1227: public synchronized Ellipsoid createEllipsoid(final String code)
1228: throws FactoryException {
1229: ensureNonNull("code", code);
1230: Ellipsoid returnValue = null;
1231: try {
1232: final String primaryKey = toPrimaryKey(Ellipsoid.class,
1233: code, "[Ellipsoid]", "ELLIPSOID_CODE",
1234: "ELLIPSOID_NAME");
1235: final PreparedStatement stmt;
1236: stmt = prepareStatement("Ellipsoid",
1237: "SELECT ELLIPSOID_CODE," + " ELLIPSOID_NAME,"
1238: + " SEMI_MAJOR_AXIS," + " INV_FLATTENING,"
1239: + " SEMI_MINOR_AXIS," + " UOM_CODE,"
1240: + " REMARKS" + " FROM [Ellipsoid]"
1241: + " WHERE ELLIPSOID_CODE = ?");
1242: stmt.setString(1, primaryKey);
1243: final ResultSet result = stmt.executeQuery();
1244: while (result.next()) {
1245: /*
1246: * One of 'semiMinorAxis' and 'inverseFlattening' values can be NULL in
1247: * the database. Consequently, we don't use 'getString(ResultSet, int)'
1248: * because we don't want to thrown an exception if a NULL value is found.
1249: */
1250: final String epsg = getString(result, 1, code);
1251: final String name = getString(result, 2, code);
1252: final double semiMajorAxis = getDouble(result, 3, code);
1253: final double inverseFlattening = result.getDouble(4);
1254: final double semiMinorAxis = result.getDouble(5);
1255: final String unitCode = getString(result, 6, code);
1256: final String remarks = result.getString(7);
1257: final Unit unit = buffered.createUnit(unitCode);
1258: final Map properties = createProperties(name, epsg,
1259: remarks);
1260: final Ellipsoid ellipsoid;
1261: if (inverseFlattening == 0) {
1262: if (semiMinorAxis == 0) {
1263: // Both are null, which is not allowed.
1264: final String column = result.getMetaData()
1265: .getColumnName(3);
1266: result.close();
1267: throw new FactoryException(Errors.format(
1268: ErrorKeys.NULL_VALUE_IN_TABLE_$3, code,
1269: column));
1270: } else {
1271: // We only have semiMinorAxis defined -> it's OK
1272: ellipsoid = factories.getDatumFactory()
1273: .createEllipsoid(properties,
1274: semiMajorAxis, semiMinorAxis,
1275: unit);
1276: }
1277: } else {
1278: if (semiMinorAxis != 0) {
1279: // Both 'inverseFlattening' and 'semiMinorAxis' are defined.
1280: // Log a warning and create the ellipsoid using the inverse flattening.
1281: LOGGER.log(Logging.format(Level.WARNING,
1282: LoggingKeys.AMBIGUOUS_ELLIPSOID));
1283: }
1284: ellipsoid = factories.getDatumFactory()
1285: .createFlattenedSphere(properties,
1286: semiMajorAxis, inverseFlattening,
1287: unit);
1288: }
1289: /*
1290: * Now that we have built an ellipsoid, compare
1291: * it with the previous one (if any).
1292: */
1293: returnValue = (Ellipsoid) ensureSingleton(ellipsoid,
1294: returnValue, code);
1295: }
1296: result.close();
1297: } catch (SQLException exception) {
1298: throw databaseFailure(Ellipsoid.class, code, exception);
1299: }
1300: if (returnValue == null) {
1301: throw noSuchAuthorityCode(Ellipsoid.class, code);
1302: }
1303: return returnValue;
1304: }
1305:
1306: /**
1307: * Returns a prime meridian, relative to Greenwich.
1308: *
1309: * @param code Value allocated by authority.
1310: * @return The prime meridian object.
1311: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1312: * @throws FactoryException if some other kind of failure occured in the backing
1313: * store. This exception usually have {@link SQLException} as its cause.
1314: */
1315: public synchronized PrimeMeridian createPrimeMeridian(
1316: final String code) throws FactoryException {
1317: ensureNonNull("code", code);
1318: PrimeMeridian returnValue = null;
1319: try {
1320: final String primaryKey = toPrimaryKey(PrimeMeridian.class,
1321: code, "[Prime Meridian]", "PRIME_MERIDIAN_CODE",
1322: "PRIME_MERIDIAN_NAME");
1323: final PreparedStatement stmt;
1324: stmt = prepareStatement("PrimeMeridian",
1325: "SELECT PRIME_MERIDIAN_CODE,"
1326: + " PRIME_MERIDIAN_NAME,"
1327: + " GREENWICH_LONGITUDE," + " UOM_CODE,"
1328: + " REMARKS" + " FROM [Prime Meridian]"
1329: + " WHERE PRIME_MERIDIAN_CODE = ?");
1330: stmt.setString(1, primaryKey);
1331: final ResultSet result = stmt.executeQuery();
1332: while (result.next()) {
1333: final String epsg = getString(result, 1, code);
1334: final String name = getString(result, 2, code);
1335: final double longitude = getDouble(result, 3, code);
1336: final String unit_code = getString(result, 4, code);
1337: final String remarks = result.getString(5);
1338: final Unit unit = buffered.createUnit(unit_code);
1339: final Map properties = createProperties(name, epsg,
1340: remarks);
1341: PrimeMeridian primeMeridian = factories
1342: .getDatumFactory().createPrimeMeridian(
1343: properties, longitude, unit);
1344: returnValue = (PrimeMeridian) ensureSingleton(
1345: primeMeridian, returnValue, code);
1346: }
1347: result.close();
1348: } catch (SQLException exception) {
1349: throw databaseFailure(PrimeMeridian.class, code, exception);
1350: }
1351: if (returnValue == null) {
1352: throw noSuchAuthorityCode(PrimeMeridian.class, code);
1353: }
1354: return returnValue;
1355: }
1356:
1357: /**
1358: * Returns an area of use.
1359: *
1360: * @param code Value allocated by authority.
1361: * @return The area of use.
1362: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1363: * @throws FactoryException if some other kind of failure occured in the backing
1364: * store. This exception usually have {@link SQLException} as its cause.
1365: */
1366: public synchronized Extent createExtent(final String code)
1367: throws FactoryException {
1368: ensureNonNull("code", code);
1369: Extent returnValue = null;
1370: try {
1371: final String primaryKey = toPrimaryKey(Extent.class, code,
1372: "[Area]", "AREA_CODE", "AREA_NAME");
1373: final PreparedStatement stmt;
1374: stmt = prepareStatement("Area", "SELECT AREA_OF_USE,"
1375: + " AREA_SOUTH_BOUND_LAT,"
1376: + " AREA_NORTH_BOUND_LAT,"
1377: + " AREA_WEST_BOUND_LON," + " AREA_EAST_BOUND_LON"
1378: + " FROM [Area]" + " WHERE AREA_CODE = ?");
1379: stmt.setString(1, primaryKey);
1380: final ResultSet result = stmt.executeQuery();
1381: while (result.next()) {
1382: ExtentImpl extent = null;
1383: final String description = result.getString(1);
1384: if (description != null) {
1385: extent = new ExtentImpl();
1386: extent
1387: .setDescription(new SimpleInternationalString(
1388: description));
1389: }
1390: final double ymin = result.getDouble(2);
1391: if (!result.wasNull()) {
1392: final double ymax = result.getDouble(3);
1393: if (!result.wasNull()) {
1394: final double xmin = result.getDouble(4);
1395: if (!result.wasNull()) {
1396: final double xmax = result.getDouble(5);
1397: if (!result.wasNull()) {
1398: if (extent == null) {
1399: extent = new ExtentImpl();
1400: }
1401: extent
1402: .setGeographicElements(Collections
1403: .singleton(new GeographicBoundingBoxImpl(
1404: xmin, xmax,
1405: ymin, ymax)));
1406: }
1407: }
1408: }
1409: }
1410: if (extent != null) {
1411: returnValue = (Extent) ensureSingleton(extent
1412: .unmodifiable(), returnValue, code);
1413: }
1414: }
1415: result.close();
1416: } catch (SQLException exception) {
1417: throw databaseFailure(Extent.class, code, exception);
1418: }
1419: if (returnValue == null) {
1420: throw noSuchAuthorityCode(Extent.class, code);
1421: }
1422: return returnValue;
1423: }
1424:
1425: /**
1426: * Returns Bursa-Wolf parameters for a geodetic datum. If the specified datum has
1427: * no conversion informations, then this method will returns {@code null}.
1428: *
1429: * @param code The EPSG code of the {@link GeodeticDatum}.
1430: * @param toClose The result set to close if this method is going to invokes
1431: * {@link #createDatum} recursively. This hack is necessary because many
1432: * JDBC drivers do not support multiple result sets for the same statement.
1433: * The result set is closed if an only if this method returns a non-null value.
1434: * @return an array of Bursa-Wolf parameters (in which case {@code toClose} has
1435: * been closed), or {@code null} (in which case {@code toClose} has
1436: * <strong>not</strong> been closed).
1437: */
1438: private BursaWolfParameters[] createBursaWolfParameters(
1439: final String code, final ResultSet toClose)
1440: throws SQLException, FactoryException {
1441: if (safetyGuard.contains(code)) {
1442: /*
1443: * Do not try to create Bursa-Wolf parameters if the datum is already
1444: * in process of being created. This check avoid never-ending loops in
1445: * recursive call to 'createDatum'.
1446: */
1447: return null;
1448: }
1449: PreparedStatement stmt;
1450: stmt = prepareStatement(
1451: "BursaWolfParametersSet",
1452: "SELECT CO.COORD_OP_CODE,"
1453: + " CO.COORD_OP_METHOD_CODE,"
1454: + " CRS2.DATUM_CODE"
1455: + " FROM [Coordinate_Operation] AS CO"
1456: + " INNER JOIN [Coordinate Reference System] AS CRS2"
1457: + " ON CO.TARGET_CRS_CODE = CRS2.COORD_REF_SYS_CODE"
1458: + " WHERE CO.COORD_OP_METHOD_CODE >= "
1459: + BURSA_WOLF_MIN_CODE
1460: + " AND CO.COORD_OP_METHOD_CODE <= "
1461: + BURSA_WOLF_MAX_CODE
1462: + " AND CO.COORD_OP_CODE <> "
1463: + DUMMY_OPERATION // GEOT-1008
1464: + " AND CO.SOURCE_CRS_CODE IN ("
1465: + " SELECT CRS1.COORD_REF_SYS_CODE " // GEOT-1129
1466: + " FROM [Coordinate Reference System] AS CRS1 "
1467: + " WHERE CRS1.DATUM_CODE = ?)"
1468: + " ORDER BY CRS2.DATUM_CODE,"
1469: + " ABS(CO.DEPRECATED), CO.COORD_OP_ACCURACY,"
1470: + " CO.COORD_OP_CODE DESC"); // GEOT-846 fix
1471: stmt.setString(1, code);
1472: ResultSet result = stmt.executeQuery();
1473: List bwInfos = null;
1474: while (result.next()) {
1475: final String operation = getString(result, 1, code);
1476: final int method = getInt(result, 2, code);
1477: final String datum = getString(result, 3, code);
1478: if (bwInfos == null) {
1479: bwInfos = new ArrayList();
1480: }
1481: bwInfos.add(new BursaWolfInfo(operation, method, datum));
1482: }
1483: result.close();
1484: if (bwInfos == null) {
1485: // Don't close the connection here.
1486: return null;
1487: }
1488: toClose.close();
1489: /*
1490: * Sorts the infos in preference order. The "ORDER BY" clause above was not enough;
1491: * we also need to take the "supersession" table in account. Once the sorting is done,
1492: * keep only one Bursa-Wolf parameters for each datum.
1493: */
1494: int size = bwInfos.size();
1495: if (size > 1) {
1496: final BursaWolfInfo[] codes = (BursaWolfInfo[]) bwInfos
1497: .toArray(new BursaWolfInfo[size]);
1498: sort(codes);
1499: bwInfos.clear();
1500: final Set added = new HashSet();
1501: for (int i = 0; i < codes.length; i++) {
1502: final BursaWolfInfo candidate = codes[i];
1503: if (added.add(candidate.target)) {
1504: bwInfos.add(candidate);
1505: }
1506: }
1507: size = bwInfos.size();
1508: }
1509: /*
1510: * We got all the needed informations before to built Bursa-Wolf parameters because the
1511: * 'createDatum(...)' call below may invokes 'createBursaWolfParameters(...)' recursively,
1512: * and not all JDBC drivers supported multi-result set for the same statement. Now, iterate
1513: * throw the results and fetch the parameter values for each BursaWolfParameters object.
1514: */
1515: stmt = prepareStatement(
1516: "BursaWolfParameters",
1517: "SELECT PARAMETER_CODE,"
1518: + " PARAMETER_VALUE,"
1519: + " UOM_CODE"
1520: + " FROM [Coordinate_Operation Parameter Value]"
1521: + " WHERE COORD_OP_CODE = ?"
1522: + " AND COORD_OP_METHOD_CODE = ?");
1523: for (int i = 0; i < size; i++) {
1524: final BursaWolfInfo info = (BursaWolfInfo) bwInfos.get(i);
1525: final GeodeticDatum datum;
1526: try {
1527: safetyGuard.add(code);
1528: datum = buffered.createGeodeticDatum(info.target);
1529: } finally {
1530: safetyGuard.remove(code);
1531: }
1532: final BursaWolfParameters parameters = new BursaWolfParameters(
1533: datum);
1534: stmt.setString(1, info.operation);
1535: stmt.setInt(2, info.method);
1536: result = stmt.executeQuery();
1537: while (result.next()) {
1538: setBursaWolfParameter(parameters, getInt(result, 1,
1539: info.operation), getDouble(result, 2,
1540: info.operation), buffered.createUnit(getString(
1541: result, 3, info.operation)));
1542: }
1543: result.close();
1544: if (info.method == ROTATION_FRAME_CODE) {
1545: // Coordinate frame rotation (9607): same as 9606,
1546: // except for the sign of rotation parameters.
1547: parameters.ex = -parameters.ex;
1548: parameters.ey = -parameters.ey;
1549: parameters.ey = -parameters.ey;
1550: }
1551: bwInfos.set(i, parameters);
1552: }
1553: return (BursaWolfParameters[]) bwInfos
1554: .toArray(new BursaWolfParameters[size]);
1555: }
1556:
1557: /**
1558: * Returns a datum from a code.
1559: *
1560: * @param code Value allocated by authority.
1561: * @return The datum object.
1562: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1563: * @throws FactoryException if some other kind of failure occured in the backing
1564: * store. This exception usually have {@link SQLException} as its cause.
1565: *
1566: * @todo Current implementation maps all "vertical" datum to
1567: * {@link VerticalDatumType#GEOIDAL}. We don't know yet how
1568: * to maps the exact vertical datum type from the EPSG database.
1569: */
1570: public synchronized Datum createDatum(final String code)
1571: throws FactoryException {
1572: ensureNonNull("code", code);
1573: Datum returnValue = null;
1574: try {
1575: final String primaryKey = toPrimaryKey(Datum.class, code,
1576: "[Datum]", "DATUM_CODE", "DATUM_NAME");
1577: final PreparedStatement stmt;
1578: stmt = prepareStatement("Datum", "SELECT DATUM_CODE,"
1579: + " DATUM_NAME," + " DATUM_TYPE,"
1580: + " ORIGIN_DESCRIPTION," + " REALIZATION_EPOCH,"
1581: + " AREA_OF_USE_CODE," + " DATUM_SCOPE,"
1582: + " REMARKS," + " ELLIPSOID_CODE," // Only for geodetic type
1583: + " PRIME_MERIDIAN_CODE" // Only for geodetic type
1584: + " FROM [Datum]" + " WHERE DATUM_CODE = ?");
1585: stmt.setString(1, primaryKey);
1586: ResultSet result = stmt.executeQuery();
1587: while (result.next()) {
1588: final String epsg = getString(result, 1, code);
1589: final String name = getString(result, 2, code);
1590: final String type = getString(result, 3, code).trim()
1591: .toLowerCase();
1592: final String anchor = result.getString(4);
1593: final String epoch = result.getString(5);
1594: final String area = result.getString(6);
1595: final String scope = result.getString(7);
1596: final String remarks = result.getString(8);
1597: Map properties = createProperties(name, epsg, area,
1598: scope, remarks);
1599: if (anchor != null) {
1600: properties.put(Datum.ANCHOR_POINT_KEY, anchor);
1601: }
1602: if (epoch != null && epoch.length() != 0)
1603: try {
1604: calendar.clear();
1605: calendar.set(Integer.parseInt(epoch), 0, 1);
1606: properties.put(Datum.REALIZATION_EPOCH_KEY,
1607: calendar.getTime());
1608: } catch (NumberFormatException exception) {
1609: // Not a fatal error...
1610: org.geotools.util.logging.Logging
1611: .unexpectedException(LOGGER.getName(),
1612: DirectEpsgFactory.class,
1613: "createDatum", exception);
1614: }
1615: final DatumFactory factory = factories
1616: .getDatumFactory();
1617: final Datum datum;
1618: /*
1619: * Now build datum according their datum type. Constructions are straightforward,
1620: * except for the "geodetic" datum type which need some special processing:
1621: *
1622: * - Because it invokes again 'createProperties' indirectly (through calls to
1623: * 'createEllipsoid' and 'createPrimeMeridian'), it must protect 'properties'
1624: * from changes.
1625: *
1626: * - Because 'createBursaWolfParameters' may invokes 'createDatum' recursively,
1627: * we must close the result set if Bursa-Wolf parameters are found. In this
1628: * case, we lost our paranoiac check for duplication.
1629: */
1630: if (type.equals("geodetic")) {
1631: properties = new HashMap(properties); // Protect from changes
1632: final Ellipsoid ellipsoid = buffered
1633: .createEllipsoid(getString(result, 9, code));
1634: final PrimeMeridian meridian = buffered
1635: .createPrimeMeridian(getString(result, 10,
1636: code));
1637: final BursaWolfParameters[] param = createBursaWolfParameters(
1638: primaryKey, result);
1639: if (param != null) {
1640: result = null; // Already closed by createBursaWolfParameters
1641: properties.put(
1642: DefaultGeodeticDatum.BURSA_WOLF_KEY,
1643: param);
1644: }
1645: datum = factory.createGeodeticDatum(properties,
1646: ellipsoid, meridian);
1647: } else if (type.equals("vertical")) {
1648: // TODO: Find the right datum type.
1649: datum = factory.createVerticalDatum(properties,
1650: VerticalDatumType.GEOIDAL);
1651: } else if (type.equals("engineering")) {
1652: datum = factory.createEngineeringDatum(properties);
1653: } else {
1654: result.close();
1655: throw new FactoryException(Errors.format(
1656: ErrorKeys.UNKNOW_TYPE_$1, type));
1657: }
1658: returnValue = (Datum) ensureSingleton(datum,
1659: returnValue, code);
1660: if (result == null) {
1661: // Bypass the 'result.close()' line below:
1662: // the ResultSet has already been closed.
1663: return returnValue;
1664: }
1665: }
1666: result.close();
1667: } catch (SQLException exception) {
1668: throw databaseFailure(Datum.class, code, exception);
1669: }
1670: if (returnValue == null) {
1671: throw noSuchAuthorityCode(Datum.class, code);
1672: }
1673: return returnValue;
1674: }
1675:
1676: /**
1677: * Returns the name and description for the specified {@linkplain CoordinateSystemAxis
1678: * coordinate system axis} code. Many axis share the same name and description, so it
1679: * is worth to cache them.
1680: */
1681: private AxisName getAxisName(final String code)
1682: throws FactoryException {
1683: assert Thread.holdsLock(this );
1684: AxisName returnValue = (AxisName) axisNames.get(code);
1685: if (returnValue == null)
1686: try {
1687: final PreparedStatement stmt;
1688: stmt = prepareStatement("AxisName",
1689: "SELECT COORD_AXIS_NAME, DESCRIPTION, REMARKS"
1690: + " FROM [Coordinate Axis Name]"
1691: + " WHERE COORD_AXIS_NAME_CODE = ?");
1692: stmt.setString(1, code);
1693: ResultSet result = stmt.executeQuery();
1694: while (result.next()) {
1695: final String name = getString(result, 1, code);
1696: String description = result.getString(2);
1697: String remarks = result.getString(3);
1698: if (description == null) {
1699: description = remarks;
1700: } else if (remarks != null) {
1701: description += System.getProperty(
1702: "line.separator", "\n")
1703: + remarks;
1704: }
1705: final AxisName axis = new AxisName(name,
1706: description);
1707: returnValue = (AxisName) ensureSingleton(axis,
1708: returnValue, code);
1709: }
1710: result.close();
1711: if (returnValue == null) {
1712: throw noSuchAuthorityCode(AxisName.class, code);
1713: }
1714: axisNames.put(code, returnValue);
1715: } catch (SQLException exception) {
1716: throw databaseFailure(AxisName.class, code, exception);
1717: }
1718: return returnValue;
1719: }
1720:
1721: /**
1722: * Returns a {@linkplain CoordinateSystemAxis coordinate system axis} from a code.
1723: *
1724: * @param code Value allocated by authority.
1725: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
1726: * @throws FactoryException if the object creation failed for some other reason.
1727: */
1728: public synchronized CoordinateSystemAxis createCoordinateSystemAxis(
1729: final String code) throws FactoryException {
1730: ensureNonNull("code", code);
1731: CoordinateSystemAxis returnValue = null;
1732: try {
1733: final String primaryKey = trimAuthority(code);
1734: final PreparedStatement stmt;
1735: stmt = prepareStatement("Axis", "SELECT COORD_AXIS_CODE,"
1736: + " COORD_AXIS_NAME_CODE,"
1737: + " COORD_AXIS_ORIENTATION,"
1738: + " COORD_AXIS_ABBREVIATION," + " UOM_CODE"
1739: + " FROM [Coordinate Axis]"
1740: + " WHERE COORD_AXIS_CODE = ?");
1741: stmt.setString(1, code);
1742: ResultSet result = stmt.executeQuery();
1743: while (result.next()) {
1744: final String epsg = getString(result, 1, code);
1745: final String nameCode = getString(result, 2, code);
1746: final String orientation = getString(result, 3, code);
1747: final String abbreviation = getString(result, 4, code);
1748: final String unit = getString(result, 5, code);
1749: AxisDirection direction;
1750: try {
1751: direction = DefaultCoordinateSystemAxis
1752: .getDirection(orientation);
1753: } catch (NoSuchElementException exception) {
1754: if (orientation
1755: .equalsIgnoreCase("Geocentre > equator/PM")) {
1756: direction = AxisDirection.OTHER; // TODO: can we choose a more accurate direction?
1757: } else if (orientation
1758: .equalsIgnoreCase("Geocentre > equator/90dE")) {
1759: direction = AxisDirection.EAST;
1760: } else if (orientation
1761: .equalsIgnoreCase("Geocentre > north pole")) {
1762: direction = AxisDirection.NORTH;
1763: } else {
1764: throw new FactoryException(exception);
1765: }
1766: }
1767: final AxisName an = getAxisName(nameCode);
1768: final Map properties = createProperties(an.name, epsg,
1769: an.description);
1770: final CSFactory factory = factories.getCSFactory();
1771: final CoordinateSystemAxis axis = factory
1772: .createCoordinateSystemAxis(properties,
1773: abbreviation, direction, buffered
1774: .createUnit(unit));
1775: returnValue = (CoordinateSystemAxis) ensureSingleton(
1776: axis, returnValue, code);
1777: }
1778: result.close();
1779: } catch (SQLException exception) {
1780: throw databaseFailure(CoordinateSystemAxis.class, code,
1781: exception);
1782: }
1783: if (returnValue == null) {
1784: throw noSuchAuthorityCode(CoordinateSystemAxis.class, code);
1785: }
1786: return returnValue;
1787: }
1788:
1789: /**
1790: * Returns the coordinate system axis from an EPSG code for a {@link CoordinateSystem}.
1791: * <p>
1792: * <strong>WARNING:</strong> The EPSG database uses "{@code ORDER}" as a column name.
1793: * This is tolerated by Access, but MySQL doesn't accept this name.
1794: *
1795: * @param code the EPSG code for coordinate system owner.
1796: * @param dimension of the coordinate system, which is also the size of the returned array.
1797: * @return An array of coordinate system axis.
1798: * @throws SQLException if an error occured during database access.
1799: * @throws FactoryException if the code has not been found.
1800: */
1801: private CoordinateSystemAxis[] createAxesForCoordinateSystem(
1802: final String code, final int dimension)
1803: throws SQLException, FactoryException {
1804: assert Thread.holdsLock(this );
1805: final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[dimension];
1806: final PreparedStatement stmt;
1807: stmt = prepareStatement("AxisOrder", "SELECT COORD_AXIS_CODE"
1808: + " FROM [Coordinate Axis]"
1809: + " WHERE COORD_SYS_CODE = ?" + " ORDER BY [ORDER]");
1810: // WARNING: Be careful about the column name :
1811: // MySQL rejects ORDER as a column name !!!
1812: stmt.setString(1, code);
1813: final ResultSet result = stmt.executeQuery();
1814: int i = 0;
1815: while (result.next()) {
1816: final String axisCode = getString(result, 1, code);
1817: if (i < axis.length) {
1818: // If 'i' is out of bounds, an exception will be thrown after the loop.
1819: // We don't want to thrown an ArrayIndexOutOfBoundsException here.
1820: axis[i] = buffered.createCoordinateSystemAxis(axisCode);
1821: }
1822: ++i;
1823: }
1824: result.close();
1825: if (i != axis.length) {
1826: throw new FactoryException(Errors.format(
1827: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
1828: axis.length), new Integer(i)));
1829: }
1830: return axis;
1831: }
1832:
1833: /**
1834: * Returns a coordinate system from a code.
1835: *
1836: * @param code Value allocated by authority.
1837: * @return The coordinate system object.
1838: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1839: * @throws FactoryException if some other kind of failure occured in the backing
1840: * store. This exception usually have {@link SQLException} as its cause.
1841: */
1842: public synchronized CoordinateSystem createCoordinateSystem(
1843: final String code) throws FactoryException {
1844: ensureNonNull("code", code);
1845: CoordinateSystem returnValue = null;
1846: final PreparedStatement stmt;
1847: try {
1848: final String primaryKey = toPrimaryKey(
1849: CoordinateSystem.class, code,
1850: "[Coordinate System]", "COORD_SYS_CODE",
1851: "COORD_SYS_NAME");
1852: stmt = prepareStatement("CoordinateSystem",
1853: "SELECT COORD_SYS_CODE," + " COORD_SYS_NAME,"
1854: + " COORD_SYS_TYPE," + " DIMENSION,"
1855: + " REMARKS" + " FROM [Coordinate System]"
1856: + " WHERE COORD_SYS_CODE = ?");
1857: stmt.setString(1, primaryKey);
1858: final ResultSet result = stmt.executeQuery();
1859: while (result.next()) {
1860: final String epsg = getString(result, 1, code);
1861: final String name = getString(result, 2, code);
1862: final String type = getString(result, 3, code).trim()
1863: .toLowerCase();
1864: final int dimension = getInt(result, 4, code);
1865: final String remarks = result.getString(5);
1866: final CoordinateSystemAxis[] axis = createAxesForCoordinateSystem(
1867: primaryKey, dimension);
1868: final Map properties = createProperties(name, epsg,
1869: remarks); // Must be after axis
1870: final CSFactory factory = factories.getCSFactory();
1871: CoordinateSystem cs = null;
1872: if (type.equals("ellipsoidal")) {
1873: switch (dimension) {
1874: case 2:
1875: cs = factory.createEllipsoidalCS(properties,
1876: axis[0], axis[1]);
1877: break;
1878: case 3:
1879: cs = factory.createEllipsoidalCS(properties,
1880: axis[0], axis[1], axis[2]);
1881: break;
1882: }
1883: } else if (type.equals("cartesian")) {
1884: switch (dimension) {
1885: case 2:
1886: cs = factory.createCartesianCS(properties,
1887: axis[0], axis[1]);
1888: break;
1889: case 3:
1890: cs = factory.createCartesianCS(properties,
1891: axis[0], axis[1], axis[2]);
1892: break;
1893: }
1894: } else if (type.equals("spherical")) {
1895: switch (dimension) {
1896: case 3:
1897: cs = factory.createSphericalCS(properties,
1898: axis[0], axis[1], axis[2]);
1899: break;
1900: }
1901: } else if (type.equals("vertical")
1902: || type.equals("gravity-related")) {
1903: switch (dimension) {
1904: case 1:
1905: cs = factory.createVerticalCS(properties,
1906: axis[0]);
1907: break;
1908: }
1909: } else if (type.equals("linear")) {
1910: switch (dimension) {
1911: case 1:
1912: cs = factory
1913: .createLinearCS(properties, axis[0]);
1914: break;
1915: }
1916: } else if (type.equals("polar")) {
1917: switch (dimension) {
1918: case 2:
1919: cs = factory.createPolarCS(properties, axis[0],
1920: axis[1]);
1921: break;
1922: }
1923: } else if (type.equals("cylindrical")) {
1924: switch (dimension) {
1925: case 3:
1926: cs = factory.createCylindricalCS(properties,
1927: axis[0], axis[1], axis[2]);
1928: break;
1929: }
1930: } else if (type.equals("affine")) {
1931: switch (dimension) {
1932: case 2:
1933: cs = factory.createAffineCS(properties,
1934: axis[0], axis[1]);
1935: break;
1936: case 3:
1937: cs = factory.createAffineCS(properties,
1938: axis[0], axis[1], axis[2]);
1939: break;
1940: }
1941: } else {
1942: result.close();
1943: throw new FactoryException(Errors.format(
1944: ErrorKeys.UNKNOW_TYPE_$1, type));
1945: }
1946: if (cs == null) {
1947: result.close();
1948: throw new FactoryException(Errors.format(
1949: ErrorKeys.UNEXPECTED_DIMENSION_FOR_CS_$1,
1950: type));
1951: }
1952: returnValue = (CoordinateSystem) ensureSingleton(cs,
1953: returnValue, code);
1954: }
1955: result.close();
1956: } catch (SQLException exception) {
1957: throw databaseFailure(CoordinateSystem.class, code,
1958: exception);
1959: }
1960: if (returnValue == null) {
1961: throw noSuchAuthorityCode(CoordinateSystem.class, code);
1962: }
1963: return returnValue;
1964:
1965: }
1966:
1967: /**
1968: * Returns the primary key for a coordinate reference system name.
1969: * This method is used both by {@link #createCoordinateReferenceSystem}
1970: * and {@link #createFromCoordinateReferenceSystemCodes}
1971: */
1972: private String toPrimaryKeyCRS(final String code)
1973: throws SQLException, FactoryException {
1974: return toPrimaryKey(CoordinateReferenceSystem.class, code,
1975: "[Coordinate Reference System]", "COORD_REF_SYS_CODE",
1976: "COORD_REF_SYS_NAME");
1977: }
1978:
1979: /**
1980: * Returns a coordinate reference system from a code.
1981: *
1982: * @param code Value allocated by authority.
1983: * @return The coordinate reference system object.
1984: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
1985: * @throws FactoryException if some other kind of failure occured in the backing
1986: * store. This exception usually have {@link SQLException} as its cause.
1987: */
1988: public synchronized CoordinateReferenceSystem createCoordinateReferenceSystem(
1989: final String code) throws FactoryException {
1990: ensureNonNull("code", code);
1991: CoordinateReferenceSystem returnValue = null;
1992: try {
1993: final String primaryKey = toPrimaryKeyCRS(code);
1994: final PreparedStatement stmt;
1995: stmt = prepareStatement("CoordinateReferenceSystem",
1996: "SELECT COORD_REF_SYS_CODE,"
1997: + " COORD_REF_SYS_NAME,"
1998: + " AREA_OF_USE_CODE," + " CRS_SCOPE,"
1999: + " REMARKS,"
2000: + " COORD_REF_SYS_KIND,"
2001: + " COORD_SYS_CODE," // Null for CompoundCRS
2002: + " DATUM_CODE," // Null for ProjectedCRS
2003: + " SOURCE_GEOGCRS_CODE," // For ProjectedCRS
2004: + " PROJECTION_CONV_CODE," // For ProjectedCRS
2005: + " CMPD_HORIZCRS_CODE," // For CompoundCRS only
2006: + " CMPD_VERTCRS_CODE" // For CompoundCRS only
2007: + " FROM [Coordinate Reference System]"
2008: + " WHERE COORD_REF_SYS_CODE = ?");
2009: stmt.setString(1, primaryKey);
2010: ResultSet result = stmt.executeQuery();
2011: while (result.next()) {
2012: final String epsg = getString(result, 1, code);
2013: final String name = getString(result, 2, code);
2014: final String area = result.getString(3);
2015: final String scope = result.getString(4);
2016: final String remarks = result.getString(5);
2017: final String type = getString(result, 6, code);
2018: // Note: Do not invoke 'createProperties' now, even if we have all required
2019: // informations, because the 'properties' map is going to overwritten
2020: // by calls to 'createDatum', 'createCoordinateSystem', etc.
2021: final CRSFactory factory = factories.getCRSFactory();
2022: final CoordinateReferenceSystem crs;
2023: /* ----------------------------------------------------------------------
2024: * GEOGRAPHIC CRS
2025: *
2026: * NOTE: 'createProperties' MUST be invoked after any call to an other
2027: * 'createFoo' method. Consequently, do not factor out.
2028: * ---------------------------------------------------------------------- */
2029: if (type.equalsIgnoreCase("geographic 2D")
2030: || type.equalsIgnoreCase("geographic 3D")) {
2031: final String csCode = getString(result, 7, code);
2032: final String dmCode = result.getString(8);
2033: final EllipsoidalCS cs = buffered
2034: .createEllipsoidalCS(csCode);
2035: final GeodeticDatum datum;
2036: if (dmCode != null) {
2037: datum = buffered.createGeodeticDatum(dmCode);
2038: } else {
2039: final String geoCode = getString(result, 9,
2040: code, 8);
2041: result.close(); // Must be close before createGeographicCRS
2042: result = null;
2043: final GeographicCRS baseCRS = buffered
2044: .createGeographicCRS(geoCode);
2045: datum = (GeodeticDatum) baseCRS.getDatum(); // TODO: remove cast with J2SE 1.5.
2046: }
2047: final Map properties = createProperties(name, epsg,
2048: area, scope, remarks);
2049: crs = factory.createGeographicCRS(properties,
2050: datum, cs);
2051: }
2052: /* ----------------------------------------------------------------------
2053: * PROJECTED CRS
2054: *
2055: * NOTE: This method invokes itself indirectly, through createGeographicCRS.
2056: * Consequently, we can't use 'result' anymore. We must close it here.
2057: * ---------------------------------------------------------------------- */
2058: else if (type.equalsIgnoreCase("projected")) {
2059: final String csCode = getString(result, 7, code);
2060: final String geoCode = getString(result, 9, code);
2061: final String opCode = getString(result, 10, code);
2062: result.close(); // Must be close before createGeographicCRS
2063: result = null;
2064: final CartesianCS cs = buffered
2065: .createCartesianCS(csCode);
2066: final GeographicCRS baseCRS = buffered
2067: .createGeographicCRS(geoCode);
2068: final CoordinateOperation op = buffered
2069: .createCoordinateOperation(opCode);
2070: if (op instanceof Conversion) {
2071: final Map properties = createProperties(name,
2072: epsg, area, scope, remarks);
2073: crs = factories.createProjectedCRS(properties,
2074: baseCRS, (Conversion) op, cs);
2075: } else {
2076: throw noSuchAuthorityCode(Projection.class,
2077: opCode);
2078: }
2079: }
2080: /* ----------------------------------------------------------------------
2081: * VERTICAL CRS
2082: * ---------------------------------------------------------------------- */
2083: else if (type.equalsIgnoreCase("vertical")) {
2084: final String csCode = getString(result, 7, code);
2085: final String dmCode = getString(result, 8, code);
2086: final VerticalCS cs = buffered
2087: .createVerticalCS(csCode);
2088: final VerticalDatum datum = buffered
2089: .createVerticalDatum(dmCode);
2090: final Map properties = createProperties(name, epsg,
2091: area, scope, remarks);
2092: crs = factory.createVerticalCRS(properties, datum,
2093: cs);
2094: }
2095: /* ----------------------------------------------------------------------
2096: * COMPOUND CRS
2097: *
2098: * NOTE: This method invokes itself recursively.
2099: * Consequently, we can't use 'result' anymore.
2100: * ---------------------------------------------------------------------- */
2101: else if (type.equalsIgnoreCase("compound")) {
2102: final String code1 = getString(result, 11, code);
2103: final String code2 = getString(result, 12, code);
2104: result.close();
2105: result = null;
2106: final CoordinateReferenceSystem crs1, crs2;
2107: if (!safetyGuard.add(epsg)) {
2108: throw recursiveCall(CompoundCRS.class, epsg);
2109: }
2110: try {
2111: crs1 = buffered
2112: .createCoordinateReferenceSystem(code1);
2113: crs2 = buffered
2114: .createCoordinateReferenceSystem(code2);
2115: } finally {
2116: safetyGuard.remove(epsg);
2117: }
2118: // Note: Don't invoke 'createProperties' sooner.
2119: final Map properties = createProperties(name, epsg,
2120: area, scope, remarks);
2121: crs = factory.createCompoundCRS(properties,
2122: new CoordinateReferenceSystem[] { crs1,
2123: crs2 });
2124: }
2125: /* ----------------------------------------------------------------------
2126: * GEOCENTRIC CRS
2127: * ---------------------------------------------------------------------- */
2128: else if (type.equalsIgnoreCase("geocentric")) {
2129: final String csCode = getString(result, 7, code);
2130: final String dmCode = getString(result, 8, code);
2131: final CoordinateSystem cs = buffered
2132: .createCoordinateSystem(csCode);
2133: final GeodeticDatum datum = buffered
2134: .createGeodeticDatum(dmCode);
2135: final Map properties = createProperties(name, epsg,
2136: area, scope, remarks);
2137: if (cs instanceof CartesianCS) {
2138: crs = factory.createGeocentricCRS(properties,
2139: datum, (CartesianCS) cs);
2140: } else if (cs instanceof SphericalCS) {
2141: crs = factory.createGeocentricCRS(properties,
2142: datum, (SphericalCS) cs);
2143: } else {
2144: result.close();
2145: throw new FactoryException(
2146: Errors
2147: .format(
2148: ErrorKeys.ILLEGAL_COORDINATE_SYSTEM_FOR_CRS_$2,
2149: Utilities
2150: .getShortClassName(cs),
2151: Utilities
2152: .getShortName(GeocentricCRS.class)));
2153: }
2154: }
2155: /* ----------------------------------------------------------------------
2156: * ENGINEERING CRS
2157: * ---------------------------------------------------------------------- */
2158: else if (type.equalsIgnoreCase("engineering")) {
2159: final String csCode = getString(result, 7, code);
2160: final String dmCode = getString(result, 8, code);
2161: final CoordinateSystem cs = buffered
2162: .createCoordinateSystem(csCode);
2163: final EngineeringDatum datum = buffered
2164: .createEngineeringDatum(dmCode);
2165: final Map properties = createProperties(name, epsg,
2166: area, scope, remarks);
2167: crs = factory.createEngineeringCRS(properties,
2168: datum, cs);
2169: }
2170: /* ----------------------------------------------------------------------
2171: * UNKNOW CRS
2172: * ---------------------------------------------------------------------- */
2173: else {
2174: result.close();
2175: throw new FactoryException(Errors.format(
2176: ErrorKeys.UNKNOW_TYPE_$1, type));
2177: }
2178: returnValue = (CoordinateReferenceSystem) ensureSingleton(
2179: crs, returnValue, code);
2180: if (result == null) {
2181: // Bypass the 'result.close()' line below:
2182: // the ResultSet has already been closed.
2183: return returnValue;
2184: }
2185: }
2186: result.close();
2187: } catch (SQLException exception) {
2188: throw databaseFailure(CoordinateReferenceSystem.class,
2189: code, exception);
2190: }
2191: if (returnValue == null) {
2192: throw noSuchAuthorityCode(CoordinateReferenceSystem.class,
2193: code);
2194: }
2195: return returnValue;
2196: }
2197:
2198: /**
2199: * Returns a parameter descriptor from a code.
2200: *
2201: * @param code The parameter descriptor code allocated by EPSG authority.
2202: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2203: * @throws FactoryException if some other kind of failure occured in the backing
2204: * store. This exception usually have {@link SQLException} as its cause.
2205: */
2206: public synchronized ParameterDescriptor createParameterDescriptor(
2207: final String code) throws FactoryException {
2208: ensureNonNull("code", code);
2209: ParameterDescriptor returnValue = null;
2210: final PreparedStatement stmt;
2211: try {
2212: final String primaryKey = toPrimaryKey(
2213: ParameterDescriptor.class, code,
2214: "[Coordinate_Operation Parameter]",
2215: "PARAMETER_CODE", "PARAMETER_NAME");
2216: stmt = prepareStatement("ParameterDescriptor", // Must be singular form.
2217: "SELECT PARAMETER_CODE," + " PARAMETER_NAME,"
2218: + " DESCRIPTION"
2219: + " FROM [Coordinate_Operation Parameter]"
2220: + " WHERE PARAMETER_CODE = ?");
2221: stmt.setString(1, primaryKey);
2222: ResultSet result = stmt.executeQuery();
2223: while (result.next()) {
2224: final String epsg = getString(result, 1, code);
2225: final String name = getString(result, 2, code);
2226: final String remarks = result.getString(3);
2227: final Unit unit;
2228: final Class type;
2229: /*
2230: * Search for units. We will choose the most commonly used one in parameter values.
2231: * If the parameter appears to have at least one non-null value in the "Parameter
2232: * File Name" column, then the type is assumed to be URI. Otherwise, the type is a
2233: * floating point number.
2234: */
2235: final PreparedStatement units = prepareStatement(
2236: "ParameterUnit",
2237: "SELECT MIN(UOM_CODE) AS UOM,"
2238: + " MIN(PARAM_VALUE_FILE_REF) AS FILEREF"
2239: + " FROM [Coordinate_Operation Parameter Value]"
2240: + " WHERE (PARAMETER_CODE = ?)"
2241: + " GROUP BY UOM_CODE"
2242: + " ORDER BY COUNT(UOM_CODE) DESC");
2243: units.setString(1, epsg);
2244: final ResultSet resultUnits = units.executeQuery();
2245: if (resultUnits.next()) {
2246: String element = resultUnits.getString(1);
2247: unit = (element != null) ? buffered
2248: .createUnit(element) : null;
2249: element = resultUnits.getString(2);
2250: type = (element != null && element.trim().length() != 0) ? URI.class
2251: : double.class;
2252: } else {
2253: unit = null;
2254: type = double.class;
2255: }
2256: resultUnits.close();
2257: /*
2258: * Now creates the parameter descriptor.
2259: */
2260: final ParameterDescriptor descriptor;
2261: final Map properties = createProperties(name, epsg,
2262: remarks);
2263: descriptor = new DefaultParameterDescriptor(properties,
2264: type, null, null, null, null, unit, true);
2265: returnValue = (ParameterDescriptor) ensureSingleton(
2266: descriptor, returnValue, code);
2267: }
2268: } catch (SQLException exception) {
2269: throw databaseFailure(OperationMethod.class, code,
2270: exception);
2271: }
2272: if (returnValue == null) {
2273: throw noSuchAuthorityCode(OperationMethod.class, code);
2274: }
2275: return returnValue;
2276: }
2277:
2278: /**
2279: * Returns all parameter descriptors for the specified method.
2280: *
2281: * @param method The operation method code.
2282: * @return The parameter descriptors.
2283: * @throws SQLException if a SQL statement failed.
2284: */
2285: private ParameterDescriptor[] createParameterDescriptors(
2286: final String method) throws FactoryException, SQLException {
2287: final PreparedStatement stmt;
2288: stmt = prepareStatement(
2289: "ParameterDescriptors", // Must be plural form.
2290: "SELECT PARAMETER_CODE"
2291: + " FROM [Coordinate_Operation Parameter Usage]"
2292: + " WHERE COORD_OP_METHOD_CODE = ?"
2293: + " ORDER BY SORT_ORDER");
2294: stmt.setString(1, method);
2295: final ResultSet results = stmt.executeQuery();
2296: final List descriptors = new ArrayList();
2297: while (results.next()) {
2298: final String param = getString(results, 1, method);
2299: descriptors.add(buffered.createParameterDescriptor(param));
2300: }
2301: results.close();
2302: return (ParameterDescriptor[]) descriptors
2303: .toArray(new ParameterDescriptor[descriptors.size()]);
2304: }
2305:
2306: /**
2307: * Fill parameter values in the specified group.
2308: *
2309: * @param method The EPSG code for the operation method.
2310: * @param operation The EPSG code for the operation (conversion or transformation).
2311: * @param value The parameter values to fill.
2312: * @throws SQLException if a SQL statement failed.
2313: */
2314: private void fillParameterValues(final String method,
2315: final String operation, final ParameterValueGroup parameters)
2316: throws FactoryException, SQLException {
2317: final PreparedStatement stmt;
2318: stmt = prepareStatement(
2319: "ParameterValues",
2320: "SELECT CP.PARAMETER_NAME,"
2321: + " CV.PARAMETER_VALUE,"
2322: + " CV.PARAM_VALUE_FILE_REF,"
2323: + " CV.UOM_CODE"
2324: + " FROM ([Coordinate_Operation Parameter Value] AS CV"
2325: + " INNER JOIN [Coordinate_Operation Parameter] AS CP"
2326: + " ON CV.PARAMETER_CODE = CP.PARAMETER_CODE)"
2327: + " INNER JOIN [Coordinate_Operation Parameter Usage] AS CU"
2328: + " ON (CP.PARAMETER_CODE = CU.PARAMETER_CODE)"
2329: + " AND (CV.COORD_OP_METHOD_CODE = CU.COORD_OP_METHOD_CODE)"
2330: + " WHERE CV.COORD_OP_METHOD_CODE = ?"
2331: + " AND CV.COORD_OP_CODE = ?"
2332: + " ORDER BY CU.SORT_ORDER");
2333: stmt.setString(1, method);
2334: stmt.setString(2, operation);
2335: final ResultSet result = stmt.executeQuery();
2336: while (result.next()) {
2337: final String name = getString(result, 1, operation);
2338: final double value = result.getDouble(2);
2339: final Unit unit;
2340: Object reference;
2341: if (result.wasNull()) {
2342: /*
2343: * If no numeric values were provided in the database, then the values must
2344: * appears in some external file. It may be a file to download from FTP.
2345: */
2346: reference = getString(result, 3, operation);
2347: try {
2348: reference = new URI((String) reference);
2349: } catch (URISyntaxException exception) {
2350: // Ignore: we will stores the reference as a file.
2351: reference = new File((String) reference);
2352: }
2353: unit = null;
2354: } else {
2355: reference = null;
2356: final String unitCode = result.getString(4);
2357: unit = (unitCode != null) ? buffered
2358: .createUnit(unitCode) : null;
2359: }
2360: final ParameterValue param;
2361: try {
2362: param = parameters.parameter(name);
2363: } catch (ParameterNotFoundException exception) {
2364: /*
2365: * Wraps the unchecked ParameterNotFoundException into the checked
2366: * NoSuchIdentifierException, which is a FactoryException subclass.
2367: * Note that in theory, NoSuchIdentifierException is for MathTransforms rather
2368: * than parameters. However, we are close in spirit here since we are setting
2369: * up MathTransform's parameters. Using NoSuchIdentifierException allows users
2370: * (including CoordinateOperationSet) to know that the failure is probably
2371: * caused by a MathTransform not yet supported in Geotools (or only partially
2372: * supported) rather than some more serious failure in the database side.
2373: * CoordinateOperationSet uses this information in order to determine if it
2374: * should try the next coordinate operation or propagate the exception.
2375: */
2376: final NoSuchIdentifierException e = new NoSuchIdentifierException(
2377: Errors.format(
2378: ErrorKeys.CANT_SET_PARAMETER_VALUE_$1,
2379: name), name);
2380: e.initCause(exception);
2381: throw e;
2382: }
2383: try {
2384: if (reference != null) {
2385: param.setValue(reference);
2386: } else if (unit != null) {
2387: param.setValue(value, unit);
2388: } else {
2389: param.setValue(value);
2390: }
2391: } catch (InvalidParameterValueException exception) {
2392: throw new FactoryException(Errors.format(
2393: ErrorKeys.CANT_SET_PARAMETER_VALUE_$1, name),
2394: exception);
2395: }
2396: }
2397: result.close();
2398: }
2399:
2400: /**
2401: * Returns an operation method from a code.
2402: *
2403: * @param code The operation method code allocated by EPSG authority.
2404: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2405: * @throws FactoryException if some other kind of failure occured in the backing
2406: * store. This exception usually have {@link SQLException} as its cause.
2407: */
2408: public synchronized OperationMethod createOperationMethod(
2409: final String code) throws FactoryException {
2410: ensureNonNull("code", code);
2411: OperationMethod returnValue = null;
2412: final PreparedStatement stmt;
2413: try {
2414: final String primaryKey = toPrimaryKey(
2415: OperationMethod.class, code,
2416: "[Coordinate_Operation Method]",
2417: "COORD_OP_METHOD_CODE", "COORD_OP_METHOD_NAME");
2418: stmt = prepareStatement("OperationMethod",
2419: "SELECT COORD_OP_METHOD_CODE,"
2420: + " COORD_OP_METHOD_NAME," + " FORMULA,"
2421: + " REMARKS"
2422: + " FROM [Coordinate_Operation Method]"
2423: + " WHERE COORD_OP_METHOD_CODE = ?");
2424: stmt.setString(1, primaryKey);
2425: final ResultSet result = stmt.executeQuery();
2426: OperationMethod method = null;
2427: while (result.next()) {
2428: final String epsg = getString(result, 1, code);
2429: final String name = getString(result, 2, code);
2430: final String formula = result.getString(3);
2431: final String remarks = result.getString(4);
2432: final int encoded = getDimensionsForMethod(epsg);
2433: final int sourceDimensions = encoded >>> 16;
2434: final int targetDimensions = encoded & 0xFFFF;
2435: final ParameterDescriptor[] descriptors = createParameterDescriptors(epsg);
2436: final Map properties = createProperties(name, epsg,
2437: remarks);
2438: if (formula != null) {
2439: properties
2440: .put(OperationMethod.FORMULA_KEY, formula);
2441: }
2442: method = new DefaultOperationMethod(properties,
2443: sourceDimensions, targetDimensions,
2444: new DefaultParameterDescriptorGroup(properties,
2445: descriptors));
2446: returnValue = (OperationMethod) ensureSingleton(method,
2447: returnValue, code);
2448: }
2449: } catch (SQLException exception) {
2450: throw databaseFailure(OperationMethod.class, code,
2451: exception);
2452: }
2453: if (returnValue == null) {
2454: throw noSuchAuthorityCode(OperationMethod.class, code);
2455: }
2456: return returnValue;
2457: }
2458:
2459: /**
2460: * Returns the must common source and target dimensions for the specified method.
2461: * Source dimension is encoded in the 16 highest bits and target dimension is encoded
2462: * in the 16 lowest bits. If this method can't infers the dimensions from the "Coordinate
2463: * Operation" table, then the operation method is probably a projection, which always have
2464: * (2,2) dimensions in the EPSG database.
2465: */
2466: private int getDimensionsForMethod(final String code)
2467: throws SQLException {
2468: final PreparedStatement stmt;
2469: stmt = prepareStatement("MethodDimensions",
2470: "SELECT SOURCE_CRS_CODE," + " TARGET_CRS_CODE"
2471: + " FROM [Coordinate_Operation]"
2472: + " WHERE COORD_OP_METHOD_CODE = ?"
2473: + " AND SOURCE_CRS_CODE IS NOT NULL"
2474: + " AND TARGET_CRS_CODE IS NOT NULL");
2475: stmt.setString(1, code);
2476: final ResultSet result = stmt.executeQuery();
2477: final Map dimensions = new HashMap();
2478: final Dimensions temp = new Dimensions((2 << 16) | 2); // Default to (2,2) dimensions.
2479: Dimensions max = temp;
2480: while (result.next()) {
2481: final short sourceDimensions = getDimensionForCRS(result
2482: .getString(1));
2483: final short targetDimensions = getDimensionForCRS(result
2484: .getString(2));
2485: temp.encoded = (sourceDimensions << 16)
2486: | (targetDimensions);
2487: Dimensions candidate = (Dimensions) dimensions.get(temp);
2488: if (candidate == null) {
2489: candidate = new Dimensions(temp.encoded);
2490: dimensions.put(candidate, candidate);
2491: }
2492: if (++candidate.occurences > max.occurences) {
2493: max = candidate;
2494: }
2495: }
2496: result.close();
2497: return max.encoded;
2498: }
2499:
2500: /** A counter for source and target dimensions (to be kept together). */
2501: private static final class Dimensions {
2502: /** The dimensions as an encoded value. */
2503: int encoded;
2504: /** The occurences of this dimensions. */
2505: int occurences;
2506:
2507: Dimensions(final int e) {
2508: encoded = e;
2509: }
2510:
2511: public int hashCode() {
2512: return encoded;
2513: }
2514:
2515: public boolean equals(final Object object) { // MUST ignore 'occurences'.
2516: return (object instanceof Dimensions)
2517: && ((Dimensions) object).encoded == encoded;
2518: }
2519:
2520: public String toString() {
2521: return "[(" + (encoded >>> 16) + ',' + (encoded & 0xFFFF)
2522: + ")\u00D7" + occurences + ']';
2523: }
2524: }
2525:
2526: /**
2527: * Returns the dimension of the specified CRS. If the CRS is not found (which should not
2528: * happen, but we don't need to be strict here), then this method assumes a two-dimensional
2529: * CRS.
2530: */
2531: private short getDimensionForCRS(final String code)
2532: throws SQLException {
2533: final PreparedStatement stmt;
2534: final Short cached = (Short) axisCounts.get(code);
2535: final short dimension;
2536: if (cached == null) {
2537: stmt = prepareStatement(
2538: "Dimension",
2539: " SELECT COUNT(COORD_AXIS_CODE)"
2540: + " FROM [Coordinate Axis]"
2541: + " WHERE COORD_SYS_CODE = (SELECT COORD_SYS_CODE "
2542: + " FROM [Coordinate Reference System]"
2543: + " WHERE COORD_REF_SYS_CODE = ?)");
2544: stmt.setString(1, code);
2545: final ResultSet result = stmt.executeQuery();
2546: dimension = result.next() ? result.getShort(1) : 2;
2547: axisCounts.put(code, new Short(dimension));
2548: result.close();
2549: } else {
2550: dimension = cached.shortValue();
2551: }
2552: return dimension;
2553: }
2554:
2555: /**
2556: * Returns {@code true} if the {@linkplain CoordinateOperation coordinate operation} for the
2557: * specified code is a {@linkplain Projection projection}. The caller must have ensured that
2558: * the designed operation is a {@linkplain Conversion conversion} before to invoke this method.
2559: */
2560: final boolean isProjection(final String code) throws SQLException {
2561: final PreparedStatement stmt;
2562: Boolean projection = (Boolean) codeProjection.get(code);
2563: if (projection == null) {
2564: stmt = prepareStatement(
2565: "isProjection",
2566: "SELECT COORD_REF_SYS_CODE"
2567: + " FROM [Coordinate Reference System]"
2568: + " WHERE PROJECTION_CONV_CODE = ?"
2569: + " AND COORD_REF_SYS_KIND LIKE 'projected%'");
2570: stmt.setString(1, code);
2571: final ResultSet result = stmt.executeQuery();
2572: final boolean found = result.next();
2573: result.close();
2574: projection = Boolean.valueOf(found);
2575: codeProjection.put(code, projection);
2576: }
2577: return projection.booleanValue();
2578: }
2579:
2580: /**
2581: * Returns a coordinate operation from a code.
2582: * The returned object will either be a {@linkplain Conversion conversion} or a
2583: * {@linkplain Transformation transformation}, depending on the code.
2584: *
2585: * @param code Value allocated by authority.
2586: * @return The coordinate operation object.
2587: * @throws NoSuchAuthorityCodeException if this method can't find the requested code.
2588: * @throws FactoryException if some other kind of failure occured in the backing
2589: * store. This exception usually have {@link SQLException} as its cause.
2590: */
2591: public synchronized CoordinateOperation createCoordinateOperation(
2592: final String code) throws FactoryException {
2593: ensureNonNull("code", code);
2594: CoordinateOperation returnValue = null;
2595: try {
2596: final String primaryKey = toPrimaryKey(
2597: CoordinateOperation.class, code,
2598: "[Coordinate_Operation]", "COORD_OP_CODE",
2599: "COORD_OP_NAME");
2600: final PreparedStatement stmt;
2601: stmt = prepareStatement("CoordinateOperation",
2602: "SELECT COORD_OP_CODE," + " COORD_OP_NAME,"
2603: + " COORD_OP_TYPE," + " SOURCE_CRS_CODE,"
2604: + " TARGET_CRS_CODE,"
2605: + " COORD_OP_METHOD_CODE,"
2606: + " COORD_TFM_VERSION,"
2607: + " COORD_OP_ACCURACY,"
2608: + " AREA_OF_USE_CODE," + " COORD_OP_SCOPE,"
2609: + " REMARKS"
2610: + " FROM [Coordinate_Operation]"
2611: + " WHERE COORD_OP_CODE = ?");
2612: stmt.setString(1, primaryKey);
2613: ResultSet result = stmt.executeQuery();
2614: while (result.next()) {
2615: final String epsg = getString(result, 1, code);
2616: final String name = getString(result, 2, code);
2617: final String type = getString(result, 3, code).trim()
2618: .toLowerCase();
2619: final boolean isTransformation = type
2620: .equals("transformation");
2621: final boolean isConversion = type.equals("conversion");
2622: final boolean isConcatenated = type
2623: .equals("concatenated operation");
2624: final String sourceCode, targetCode, methodCode;
2625: if (isConversion) {
2626: // Optional for conversions, mandatory for all others.
2627: sourceCode = result.getString(4);
2628: targetCode = result.getString(5);
2629: } else {
2630: sourceCode = getString(result, 4, code);
2631: targetCode = getString(result, 5, code);
2632: }
2633: if (isConcatenated) {
2634: // Not applicable to concatenated operation, mandatory for all others.
2635: methodCode = result.getString(6);
2636: } else {
2637: methodCode = getString(result, 6, code);
2638: }
2639: String version = result.getString(7);
2640: double accuracy = result.getDouble(8);
2641: if (result.wasNull())
2642: accuracy = Double.NaN;
2643: String area = result.getString(9);
2644: String scope = result.getString(10);
2645: String remarks = result.getString(11);
2646: /*
2647: * Gets the source and target CRS. They are mandatory for transformations (it
2648: * was checked above in this method) and optional for conversions. Conversions
2649: * are usually "defining conversions" and don't define source and target CRS.
2650: * In EPSG database 6.7, all defining conversions are projections and their
2651: * dimensions are always 2. However, this is not generalizable to other kind
2652: * of operation methods. For example the "Geocentric translation" operation
2653: * method has 3-dimensional source and target.
2654: */
2655: final int sourceDimensions, targetDimensions;
2656: final CoordinateReferenceSystem sourceCRS, targetCRS;
2657: if (sourceCode != null) {
2658: sourceCRS = buffered
2659: .createCoordinateReferenceSystem(sourceCode);
2660: sourceDimensions = sourceCRS.getCoordinateSystem()
2661: .getDimension();
2662: } else {
2663: sourceCRS = null;
2664: sourceDimensions = 2; // Acceptable default for projections only.
2665: }
2666: if (targetCode != null) {
2667: targetCRS = buffered
2668: .createCoordinateReferenceSystem(targetCode);
2669: targetDimensions = targetCRS.getCoordinateSystem()
2670: .getDimension();
2671: } else {
2672: targetCRS = null;
2673: targetDimensions = 2; // Acceptable default for projections only.
2674: }
2675: /*
2676: * Gets the operation method. This is mandatory for conversions and transformations
2677: * (it was checked above in this method) but optional for concatenated operations.
2678: * Fetching parameter values is part of this block.
2679: */
2680: final boolean isBursaWolf;
2681: OperationMethod method;
2682: final ParameterValueGroup parameters;
2683: if (methodCode == null) {
2684: isBursaWolf = false;
2685: method = null;
2686: parameters = null;
2687: } else {
2688: final int num;
2689: try {
2690: num = Integer.parseInt(methodCode);
2691: } catch (NumberFormatException exception) {
2692: result.close();
2693: throw new FactoryException(exception);
2694: }
2695: isBursaWolf = (num >= BURSA_WOLF_MIN_CODE && num <= BURSA_WOLF_MAX_CODE);
2696: // Reminder: The source and target dimensions MUST be computed when
2697: // the information is available. Dimension is not always 2!!
2698: method = buffered.createOperationMethod(methodCode);
2699: if (method.getSourceDimensions() != sourceDimensions
2700: || method.getTargetDimensions() != targetDimensions) {
2701: method = new DefaultOperationMethod(method,
2702: sourceDimensions, targetDimensions);
2703: }
2704: /*
2705: * Note that some parameters required for MathTransform creation are implicit in
2706: * the EPSG database (e.g. semi-major and semi-minor axis length in the case of
2707: * map projections). We ask the parameter value group straight from the math
2708: * transform factory instead of from the operation method in order to get all
2709: * required parameter descriptors, including implicit ones.
2710: */
2711: final String classe = method.getName().getCode();
2712: parameters = factories.getMathTransformFactory()
2713: .getDefaultParameters(classe);
2714: fillParameterValues(methodCode, epsg, parameters);
2715: }
2716: /*
2717: * Creates common properties. The 'version' and 'accuracy' are usually defined
2718: * for transformations only. However, we check them for all kind of operations
2719: * (including conversions) and copy the information inconditionnaly if present.
2720: *
2721: * NOTE: This block must be executed last before object creations below, because
2722: * methods like createCoordinateReferenceSystem and createOperationMethod
2723: * overwrite the properties map.
2724: */
2725: final Map properties = createProperties(name, epsg,
2726: area, scope, remarks);
2727: if (version != null
2728: && (version = version.trim()).length() != 0) {
2729: properties.put(
2730: CoordinateOperation.OPERATION_VERSION_KEY,
2731: version);
2732: }
2733: if (!Double.isNaN(accuracy)) {
2734: final QuantitativeResultImpl accuracyResult;
2735: final AbsoluteExternalPositionalAccuracyImpl accuracyElement;
2736: accuracyResult = new QuantitativeResultImpl(
2737: new double[] { accuracy });
2738: // TODO: Need to invoke something equivalent to:
2739: // accuracyResult.setValueType(Float.class);
2740: // This is the type declared in the MS-Access database.
2741: accuracyResult.setValueUnit(SI.METER); // In meters by definition in the EPSG database.
2742: accuracyElement = new AbsoluteExternalPositionalAccuracyImpl(
2743: accuracyResult);
2744: accuracyElement
2745: .setMeasureDescription(TRANSFORMATION_ACCURACY);
2746: accuracyElement
2747: .setEvaluationMethodType(EvaluationMethodType.DIRECT_EXTERNAL);
2748: properties
2749: .put(
2750: CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
2751: new PositionalAccuracy[] { (PositionalAccuracy) accuracyElement
2752: .unmodifiable() });
2753: }
2754: /*
2755: * Creates the operation. Conversions should be the only operations allowed to
2756: * have null source and target CRS. In such case, the operation is a defining
2757: * conversion (usually to be used later as part of a ProjectedCRS creation),
2758: * and always a projection in the specific case of the EPSG database (which
2759: * allowed us to assume 2-dimensional operation method in the code above for
2760: * this specific case - not to be generalized to the whole EPSG database).
2761: */
2762: final CoordinateOperation operation;
2763: if (isConversion
2764: && (sourceCRS == null || targetCRS == null)) {
2765: // Note: we usually can't resolve sourceCRS and targetCRS because there
2766: // is many of them for the same coordinate operation (projection) code.
2767: operation = new DefiningConversion(properties,
2768: method, parameters);
2769: } else if (isConcatenated) {
2770: /*
2771: * Concatenated operation: we need to close the current result set, because
2772: * we are going to invoke this method recursively in the following lines.
2773: *
2774: * Note: we instantiate directly the Geotools's implementation of
2775: * ConcatenatedOperation instead of using CoordinateOperationFactory in order
2776: * to avoid loading the quite large Geotools's implementation of this factory,
2777: * and also because it is not part of FactoryGroup anyway.
2778: */
2779: result.close();
2780: result = null;
2781: final PreparedStatement cstmt = prepareStatement(
2782: "ConcatenatedOperation",
2783: "SELECT SINGLE_OPERATION_CODE"
2784: + " FROM [Coordinate_Operation Path]"
2785: + " WHERE (CONCAT_OPERATION_CODE = ?)"
2786: + " ORDER BY OP_PATH_STEP");
2787: cstmt.setString(1, epsg);
2788: final ResultSet cr = cstmt.executeQuery();
2789: final List codes = new ArrayList();
2790: while (cr.next()) {
2791: codes.add(cr.getString(1));
2792: }
2793: cr.close();
2794: final CoordinateOperation[] operations = new CoordinateOperation[codes
2795: .size()];
2796: if (!safetyGuard.add(epsg)) {
2797: throw recursiveCall(
2798: ConcatenatedOperation.class, epsg);
2799: }
2800: try {
2801: for (int i = 0; i < operations.length; i++) {
2802: operations[i] = buffered
2803: .createCoordinateOperation((String) codes
2804: .get(i));
2805: }
2806: } finally {
2807: safetyGuard.remove(epsg);
2808: }
2809: try {
2810: return new DefaultConcatenatedOperation(
2811: properties, operations);
2812: } catch (IllegalArgumentException exception) {
2813: // May happen if there is less than 2 operations to concatenate.
2814: // It happen for some deprecated CRS like 8658 for example.
2815: throw new FactoryException(exception);
2816: }
2817: } else {
2818: /*
2819: * Needs to create a math transform. A special processing is performed for
2820: * datum shift methods, since the conversion from ellipsoid to geocentric
2821: * for "geocentric translations" is implicit in the EPSG database. Even in
2822: * the case of Molodenski transforms, the axis length to set are the same.
2823: */
2824: if (isBursaWolf)
2825: try {
2826: Ellipsoid ellipsoid = CRSUtilities
2827: .getHeadGeoEllipsoid(sourceCRS);
2828: if (ellipsoid != null) {
2829: final Unit axisUnit = ellipsoid
2830: .getAxisUnit();
2831: parameters
2832: .parameter("src_semi_major")
2833: .setValue(
2834: ellipsoid
2835: .getSemiMajorAxis(),
2836: axisUnit);
2837: parameters
2838: .parameter("src_semi_minor")
2839: .setValue(
2840: ellipsoid
2841: .getSemiMinorAxis(),
2842: axisUnit);
2843: parameters
2844: .parameter("src_dim")
2845: .setValue(
2846: sourceCRS
2847: .getCoordinateSystem()
2848: .getDimension());
2849: }
2850: ellipsoid = CRSUtilities
2851: .getHeadGeoEllipsoid(targetCRS);
2852: if (ellipsoid != null) {
2853: final Unit axisUnit = ellipsoid
2854: .getAxisUnit();
2855: parameters
2856: .parameter("tgt_semi_major")
2857: .setValue(
2858: ellipsoid
2859: .getSemiMajorAxis(),
2860: axisUnit);
2861: parameters
2862: .parameter("tgt_semi_minor")
2863: .setValue(
2864: ellipsoid
2865: .getSemiMinorAxis(),
2866: axisUnit);
2867: parameters
2868: .parameter("tgt_dim")
2869: .setValue(
2870: targetCRS
2871: .getCoordinateSystem()
2872: .getDimension());
2873: }
2874: } catch (ParameterNotFoundException exception) {
2875: result.close();
2876: throw new FactoryException(
2877: Errors
2878: .format(
2879: ErrorKeys.GEOTOOLS_EXTENSION_REQUIRED_$1,
2880: method.getName()
2881: .getCode(),
2882: exception));
2883: }
2884: /*
2885: * At this stage, the parameters are ready for use. Creates the math transform
2886: * and wraps it in the final operation (a Conversion or a Transformation).
2887: */
2888: final Class expected;
2889: if (isTransformation) {
2890: expected = Transformation.class;
2891: } else if (isConversion) {
2892: expected = Conversion.class;
2893: } else {
2894: result.close();
2895: throw new FactoryException(Errors.format(
2896: ErrorKeys.UNKNOW_TYPE_$1, type));
2897: }
2898: final MathTransform mt = factories
2899: .createBaseToDerived(sourceCRS, parameters,
2900: targetCRS.getCoordinateSystem());
2901: // TODO: uses GeoAPI factory method once available.
2902: operation = DefaultOperation.create(properties,
2903: sourceCRS, targetCRS, mt, method, expected);
2904: }
2905: returnValue = (CoordinateOperation) ensureSingleton(
2906: operation, returnValue, code);
2907: if (result == null) {
2908: // Bypass the 'result.close()' line below:
2909: // the ResultSet has already been closed.
2910: return returnValue;
2911: }
2912: }
2913: result.close();
2914: } catch (SQLException exception) {
2915: throw databaseFailure(CoordinateOperation.class, code,
2916: exception);
2917: }
2918: if (returnValue == null) {
2919: throw noSuchAuthorityCode(CoordinateOperation.class, code);
2920: }
2921: return returnValue;
2922: }
2923:
2924: /**
2925: * Creates operations from coordinate reference system codes.
2926: * The returned set is ordered with the most accurate operations first.
2927: *
2928: * @param sourceCode Coded value of source coordinate reference system.
2929: * @param targetCode Coded value of target coordinate reference system.
2930: * @throws FactoryException if the object creation failed.
2931: *
2932: * @todo The ordering is not consistent among all database software, because the "accuracy"
2933: * column may contains null values. When used in an "ORDER BY" clause, PostgreSQL put
2934: * null values last, while Access and HSQL put them first. The PostgreSQL's behavior is
2935: * better for what we want (put operations with unknow accuracy last). Unfortunatly,
2936: * I don't know yet how to instruct Access to put null values last using standard SQL
2937: * ("IIF" is not standard, and Access doesn't seem to understand "CASE ... THEN" clauses).
2938: */
2939: public synchronized Set createFromCoordinateReferenceSystemCodes(
2940: final String sourceCode, final String targetCode)
2941: throws FactoryException {
2942: ensureNonNull("sourceCode", sourceCode);
2943: ensureNonNull("targetCode", targetCode);
2944: final String pair = sourceCode + " \u21E8 " + targetCode;
2945: final CoordinateOperationSet set = new CoordinateOperationSet(
2946: buffered);
2947: try {
2948: final String sourceKey = toPrimaryKeyCRS(sourceCode);
2949: final String targetKey = toPrimaryKeyCRS(targetCode);
2950: boolean searchTransformations = false;
2951: do {
2952: /*
2953: * This 'do' loop is executed twice: the first time for searching defining
2954: * conversions, and the second time for searching all other kind of operations.
2955: * Defining conversions are searched first because they are, by definition, the
2956: * most accurate operations.
2957: */
2958: final String key, sql;
2959: if (searchTransformations) {
2960: key = "TransformationFromCRS";
2961: sql = "SELECT COORD_OP_CODE"
2962: + " FROM [Coordinate_Operation]"
2963: + " WHERE SOURCE_CRS_CODE = ?"
2964: + " AND TARGET_CRS_CODE = ?"
2965: + " ORDER BY ABS(DEPRECATED), COORD_OP_ACCURACY";
2966: } else {
2967: key = "ConversionFromCRS";
2968: sql = "SELECT PROJECTION_CONV_CODE"
2969: + " FROM [Coordinate Reference System]"
2970: + " WHERE SOURCE_GEOGCRS_CODE = ?"
2971: + " AND COORD_REF_SYS_CODE = ?";
2972: }
2973: final PreparedStatement stmt = prepareStatement(key,
2974: sql);
2975: stmt.setString(1, sourceKey);
2976: stmt.setString(2, targetKey);
2977: final ResultSet result = stmt.executeQuery();
2978: while (result.next()) {
2979: final String code = getString(result, 1, pair);
2980: set.addAuthorityCode(code,
2981: searchTransformations ? null : targetKey);
2982: }
2983: result.close();
2984: } while ((searchTransformations = !searchTransformations) == true);
2985: /*
2986: * Search finished. We may have a lot of coordinate operations
2987: * (e.g. about 40 for "ED50" (EPSG:4230) to "WGS 84" (EPSG:4326)).
2988: * Alter the ordering using the information supplied in the supersession table.
2989: */
2990: final String[] codes = set.getAuthorityCodes();
2991: sort(codes);
2992: set.setAuthorityCodes(codes);
2993: } catch (SQLException exception) {
2994: throw databaseFailure(CoordinateOperation.class, pair,
2995: exception);
2996: }
2997: /*
2998: * Before to return the set, tests the creation of 1 object in order to report early
2999: * (i.e. now) any problems with SQL statements. Remaining operations will be created
3000: * only when first needed.
3001: */
3002: set.resolve(1);
3003: return set;
3004: }
3005:
3006: /**
3007: * Sorts an array of codes in preference order. This method orders pairwise the codes according
3008: * the information provided in the supersession table. If the same object is superseded by more
3009: * than one object, then the most recent one is inserted first. Except for the codes moved as a
3010: * result of pairwise ordering, this method try to preserve the old ordering of the supplied
3011: * codes (since deprecated operations should already be last). The ordering is performed in
3012: * place.
3013: *
3014: * @param codes The codes, usually as an array of {@link String}. If the array do not contains
3015: * string objects, then the {@link Object#toString} method must returns the code
3016: * for each element.
3017: */
3018: // TODO: Use generic type for "Object[] codes" with J2SE 1.5.
3019: private void sort(final Object[] codes) throws SQLException,
3020: FactoryException {
3021: if (codes.length <= 1) {
3022: return; // Nothing to sort.
3023: }
3024: final PreparedStatement stmt;
3025: stmt = prepareStatement("Supersession", "SELECT SUPERSEDED_BY"
3026: + " FROM [Supersession]" + " WHERE OBJECT_CODE = ?"
3027: + " ORDER BY SUPERSESSION_YEAR DESC");
3028: int maxIterations = 15; // For avoiding never-ending loop.
3029: do {
3030: boolean changed = false;
3031: for (int i = 0; i < codes.length; i++) {
3032: final String code = codes[i].toString();
3033: stmt.setString(1, code);
3034: final ResultSet result = stmt.executeQuery();
3035: while (result.next()) {
3036: final String replacement = getString(result, 1,
3037: code);
3038: for (int j = i + 1; j < codes.length; j++) {
3039: final Object candidate = codes[j];
3040: if (replacement.equals(candidate.toString())) {
3041: /*
3042: * Found a code to move in front of the superceded one.
3043: */
3044: System.arraycopy(codes, i, codes, i + 1, j
3045: - i);
3046: codes[i++] = candidate;
3047: changed = true;
3048: }
3049: }
3050: }
3051: result.close();
3052: }
3053: if (!changed) {
3054: return;
3055: }
3056: } while (--maxIterations != 0);
3057: LOGGER.finer("Possible recursivity in supersessions.");
3058: }
3059:
3060: /**
3061: * Returns a finder which can be used for looking up unidentified objects.
3062: *
3063: * @param type The type of objects to look for.
3064: * @return A finder to use for looking up unidentified objects.
3065: * @throws FactoryException if the finder can not be created.
3066: */
3067: //@Override
3068: public IdentifiedObjectFinder getIdentifiedObjectFinder(
3069: final Class/*<? extends IdentifiedObject>*/type)
3070: throws FactoryException {
3071: return new Finder(buffered, type);
3072: }
3073:
3074: /**
3075: * An implementation of {@link IdentifiedObjectFinder} which scans over a smaller set
3076: * of authority codes.
3077: * <p>
3078: * <b>Implementation note:</b> Since this method may be invoked indirectly by
3079: * {@link LongitudeFirstFactory}, it must be insensitive to axis order.
3080: */
3081: private final class Finder extends IdentifiedObjectFinder {
3082: /**
3083: * Creates a new finder backed by the specified <em>buffered</em> authority factory.
3084: */
3085: Finder(final AbstractAuthorityFactory buffered,
3086: final Class/*<? extends IdentifiedObject>*/type) {
3087: super (buffered, type);
3088: }
3089:
3090: /**
3091: * Returns a set of authority codes that <strong>may</strong> identify the same object
3092: * than the specified one. This implementation tries to get a smaller set than what
3093: * {@link DirectEpsgFactory#getAuthorityCodes} would produce.
3094: */
3095: //@Override
3096: protected Set getCodeCandidates(final IdentifiedObject object)
3097: throws FactoryException {
3098: String select = "COORD_REF_SYS_CODE";
3099: String from = "[Coordinate Reference System]";
3100: String where, code;
3101: if (object instanceof Ellipsoid) {
3102: select = "ELLIPSOID_CODE";
3103: from = "[Ellipsoid]";
3104: where = "SEMI_MAJOR_AXIS";
3105: code = Double.toString(((Ellipsoid) object)
3106: .getSemiMajorAxis());
3107: } else {
3108: IdentifiedObject dependency;
3109: if (object instanceof GeneralDerivedCRS) {
3110: dependency = ((GeneralDerivedCRS) object)
3111: .getBaseCRS();
3112: where = "SOURCE_GEOGCRS_CODE";
3113: } else if (object instanceof SingleCRS) {
3114: dependency = ((SingleCRS) object).getDatum();
3115: where = "DATUM_CODE";
3116: } else if (object instanceof GeodeticDatum) {
3117: dependency = ((GeodeticDatum) object)
3118: .getEllipsoid();
3119: select = "DATUM_CODE";
3120: from = "[Datum]";
3121: where = "ELLIPSOID_CODE";
3122: } else {
3123: return super .getCodeCandidates(object);
3124: }
3125: dependency = buffered.getIdentifiedObjectFinder(
3126: dependency.getClass()).find(dependency);
3127: Identifier id = AbstractIdentifiedObject.getIdentifier(
3128: dependency, getAuthority());
3129: if (id == null || (code = id.getCode()) == null) {
3130: return super .getCodeCandidates(object);
3131: }
3132: }
3133: String sql = "SELECT " + select + " FROM " + from
3134: + " WHERE " + where + "='" + code + '\'';
3135: sql = adaptSQL(sql);
3136: final Set/*<String>*/result = new LinkedHashSet();
3137: try {
3138: final Statement s = connection.createStatement();
3139: final ResultSet r = s.executeQuery(sql);
3140: while (r.next()) {
3141: result.add(r.getString(1));
3142: }
3143: r.close();
3144: s.close();
3145: } catch (SQLException exception) {
3146: throw databaseFailure(Identifier.class, code, exception);
3147: }
3148: return result;
3149: }
3150: }
3151:
3152: /**
3153: * Constructs an exception for recursive calls.
3154: */
3155: private static FactoryException recursiveCall(final Class type,
3156: final String code) {
3157: return new FactoryException(Errors.format(
3158: ErrorKeys.RECURSIVE_CALL_$2, Utilities
3159: .getShortName(type), code));
3160: }
3161:
3162: /**
3163: * Constructs an exception for a database failure.
3164: */
3165: private static FactoryException databaseFailure(final Class type,
3166: final String code, final SQLException cause) {
3167: return new FactoryException(Errors.format(
3168: ErrorKeys.DATABASE_FAILURE_$2, Utilities
3169: .getShortName(type), code), cause);
3170: }
3171:
3172: /**
3173: * Invoked when a new {@link PreparedStatement} is about to be created from a SQL string.
3174: * Since the <A HREF="http://www.epsg.org">EPSG database</A> is available mainly in MS-Access
3175: * format, SQL statements are formatted using some syntax specific to this particular database
3176: * software (for example "<code>SELECT * FROM [Coordinate Reference System]</code>"). When
3177: * providing subclass targeting another database vendor, then this method should be overridden
3178: * in order to adapt the local SQL syntax.
3179: * <p>
3180: * For example a subclass connecting to a <cite>PostgreSQL</cite> database could replace
3181: * all spaces (" ") between watching braces ("[" and "]") by underscore ("_").
3182: *
3183: * @param statement The statement in MS-Access syntax.
3184: * @return The SQL statement to use. The default implementation returns the string unchanged.
3185: */
3186: protected abstract String adaptSQL(final String statement);
3187:
3188: /**
3189: * Returns {@code true} if the specified code may be a primary key in some table. This method
3190: * do not needs to checks any entry in the database. It should just checks from the syntax if
3191: * the code looks like a valid EPSG identifier. The default implementation returns {@code true}
3192: * if all non-space characters are {@linkplain Character#isDigit(char) digits}.
3193: * <p>
3194: * When this method returns {@code false}, some {@code createFoo(...)} methods look for the
3195: * code in the name column instead of the primary key column. This allows to accept the
3196: * "<cite>NTF (Paris) / France I</cite>" string (for example) in addition to the {@code "27581"}
3197: * primary key. Both should fetch the same object.
3198: * <p>
3199: * If this method returns {@code true} in all cases, then this factory never search for matching
3200: * names. In such case, an appropriate exception will be thrown in {@code createFoo(...)}
3201: * methods if the code is not found in the primary key column. Subclasses can overrides this
3202: * method that way if this is the intended behavior.
3203: *
3204: * @param code The code the inspect.
3205: * @return {@code true} if the code is probably a primary key.
3206: * @throws FactoryException if an unexpected error occured while inspecting the code.
3207: */
3208: protected boolean isPrimaryKey(final String code)
3209: throws FactoryException {
3210: final int length = code.length();
3211: for (int i = 0; i < length; i++) {
3212: final char c = code.charAt(i);
3213: if (!Character.isDigit(c) && !Character.isSpaceChar(c)) {
3214: return false;
3215: }
3216: }
3217: return true;
3218: }
3219:
3220: /**
3221: * Returns {@code true} if it is safe to dispose this factory. This method is invoked indirectly
3222: * by {@link ThreadedEpsgFactory} after some timeout in order to release resources. This method will
3223: * block the disposal if some {@linkplain #getAuthorityCodes set of authority codes} are still
3224: * in use.
3225: */
3226: final synchronized boolean canDispose() {
3227: boolean can = true;
3228: Map pool/*<SoftReference,WeakReference>*/= null;
3229: for (final Iterator it = authorityCodes.entrySet().iterator(); it
3230: .hasNext();) {
3231: final Map.Entry entry = (Map.Entry) it.next();
3232: final Reference reference = (Reference) entry.getValue();
3233: final AuthorityCodes codes = (AuthorityCodes) reference
3234: .get();
3235: if (codes == null) {
3236: it.remove();
3237: continue;
3238: }
3239: /*
3240: * A set of authority codes is still in use. We can't dispose this factory.
3241: * But maybe the set was retained only by soft references... So we continue
3242: * the iteration anyway and replace all soft references by weak ones, in order
3243: * to get more chances to be garbage-collected before the next disposal cycle.
3244: */
3245: can = false;
3246: if (reference instanceof SoftReference) {
3247: // Each reference appears twice (once with the type key, and once under the SQL
3248: // statement as key). So we need to manage a pool of references for avoiding
3249: // duplication.
3250: if (pool == null) {
3251: pool = new IdentityHashMap();
3252: }
3253: WeakReference weak = (WeakReference) pool
3254: .get(reference);
3255: if (weak == null) {
3256: weak = new WeakReference(codes);
3257: pool.put(reference, weak);
3258: }
3259: entry.setValue(weak);
3260: }
3261: }
3262: return can;
3263: }
3264:
3265: /**
3266: * Disposes any resources hold by this object.
3267: *
3268: * @throws FactoryException if an error occured while closing the connection.
3269: */
3270: public synchronized void dispose() throws FactoryException {
3271: final boolean shutdown = SHUTDOWN_THREAD.equals(Thread
3272: .currentThread().getName());
3273: final boolean isClosed;
3274: try {
3275: isClosed = connection.isClosed();
3276: for (final Iterator it = authorityCodes.values().iterator(); it
3277: .hasNext();) {
3278: final AuthorityCodes set = (AuthorityCodes) ((Reference) it
3279: .next()).get();
3280: if (set != null) {
3281: set.finalize();
3282: }
3283: it.remove();
3284: }
3285: for (final Iterator it = statements.values().iterator(); it
3286: .hasNext();) {
3287: ((PreparedStatement) it.next()).close();
3288: it.remove();
3289: }
3290: if (shutdown) {
3291: shutdown(true);
3292: }
3293: connection.close();
3294: } catch (SQLException exception) {
3295: throw new FactoryException(exception);
3296: }
3297: super .dispose();
3298: if (shutdown)
3299: try {
3300: shutdown(false);
3301: } catch (SQLException exception) {
3302: throw new FactoryException(exception);
3303: }
3304: if (!isClosed) {
3305: /*
3306: * The above code was run inconditionnaly as a safety, even if the connection
3307: * was already closed. However we will log a message only if we actually closed
3308: * the connection, otherwise the log records are a little bit misleading.
3309: */
3310: LOGGER.log(Logging.format(Level.FINE,
3311: LoggingKeys.CLOSED_EPSG_DATABASE));
3312: }
3313: }
3314:
3315: /**
3316: * Shutdown the database engine. This method is invoked twice by {@link ThreadedEpsgFactory}
3317: * at JVM shutdown: one time before the {@linkplain #connection} is closed, and a second
3318: * time after. This shutdown hook is usefull for <cite>embedded</cite> database engine
3319: * starting a server process in addition to the client process. Just closing the connection
3320: * is not enough for them. Example:
3321: * <P>
3322: * <UL>
3323: * <LI>HSQL database engine needs to execute a {@code "SHUTDOWN"} statement using the
3324: * {@linkplain #connection} before it is closed.</LI>
3325: * <LI>Derby database engine needs to instruct the {@linkplain java.sql.DriverManager driver
3326: * manager} after all connections have been closed.</LI>
3327: * </UL>
3328: * <P>
3329: * The default implementation does nothing, which is suffisient for implementations
3330: * connecting to a distant server (i.e. non-embedded database engine), for example
3331: * {@linkplain AccessDataSource MS-Access} or {@linkplain PostgreDataSource PostgreSQL}.
3332: *
3333: * @param active {@code true} if the {@linkplain #connection} is alive, or {@code false}
3334: * otherwise. This method is invoked first with {@code active} set to {@code true},
3335: * then a second time with {@code active} set to {@code false}.
3336: * @throws SQLException if this method failed to shutdown the database engine.
3337: */
3338: protected void shutdown(final boolean active) throws SQLException {
3339: }
3340:
3341: /**
3342: * Invokes {@link #dispose} when this factory is garbage collected.
3343: *
3344: * @throws Throwable if an error occured while closing the connection.
3345: */
3346: protected final void finalize() throws Throwable {
3347: dispose();
3348: super.finalize();
3349: }
3350: }
|