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 }