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 }