001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/sql/oracle/OracleDatastore.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 This file is part of deegree. 004 Copyright (C) 2001-2006 by: 005 Department of Geography, University of Bonn 006 http://www.giub.uni-bonn.de/deegree/ 007 lat/lon GmbH 008 http://www.lat-lon.de 009 010 This library is free software; you can redistribute it and/or 011 modify it under the terms of the GNU Lesser General Public 012 License as published by the Free Software Foundation; either 013 version 2.1 of the License, or (at your option) any later version. 014 015 This library is distributed in the hope that it will be useful, 016 but WITHOUT ANY WARRANTY; without even the implied warranty of 017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 018 Lesser General Public License for more details. 019 020 You should have received a copy of the GNU Lesser General Public 021 License along with this library; if not, write to the Free Software 022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 023 024 Contact: 025 026 Andreas Poth 027 lat/lon GmbH 028 Aennchenstraße 19 029 53177 Bonn 030 Germany 031 E-Mail: poth@lat-lon.de 032 033 Jens Fitzke 034 lat/lon GmbH 035 Aennchenstraße 19 036 53177 Bonn 037 Germany 038 E-Mail: jens.fitzke@uni-bonn.de 039 ---------------------------------------------------------------------------*/ 040 041 package org.deegree.io.datastore.sql.oracle; 042 043 import java.io.IOException; 044 import java.io.InputStream; 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.util.Date; 051 import java.util.HashMap; 052 import java.util.Iterator; 053 import java.util.Map; 054 import java.util.Properties; 055 056 import oracle.spatial.geometry.JGeometry; 057 import oracle.sql.STRUCT; 058 import oracle.sql.TIMESTAMP; 059 060 import org.deegree.datatypes.Types; 061 import org.deegree.datatypes.UnknownTypeException; 062 import org.deegree.framework.log.ILogger; 063 import org.deegree.framework.log.LoggerFactory; 064 import org.deegree.framework.util.TimeTools; 065 import org.deegree.i18n.Messages; 066 import org.deegree.io.datastore.Datastore; 067 import org.deegree.io.datastore.DatastoreException; 068 import org.deegree.io.datastore.schema.MappedFeatureType; 069 import org.deegree.io.datastore.schema.MappedGeometryPropertyType; 070 import org.deegree.io.datastore.schema.TableRelation; 071 import org.deegree.io.datastore.schema.content.ConstantContent; 072 import org.deegree.io.datastore.schema.content.FieldContent; 073 import org.deegree.io.datastore.schema.content.FunctionParam; 074 import org.deegree.io.datastore.schema.content.MappingGeometryField; 075 import org.deegree.io.datastore.schema.content.SQLFunctionCall; 076 import org.deegree.io.datastore.sql.AbstractSQLDatastore; 077 import org.deegree.io.datastore.sql.StatementBuffer; 078 import org.deegree.io.datastore.sql.TableAliasGenerator; 079 import org.deegree.io.datastore.sql.VirtualContentProvider; 080 import org.deegree.io.datastore.sql.StatementBuffer.StatementArgument; 081 import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder; 082 import org.deegree.model.crs.CoordinateSystem; 083 import org.deegree.model.feature.FeatureCollection; 084 import org.deegree.model.filterencoding.Filter; 085 import org.deegree.model.spatialschema.Geometry; 086 import org.deegree.model.spatialschema.GeometryException; 087 import org.deegree.ogcbase.SortProperty; 088 import org.deegree.ogcwebservices.wfs.operation.Query; 089 090 /** 091 * {@link Datastore} implementation for Oracle Spatial database systems. Supports Oracle Spatial for Oracle 10g. 092 * 093 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 094 * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a> 095 * @author last edited by: $Author: mschneider $ 096 * 097 * @version $Revision: 7782 $, $Date: 2007-07-17 18:40:42 +0200 (Di, 17 Jul 2007) $ 098 */ 099 public class OracleDatastore extends AbstractSQLDatastore { 100 101 protected static final ILogger LOG = LoggerFactory.getLogger( OracleDatastore.class ); 102 103 private static final String SRS_CODE_PROP_FILE = "srs_codes_oracle.properties"; 104 105 private static Map<String, Integer> nativeSrsCodeMap = new HashMap<String, Integer>(); 106 107 private static final int SRS_UNDEFINED = -1; 108 109 static { 110 try { 111 initSRSCodeMap(); 112 } catch ( IOException e ) { 113 String msg = "Cannot load native srs code file '" + SRS_CODE_PROP_FILE + "'."; 114 LOG.logError( msg, e ); 115 } 116 } 117 118 /** 119 * Returns a specific {@link WhereBuilder} implementation for Oracle Spatial. 120 * 121 * @param rootFts 122 * involved (requested) feature types 123 * @param aliases 124 * aliases for the feature types, may be null 125 * @param filter 126 * filter that restricts the matched features 127 * @param sortProperties 128 * sort criteria for the result, may be null or empty 129 * @param aliasGenerator 130 * used to generate unique table aliases 131 * @param vcProvider 132 * @return <code>WhereBuilder</code> implementation for Oracle Spatial 133 * @throws DatastoreException 134 */ 135 @Override 136 public WhereBuilder getWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter, 137 SortProperty[] sortProperties, TableAliasGenerator aliasGenerator, 138 VirtualContentProvider vcProvider ) 139 throws DatastoreException { 140 return new OracleSpatialWhereBuilder( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider ); 141 } 142 143 /** 144 * Converts an Oracle specific geometry <code>Object</code> from the <code>ResultSet</code> to a deegree 145 * <code>Geometry</code>. 146 * 147 * @param value 148 * @param targetCS 149 * @param conn 150 * @return corresponding deegree geometry 151 * @throws SQLException 152 */ 153 @Override 154 public Geometry convertDBToDeegreeGeometry( Object value, CoordinateSystem targetCS, Connection conn ) 155 throws SQLException { 156 Geometry geometry = null; 157 if ( value != null ) { 158 LOG.logDebug( "Converting STRUCT to JGeometry." ); 159 JGeometry jGeometry = JGeometry.load( (STRUCT) value ); 160 try { 161 LOG.logDebug( "Converting JGeometry to deegree geometry ('" + targetCS + "')" ); 162 geometry = JGeometryAdapter.wrap( jGeometry, targetCS ); 163 } catch ( Exception e ) { 164 throw new SQLException( "Error converting STRUCT to Geometry: " + e.getMessage() ); 165 } 166 } 167 return geometry; 168 } 169 170 /** 171 * Converts a deegree <code>Geometry</code> to an Oracle specific geometry object. 172 * 173 * @param geometry 174 * @param nativeSRSCode 175 * @param conn 176 * @return corresponding Oracle specific geometry object 177 * @throws DatastoreException 178 */ 179 @Override 180 public STRUCT convertDeegreeToDBGeometry( Geometry geometry, int nativeSRSCode, Connection conn ) 181 throws DatastoreException { 182 183 JGeometry jGeometry = null; 184 LOG.logDebug( "Converting deegree geometry to JGeometry." ); 185 try { 186 jGeometry = JGeometryAdapter.export( geometry, nativeSRSCode ); 187 } catch ( GeometryException e ) { 188 throw new DatastoreException( "Error converting deegree geometry to JGeometry: " + e.getMessage(), e ); 189 } 190 191 LOG.logDebug( "Converting JGeometry to STRUCT." ); 192 STRUCT struct = null; 193 try { 194 struct = JGeometry.store( jGeometry, conn ); 195 } catch ( SQLException e ) { 196 throw new DatastoreException( "Error converting JGeometry to STRUCT: " + e.getMessage(), e ); 197 } 198 return struct; 199 } 200 201 /** 202 * Converts the given object from a <code>java.sql.ResultSet</code> column to the common type to be used as a 203 * feature property. 204 * 205 * @param rsObject 206 * @param sqlTypeCode 207 * @return an object that is suitable for a table column of the specified SQL type 208 * @throws DatastoreException 209 */ 210 @Override 211 public Object convertFromDBType( Object rsObject, int sqlTypeCode ) 212 throws DatastoreException { 213 Object propertyValue = rsObject; 214 try { 215 if ( rsObject instanceof TIMESTAMP ) { 216 propertyValue = ( (TIMESTAMP) rsObject ).timestampValue(); 217 } 218 } catch ( SQLException e ) { 219 throw new DatastoreException( e.getMessage(), e ); 220 } 221 return propertyValue; 222 } 223 224 /** 225 * Returns the next value of the given SQL sequence. 226 * 227 * @param conn 228 * JDBC connection to be used. 229 * @param sequence 230 * name of the SQL sequence 231 * @return next value of the given SQL sequence 232 * @throws DatastoreException 233 * if the value could not be retrieved 234 */ 235 @Override 236 public Object getSequenceNextVal( Connection conn, String sequence ) 237 throws DatastoreException { 238 239 Object nextVal = null; 240 Statement stmt = null; 241 ResultSet rs = null; 242 243 try { 244 try { 245 stmt = conn.createStatement(); 246 rs = stmt.executeQuery( "SELECT " + sequence + ".nextval FROM dual" ); 247 if ( rs.next() ) { 248 nextVal = rs.getObject( 1 ); 249 } 250 } finally { 251 try { 252 if ( rs != null ) { 253 rs.close(); 254 } 255 } finally { 256 if ( stmt != null ) { 257 stmt.close(); 258 } 259 } 260 } 261 } catch ( SQLException e ) { 262 String msg = "Could not retrieve value for sequence '" + sequence + "': " + e.getMessage(); 263 throw new DatastoreException( msg, e ); 264 } 265 return nextVal; 266 } 267 268 /** 269 * Converts the {@link StatementBuffer} into a {@link PreparedStatement}, which is initialized and ready to be 270 * performed. 271 * 272 * TODO remove this method (use super class method instead), change handling of JGeometry 273 * 274 * @param conn 275 * connection to be used to create the <code>PreparedStatement</code> 276 * @param statementBuffer 277 * @return the <code>PreparedStatment</code>, ready to be performed 278 * @throws SQLException 279 * if a JDBC related error occurs 280 */ 281 @Override 282 public PreparedStatement prepareStatement( Connection conn, StatementBuffer statementBuffer ) 283 throws SQLException { 284 LOG.logDebug( "Preparing statement: " + statementBuffer.getQueryString() ); 285 286 PreparedStatement preparedStatement = conn.prepareStatement( statementBuffer.getQueryString() ); 287 288 Iterator it = statementBuffer.getArgumentsIterator(); 289 int i = 1; 290 while ( it.hasNext() ) { 291 StatementArgument argument = (StatementArgument) it.next(); 292 Object parameter = argument.getArgument(); 293 int targetSqlType = argument.getTypeCode(); 294 if ( parameter != null ) { 295 if ( targetSqlType == Types.DATE ) { 296 if ( parameter instanceof String ) { 297 parameter = TimeTools.createCalendar( (String) parameter ).getTime(); 298 } 299 parameter = new java.sql.Date( ( (Date) parameter ).getTime() ); 300 } else if ( targetSqlType == Types.TIMESTAMP ) { 301 if ( parameter instanceof String ) { 302 parameter = TimeTools.createCalendar( (String) parameter ).getTime(); 303 } 304 parameter = new java.sql.Timestamp( ( (Date) parameter ).getTime() ); 305 } else if ( parameter != null && parameter instanceof JGeometry ) { 306 parameter = JGeometry.store( (JGeometry) parameter, conn ); 307 } else if ( targetSqlType == Types.INTEGER || targetSqlType == Types.SMALLINT 308 || targetSqlType == Types.TINYINT ) { 309 parameter = Integer.parseInt( parameter.toString() ); 310 } else if ( targetSqlType == Types.DECIMAL || targetSqlType == Types.DOUBLE 311 || targetSqlType == Types.REAL || targetSqlType == Types.FLOAT ) { 312 parameter = Double.parseDouble( parameter.toString() ); 313 } else if ( targetSqlType == Types.NUMERIC ) { 314 try { 315 parameter = Integer.parseInt( parameter.toString() ); 316 } catch ( Exception e ) { 317 parameter = Double.parseDouble( parameter.toString() ); 318 } 319 } 320 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 321 try { 322 String typeName = Types.getTypeNameForSQLTypeCode( targetSqlType ); 323 LOG.logDebug( "Setting argument " + i + ": type=" + typeName + ", value class=" 324 + parameter.getClass() ); 325 if ( parameter instanceof String || parameter instanceof Number 326 || parameter instanceof java.sql.Date ) { 327 LOG.logDebug( "Value: " + parameter ); 328 } 329 } catch ( UnknownTypeException e ) { 330 throw new SQLException( e.getMessage() ); 331 } 332 } 333 preparedStatement.setObject( i, parameter, targetSqlType ); 334 } else { 335 setNullValue( preparedStatement, i, targetSqlType ); 336 } 337 i++; 338 } 339 return preparedStatement; 340 } 341 342 /** 343 * Transforms the incoming {@link Query} so that the {@link CoordinateSystem} of all spatial arguments (BBOX, etc.) 344 * in the {@link Filter} match the SRS of the targeted {@link MappingGeometryField}s. 345 * <p> 346 * NOTE: If this transformation can be performed by the backend (e.g. by Oracle Spatial), this method should be 347 * overwritten to return the original input {@link Query}. 348 * 349 * @param query 350 * query to be transformed 351 * @return query with spatial arguments transformed to target SRS 352 */ 353 @Override 354 protected Query transformQuery( Query query ) { 355 return query; 356 } 357 358 /** 359 * Transforms the {@link FeatureCollection} so that the geometries of all contained geometry properties use the 360 * requested SRS. 361 * 362 * @param fc 363 * feature collection to be transformed 364 * @param targetSRS 365 * requested SRS 366 * @return transformed FeatureCollection 367 */ 368 @Override 369 protected FeatureCollection transformResult( FeatureCollection fc, String targetSRS ) { 370 return fc; 371 } 372 373 /** 374 * Returns whether the datastore is capable of performing a native coordinate transformation (using an SQL function 375 * call for example) into the given SRS. 376 * 377 * @param targetSRS 378 * target spatial reference system (usually "EPSG:XYZ") 379 * @return true, if the datastore can perform the coordinate transformation, false otherwise 380 */ 381 @Override 382 protected boolean canTransformTo( String targetSRS ) { 383 return getNativeSRSCode( targetSRS ) != SRS_UNDEFINED; 384 } 385 386 /** 387 * Returns an {@link SQLFunctionCall} that refers to the given {@link MappingGeometryField} in the specified target 388 * SRS using a database specific SQL function. 389 * 390 * @param geoProperty 391 * geometry property 392 * @param targetSRS 393 * target spatial reference system (usually "EPSG:XYZ") 394 * @return an {@link SQLFunctionCall} that refers to the geometry in the specified srs 395 * @throws DatastoreException 396 */ 397 @Override 398 public SQLFunctionCall buildSRSTransformCall( MappedGeometryPropertyType geoProperty, String targetSRS ) 399 throws DatastoreException { 400 401 int nativeSRSCode = getNativeSRSCode( targetSRS ); 402 if ( nativeSRSCode == SRS_UNDEFINED ) { 403 String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS", this.getClass().getName(), 404 targetSRS ); 405 throw new DatastoreException( msg ); 406 } 407 408 MappingGeometryField field = geoProperty.getMappingField(); 409 FunctionParam param1 = new FieldContent( field, new TableRelation[0] ); 410 FunctionParam param2 = new ConstantContent( "" + nativeSRSCode ); 411 412 SQLFunctionCall transformCall = new SQLFunctionCall( "SDO_CS.TRANSFORM($1,$2)", field.getType(), param1, param2 ); 413 return transformCall; 414 } 415 416 @Override 417 public String buildSRSTransformCall( String geomIdentifier, int nativeSRSCode ) 418 throws DatastoreException { 419 String call = "SDO_CS.TRANSFORM(" + geomIdentifier + "," + nativeSRSCode + ")"; 420 return call; 421 } 422 423 @Override 424 public int getNativeSRSCode( String srsName ) { 425 Integer nativeSRSCode = nativeSrsCodeMap.get( srsName ); 426 if ( nativeSRSCode == null ) { 427 return SRS_UNDEFINED; 428 } 429 return nativeSRSCode; 430 } 431 432 private void setNullValue( PreparedStatement preparedStatement, int i, int targetSqlType ) 433 throws SQLException { 434 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 435 try { 436 String typeName = Types.getTypeNameForSQLTypeCode( targetSqlType ); 437 LOG.logDebug( "Setting argument " + i + ": type=" + typeName ); 438 LOG.logDebug( "Value: null" ); 439 } catch ( UnknownTypeException e ) { 440 throw new SQLException( e.getMessage() ); 441 } 442 } 443 preparedStatement.setNull( i, targetSqlType ); 444 } 445 446 private static void initSRSCodeMap() 447 throws IOException { 448 InputStream is = OracleDatastore.class.getResourceAsStream( SRS_CODE_PROP_FILE ); 449 Properties props = new Properties(); 450 props.load( is ); 451 for ( Object key : props.keySet() ) { 452 String nativeCodeStr = props.getProperty( (String) key ).trim(); 453 try { 454 int nativeCode = Integer.parseInt( nativeCodeStr ); 455 nativeSrsCodeMap.put( (String) key, nativeCode ); 456 } catch ( NumberFormatException e ) { 457 String msg = Messages.getMessage( "DATASTORE_SRS_CODE_INVALID", SRS_CODE_PROP_FILE, nativeCodeStr, key ); 458 throw new IOException( msg ); 459 } 460 } 461 } 462 }