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