001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sql/AbstractSQLDatastore.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.io.datastore.sql;
037    
038    import java.sql.Connection;
039    import java.sql.PreparedStatement;
040    import java.sql.ResultSet;
041    import java.sql.SQLException;
042    import java.sql.Statement;
043    import java.sql.Timestamp;
044    import java.util.Date;
045    import java.util.Iterator;
046    import java.util.List;
047    import java.util.Set;
048    
049    import org.deegree.datatypes.Types;
050    import org.deegree.datatypes.UnknownTypeException;
051    import org.deegree.framework.log.ILogger;
052    import org.deegree.framework.log.LoggerFactory;
053    import org.deegree.framework.util.TimeTools;
054    import org.deegree.i18n.Messages;
055    import org.deegree.io.DBConnectionPool;
056    import org.deegree.io.JDBCConnection;
057    import org.deegree.io.datastore.AnnotationDocument;
058    import org.deegree.io.datastore.Datastore;
059    import org.deegree.io.datastore.DatastoreConfiguration;
060    import org.deegree.io.datastore.DatastoreException;
061    import org.deegree.io.datastore.DatastoreTransaction;
062    import org.deegree.io.datastore.FeatureId;
063    import org.deegree.io.datastore.TransactionException;
064    import org.deegree.io.datastore.idgenerator.IdGenerationException;
065    import org.deegree.io.datastore.schema.MappedFeatureType;
066    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
067    import org.deegree.io.datastore.schema.content.SQLFunctionCall;
068    import org.deegree.io.datastore.sql.StatementBuffer.StatementArgument;
069    import org.deegree.io.datastore.sql.transaction.SQLTransaction;
070    import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
071    import org.deegree.model.crs.CoordinateSystem;
072    import org.deegree.model.crs.UnknownCRSException;
073    import org.deegree.model.feature.FeatureCollection;
074    import org.deegree.model.feature.GMLFeatureDocument;
075    import org.deegree.model.filterencoding.Filter;
076    import org.deegree.model.spatialschema.Geometry;
077    import org.deegree.ogcbase.SortProperty;
078    import org.deegree.ogcwebservices.wfs.operation.Lock;
079    import org.deegree.ogcwebservices.wfs.operation.Query;
080    
081    /**
082     * This abstract class implements the common functionality of {@link Datastore} implementations that use SQL databases
083     * as backend.
084     * 
085     * @see QueryHandler
086     * 
087     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
088     * @author last edited by: $Author: aschmitz $
089     * 
090     * @version $Revision: 20115 $, $Date: 2009-10-14 10:57:39 +0200 (Mi, 14 Okt 2009) $
091     */
092    public abstract class AbstractSQLDatastore extends Datastore {
093    
094        private static final ILogger LOG = LoggerFactory.getLogger( AbstractSQLDatastore.class );
095    
096        /** Database specific SRS code for an unspecified SRS. */
097        protected static final int SRS_UNDEFINED = -1;
098    
099        /** Pool of database connections. */
100        protected DBConnectionPool pool;
101    
102        private DatastoreTransaction activeTransaction;
103    
104        private Thread transactionHolder;
105    
106        @Override
107        public AnnotationDocument getAnnotationParser() {
108            return new SQLAnnotationDocument( this.getClass() );
109        }
110    
111        @Override
112        public void configure( DatastoreConfiguration datastoreConfiguration )
113                                throws DatastoreException {
114            super.configure( datastoreConfiguration );
115            this.pool = DBConnectionPool.getInstance();
116        }
117    
118        @Override
119        public void close()
120                                throws DatastoreException {
121            LOG.logInfo( "close() does not do nothing for AbstractSQLDatastore because no resources must be released" );
122        }
123    
124        /**
125         * Overwrite this to return a database specific (spatial capable) {@link WhereBuilder} implementation.
126         * 
127         * @param rootFts
128         *            involved (requested) feature types
129         * @param aliases
130         *            aliases for the feature types, may be null
131         * @param filter
132         *            filter that restricts the matched features
133         * @param sortProperties
134         *            sort criteria for the result, may be null or empty
135         * @param aliasGenerator
136         *            used to generate unique table aliases
137         * @param vcProvider
138         * @return <code>WhereBuilder</code> implementation suitable for this datastore
139         * @throws DatastoreException
140         */
141        public WhereBuilder getWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter,
142                                             SortProperty[] sortProperties, TableAliasGenerator aliasGenerator,
143                                             VirtualContentProvider vcProvider )
144                                throws DatastoreException {
145            return new WhereBuilder( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider );
146        }
147    
148        @Override
149        public FeatureCollection performQuery( Query query, MappedFeatureType[] rootFts )
150                                throws DatastoreException, UnknownCRSException {
151            Connection conn = acquireConnection();
152            FeatureCollection result;
153            try {
154                result = performQuery( query, rootFts, conn );
155            } finally {
156                releaseConnection( conn );
157            }
158            return result;
159        }
160    
161        @Override
162        public FeatureCollection performQuery( Query query, MappedFeatureType[] rootFts, DatastoreTransaction context )
163                                throws DatastoreException, UnknownCRSException {
164            return performQuery( query, rootFts, ( (SQLTransaction) context ).getConnection() );
165        }
166    
167        /**
168         * Performs a {@link Query} against the datastore.
169         * <p>
170         * Note that this method is responsible for the coordinate system tranformation of the input {@link Query} and the
171         * output {@link FeatureCollection}.
172         * 
173         * @param query
174         *            query to be performed
175         * @param rootFts
176         *            the root feature types that are queried, more than one type means that the types are joined
177         * @param conn
178         *            JDBC connection to use
179         * @return requested feature instances
180         * @throws DatastoreException
181         * @throws UnknownCRSException
182         */
183        protected FeatureCollection performQuery( Query query, MappedFeatureType[] rootFts, Connection conn )
184                                throws DatastoreException, UnknownCRSException {
185    
186            Query transformedQuery = transformQuery( query );
187    
188            FeatureCollection result = null;
189            try {
190                QueryHandler queryHandler = new QueryHandler( this, new TableAliasGenerator(), conn, rootFts, query );
191                result = queryHandler.performQuery();
192            } catch ( SQLException e ) {
193                String msg = "SQL error while performing query: " + e.getMessage();
194                LOG.logError( msg, e );
195                throw new DatastoreException( msg, e );
196            }
197    
198            // transform result to queried srs (only if necessary)
199            String targetSrs = transformedQuery.getSrsName();
200            if ( targetSrs != null && !this.canTransformTo( targetSrs ) ) {
201                result = transformResult( result, targetSrs );
202            }
203            return result;
204        }
205    
206        /**
207         * Acquires transactional access to the datastore. There's only one active transaction per datastore instance
208         * allowed at a time.
209         * 
210         * @return transaction object that allows to perform transaction operations on the datastore
211         * @throws DatastoreException
212         */
213        @Override
214        public DatastoreTransaction acquireTransaction()
215                                throws DatastoreException {
216    
217            while ( this.activeTransaction != null ) {
218                Thread holder = this.transactionHolder;
219                // check if transaction holder variable has (just) been cleared or if the other thread
220                // has been killed (avoid deadlocks)
221                if ( holder == null || !holder.isAlive() ) {
222                    this.activeTransaction = null;
223                    this.transactionHolder = null;
224                    break;
225                }
226            }
227    
228            this.activeTransaction = createTransaction();
229            this.transactionHolder = Thread.currentThread();
230            return this.activeTransaction;
231        }
232    
233        /**
234         * Creates a new {@link SQLTransaction} that provides transactional access.
235         * 
236         * @return new {@link SQLTransaction} instance
237         * @throws DatastoreException
238         */
239        protected SQLTransaction createTransaction()
240                                throws DatastoreException {
241            return new SQLTransaction( this, new TableAliasGenerator(), acquireConnection() );
242        }
243    
244        /**
245         * Returns the transaction to the datastore. This makes the transaction available to other clients again (via
246         * <code>acquireTransaction</code>). Underlying resources (such as JDBCConnections are freed).
247         * <p>
248         * The transaction should be terminated, i.e. commit() or rollback() must have been called before.
249         * 
250         * @param ta
251         *            the DatastoreTransaction to be returned
252         * @throws DatastoreException
253         */
254        @Override
255        public void releaseTransaction( DatastoreTransaction ta )
256                                throws DatastoreException {
257            if ( ta.getDatastore() != this ) {
258                String msg = Messages.getMessage( "DATASTORE_TA_NOT_OWNER" );
259                throw new TransactionException( msg );
260            }
261            if ( ta != this.activeTransaction ) {
262                String msg = Messages.getMessage( "DATASTORE_TA_NOT_ACTIVE" );
263                throw new TransactionException( msg );
264            }
265            releaseConnection( ( (SQLTransaction) ta ).getConnection() );
266    
267            this.activeTransaction = null;
268            this.transactionHolder = null;
269        }
270    
271        @Override
272        public Set<FeatureId> determineFidsToLock( List<Lock> requestParts )
273                                throws DatastoreException {
274    
275            Set<FeatureId> lockedFids = null;
276            Connection conn = acquireConnection();
277            LockHandler handler = new LockHandler( this, new TableAliasGenerator(), conn, requestParts );
278            try {
279                lockedFids = handler.determineFidsToLock();
280            } finally {
281                releaseConnection( conn );
282            }
283            return lockedFids;
284        }
285    
286        /**
287         * Converts a database specific geometry <code>Object</code> from the <code>ResultSet</code> to a deegree
288         * <code>Geometry</code>.
289         * 
290         * @param value
291         * @param targetSRS
292         * @param conn
293         * @return corresponding deegree geometry
294         * @throws SQLException
295         */
296        public abstract Geometry convertDBToDeegreeGeometry( Object value, CoordinateSystem targetSRS, Connection conn )
297                                throws SQLException;
298    
299        /**
300         * Converts a deegree <code>Geometry</code> to a database specific geometry <code>Object</code>.
301         * 
302         * @param geometry
303         * @param nativeSRSCode
304         * @param conn
305         * @return corresponding database specific geometry object
306         * @throws DatastoreException
307         */
308        public abstract Object convertDeegreeToDBGeometry( Geometry geometry, int nativeSRSCode, Connection conn )
309                                throws DatastoreException;
310    
311        /**
312         * Returns the database connection requested for.
313         * 
314         * @return Connection
315         * @throws DatastoreException
316         */
317        protected Connection acquireConnection()
318                                throws DatastoreException {
319            JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
320            Connection conn = null;
321            try {
322                conn = pool.acquireConnection( jdbcConnection.getDriver(), jdbcConnection.getURL(),
323                                               jdbcConnection.getUser(), jdbcConnection.getPassword() );
324            } catch ( Exception e ) {
325                String msg = "Cannot acquire database connection: " + e.getMessage();
326                LOG.logError( msg, e );
327                throw new DatastoreException( msg, e );
328            }
329            return conn;
330        }
331    
332        /**
333         * Releases the connection.
334         * 
335         * @param conn
336         *            connection to be released.
337         * @throws DatastoreException
338         */
339        public void releaseConnection( Connection conn )
340                                throws DatastoreException {
341            LOG.logDebug( "Releasing JDBCConnection." );
342            JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
343            try {
344                pool.releaseConnection( conn, jdbcConnection.getDriver(), jdbcConnection.getURL(),
345                                        jdbcConnection.getUser(), jdbcConnection.getPassword() );
346            } catch ( Exception e ) {
347                String msg = "Cannot release database connection: " + e.getMessage();
348                LOG.logError( msg, e );
349                throw new DatastoreException( msg, e );
350            }
351        }
352    
353        /**
354         * Converts the <code>StatementBuffer</code> into a <code>PreparedStatement</code>, which is initialized and ready
355         * to be performed.
356         * 
357         * @param conn
358         *            connection to be used to create the <code>PreparedStatement</code>
359         * @param statementBuffer
360         * @return the <code>PreparedStatment</code>, ready to be performed
361         * @throws SQLException
362         *             if a JDBC related error occurs
363         * @throws DatastoreException
364         */
365        public PreparedStatement prepareStatement( Connection conn, StatementBuffer statementBuffer )
366                                throws SQLException, DatastoreException {
367    
368            LOG.logDebug( "Preparing statement: " + statementBuffer.getQueryString() );
369            PreparedStatement preparedStatement = conn.prepareStatement( statementBuffer.getQueryString() );
370    
371            Iterator<StatementArgument> argumentIter = statementBuffer.getArgumentsIterator();
372            int i = 1;
373            while ( argumentIter.hasNext() ) {
374                StatementArgument argument = argumentIter.next();
375                int targetSqlType = argument.getTypeCode();
376                Object obj = argument.getArgument();
377                Object sqlObject = obj != null ? convertToDBType( obj, targetSqlType ) : null;
378    
379                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
380                    try {
381                        String typeName = Types.getTypeNameForSQLTypeCode( targetSqlType );
382                        LOG.logDebug( "Setting argument " + i + ": type=" + typeName + ", value class="
383                                      + ( sqlObject == null ? "none (null object)" : sqlObject.getClass() ) );
384                        if ( sqlObject instanceof String ) {
385                            String s = (String) sqlObject;
386                            if ( s.length() <= 100 ) {
387                                LOG.logDebug( "Value: '" + s + "'" );
388                            } else {
389                                LOG.logDebug( "Value: '" + s.substring( 0, 100 ) + "...'" );
390                            }
391                        }
392                        if ( sqlObject instanceof Number ) {
393                            LOG.logDebug( "Value: '" + sqlObject + "'" );
394                        }
395                    } catch ( UnknownTypeException e ) {
396                        LOG.logError( e.getMessage(), e );
397                        throw new SQLException( e.getMessage() );
398                    }
399                }
400                if ( sqlObject == null ) {
401                    preparedStatement.setNull( i, targetSqlType );
402                } else {
403                    preparedStatement.setObject( i, sqlObject, targetSqlType );
404                }
405                i++;
406            }
407            return preparedStatement;
408        }
409    
410        /**
411         * Converts the given object into an object that is suitable for a table column of the specified SQL type.
412         * <p>
413         * The return value is used in a java.sql.PreparedStatement#setObject() call.
414         * <p>
415         * Please note that this implementation is subject to change. There are missing type cases, and it is preferable to
416         * use the original string representation of the input object (except for geometries).
417         * <p>
418         * NOTE: Almost identical functionality exists in {@link GMLFeatureDocument}. This is subject to change.
419         * 
420         * @see java.sql.PreparedStatement#setObject(int, Object, int)
421         * @param o
422         * @param sqlTypeCode
423         * @return an object that is suitable for a table column of the specified SQL type
424         * @throws DatastoreException
425         */
426        private Object convertToDBType( Object o, int sqlTypeCode )
427                                throws DatastoreException {
428    
429            Object sqlType = null;
430    
431            switch ( sqlTypeCode ) {
432            case Types.VARCHAR: {
433                sqlType = o.toString();
434                break;
435            }
436            case Types.INTEGER:
437            case Types.SMALLINT: {
438                try {
439                    sqlType = new Integer( o.toString().trim() );
440                } catch ( NumberFormatException e ) {
441                    throw new DatastoreException( "'" + o + "' does not denote a valid Integer value." );
442                }
443                break;
444            }
445            case Types.NUMERIC:
446            case Types.REAL:
447            case Types.DOUBLE: {
448                try {
449                    sqlType = new Double( o.toString() );
450                } catch ( NumberFormatException e ) {
451                    throw new DatastoreException( "'" + o + "' does not denote a valid Double value." );
452                }
453                break;
454            }
455            case Types.DECIMAL:
456            case Types.FLOAT: {
457                try {
458                    sqlType = new Float( o.toString() );
459                } catch ( NumberFormatException e ) {
460                    throw new DatastoreException( "'" + o + "' does not denote a valid Double value." );
461                }
462                break;
463            }
464            case Types.BOOLEAN: {
465                sqlType = new Boolean( o.toString() );
466                break;
467            }
468            case Types.DATE: {
469                if ( o instanceof Date ) {
470                    sqlType = new java.sql.Date( ( (Date) o ).getTime() );
471                } else {
472                    String s = o.toString();
473                    int idx = s.indexOf( " " ); // Datestring like "2005-04-21 00:00:00"
474                    if ( -1 != idx )
475                        s = s.substring( 0, idx );
476                    sqlType = new java.sql.Date( TimeTools.createCalendar( s ).getTimeInMillis() );
477                }
478                break;
479            }
480            case Types.TIME: {
481                if ( o instanceof Date ) {
482                    sqlType = new java.sql.Time( ( (Date) o ).getTime() );
483                } else {
484                    sqlType = new java.sql.Time( TimeTools.createCalendar( o.toString() ).getTimeInMillis() );
485                }
486                break;
487            }
488            case Types.TIMESTAMP: {
489                if ( o instanceof Date ) {
490                    sqlType = new Timestamp( ( (Date) o ).getTime() );
491                } else {
492                    sqlType = new java.sql.Timestamp( TimeTools.createCalendar( o.toString() ).getTimeInMillis() );
493                }
494                break;
495            }
496            default: {
497                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
498                    String sqlTypeName = "" + sqlTypeCode;
499                    try {
500                        sqlTypeName = Types.getTypeNameForSQLTypeCode( sqlTypeCode );
501                    } catch ( UnknownTypeException e ) {
502                        LOG.logError( e.getMessage(), e );
503                    }
504                    LOG.logDebug( "No type conversion for sql type '" + sqlTypeName
505                                  + "' defined. Passing argument of type '" + o.getClass().getName() + "'." );
506                }
507                sqlType = o;
508            }
509            }
510            return sqlType;
511        }
512    
513        /**
514         * Converts the given object from a <code>java.sql.ResultSet</code> column to the common type to be used as a
515         * feature property.
516         * 
517         * @param rsObject
518         * @param sqlTypeCode
519         * @return an object that is suitable for a table column of the specified SQL type
520         * @throws DatastoreException
521         */
522        @SuppressWarnings("unused")
523        public Object convertFromDBType( Object rsObject, int sqlTypeCode )
524                                throws DatastoreException {
525            return rsObject;
526        }
527    
528        /**
529         * Overwrite this to enable the datastore to fetch the next value of a SQL sequence.
530         * 
531         * @param conn
532         *            JDBC connection to be used.
533         * @param sequence
534         *            name of the SQL sequence.
535         * @return next value of the given SQL sequence
536         * @throws DatastoreException
537         *             if the value could not be retrieved
538         */
539        public Object getSequenceNextVal( Connection conn, String sequence )
540                                throws DatastoreException {
541            String msg = Messages.getMessage( "DATASTORE_SEQ_NOT_SUPPORTED", this.getClass().getName() );
542            throw new DatastoreException( msg );
543        }
544    
545        /**
546         * Overwrite this to enable the datastore to fetch the current value (plus an offset) of a SQL sequence.
547         * 
548         * @param conn
549         *            JDBC connection to be used.
550         * @param sequence
551         *            name of the SQL sequence
552         * @param offset
553         *            offset added to the sequence value
554         * @return next value of the given SQL sequence
555         * @throws DatastoreException
556         *             if the value could not be retrieved
557         */
558        public Object getSequenceCurrValPlusOffset( Connection conn, String sequence, int offset )
559                                throws DatastoreException {
560            String msg = Messages.getMessage( "DATASTORE_SEQ_NOT_SUPPORTED", this.getClass().getName() );
561            throw new DatastoreException( msg );
562        }
563    
564        /**
565         * Returns the maximum (integer) value stored in a certain table column.
566         * 
567         * @param conn
568         *            JDBC connection to be used
569         * @param tableName
570         *            name of the table
571         * @param columnName
572         *            name of the column
573         * @return the maximum value
574         * @throws IdGenerationException
575         *             if the value could not be retrieved
576         */
577        public int getMaxValue( Connection conn, String tableName, String columnName )
578                                throws IdGenerationException {
579    
580            int max = 0;
581            Statement stmt = null;
582            ResultSet rs = null;
583    
584            LOG.logDebug( "Retrieving max value in " + tableName + "." + columnName + "..." );
585    
586            try {
587                try {
588                    stmt = conn.createStatement();
589                    rs = stmt.executeQuery( "SELECT MAX(" + columnName + ") FROM " + tableName );
590                    if ( rs.next() ) {
591                        Object columnMax = rs.getObject( 1 );
592                        if ( columnMax != null ) {
593                            if ( columnMax instanceof Integer ) {
594                                max = ( (Integer) columnMax ).intValue();
595                            } else {
596                                max = Integer.parseInt( columnMax.toString() );
597                            }
598                        }
599                    }
600                } finally {
601                    try {
602                        if ( rs != null ) {
603                            rs.close();
604                        }
605                    } finally {
606                        if ( stmt != null ) {
607                            stmt.close();
608                        }
609                    }
610                }
611            } catch ( SQLException e ) {
612                String msg = "Could not retrieve max value for table column '" + tableName + "." + columnName + "': "
613                             + e.getMessage();
614                LOG.logError( msg, e );
615                throw new IdGenerationException( msg, e );
616            } catch ( NumberFormatException e ) {
617                String msg = "Could not convert selected value to integer: " + e.getMessage();
618                LOG.logError( msg, e );
619                throw new IdGenerationException( msg, e );
620            }
621            LOG.logDebug( "max value: " + max );
622            return max;
623        }
624    
625        /**
626         * Returns an {@link SQLFunctionCall} that refers to the given {@link MappedGeometryPropertyType} in the specified
627         * target SRS using a database specific SQL function.
628         * 
629         * @param geoProperty
630         *            geometry property
631         * @param targetSRS
632         *            target spatial reference system (usually "EPSG:XYZ")
633         * @return an {@link SQLFunctionCall} that refers to the geometry in the specified srs
634         * @throws DatastoreException
635         */
636        public SQLFunctionCall buildSRSTransformCall( @SuppressWarnings("unused") MappedGeometryPropertyType geoProperty,
637                                                      @SuppressWarnings("unused") String targetSRS )
638                                throws DatastoreException {
639            String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNSUPPORTED", this.getClass().getName() );
640            throw new DatastoreException( msg );
641        }
642    
643        /**
644         * Builds an SQL fragment that converts the given geometry to the specified SRS.
645         * 
646         * @param geomIdentifier
647         * @param nativeSRSCode
648         * @return an SQL fragment that converts the given geometry to the specified SRS
649         * @throws DatastoreException
650         */
651        public String buildSRSTransformCall( @SuppressWarnings("unused") String geomIdentifier,
652                                             @SuppressWarnings("unused") int nativeSRSCode )
653                                throws DatastoreException {
654            String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNSUPPORTED", this.getClass().getName() );
655            throw new DatastoreException( msg );
656        }
657    
658        /**
659         * Returns the database specific code for the given SRS name.
660         * 
661         * @param srsName
662         *            spatial reference system name (usually "EPSG:XYZ")
663         * @return the database specific SRS code, or -1 if no corresponding native code is known
664         * @throws DatastoreException
665         */
666        public int getNativeSRSCode( @SuppressWarnings("unused") String srsName )
667                                throws DatastoreException {
668            String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNSUPPORTED", this.getClass().getName() );
669            throw new DatastoreException( msg );
670        }
671    
672        /**
673         * Checks whether the (native) coordinate transformation of the specified geometry property to the given SRS is
674         * possible (and necessary), i.e.
675         * <ul>
676         * <li>the internal srs of the property is specified (and not -1)
677         * <li>or the requested SRS is null or equal to the property's srs
678         * </ul>
679         * If this is not the case, a {@link DatastoreException} is thrown to indicate the problem.
680         * 
681         * @param pt
682         * @param queriedSrs
683         * @return the srs to transform to, or null, if transformation is unnecessary
684         * @throws DatastoreException
685         */
686        String checkTransformation( MappedGeometryPropertyType pt, String queriedSrs )
687                                throws DatastoreException {
688    
689            String targetSrs = null;
690            int internalSrs = pt.getMappingField().getSRS();
691            String propertySrs = pt.getCS().getIdentifier();
692    
693            if ( queriedSrs != null && !propertySrs.equals( queriedSrs ) ) {
694                if ( internalSrs == SRS_UNDEFINED ) {
695                    String msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED", pt.getName(), queriedSrs, propertySrs );
696                    throw new DatastoreException( msg );
697                }
698                targetSrs = queriedSrs;
699            }
700            return targetSrs;
701        }
702    
703        public void appendGeometryColumnGet( StatementBuffer query, String tableAlias, String column ) {
704            query.append( tableAlias );
705            query.append( '.' );
706            query.append( column );
707        }
708    }