001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/sql/oracle/OracleDatastore.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    
037    package org.deegree.io.datastore.sql.oracle;
038    
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.lang.reflect.Field;
042    import java.sql.Connection;
043    import java.sql.PreparedStatement;
044    import java.sql.ResultSet;
045    import java.sql.SQLException;
046    import java.sql.Statement;
047    import java.util.Date;
048    import java.util.HashMap;
049    import java.util.Iterator;
050    import java.util.Map;
051    import java.util.Properties;
052    
053    import oracle.spatial.geometry.JGeometry;
054    import oracle.sql.CLOB;
055    import oracle.sql.STRUCT;
056    import oracle.sql.TIMESTAMP;
057    
058    import org.deegree.datatypes.Types;
059    import org.deegree.datatypes.UnknownTypeException;
060    import org.deegree.framework.log.ILogger;
061    import org.deegree.framework.log.LoggerFactory;
062    import org.deegree.framework.util.FileUtils;
063    import org.deegree.framework.util.TimeTools;
064    import org.deegree.i18n.Messages;
065    import org.deegree.io.JDBCConnection;
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.SQLDatastoreConfiguration;
078    import org.deegree.io.datastore.sql.StatementBuffer;
079    import org.deegree.io.datastore.sql.TableAliasGenerator;
080    import org.deegree.io.datastore.sql.VirtualContentProvider;
081    import org.deegree.io.datastore.sql.StatementBuffer.StatementArgument;
082    import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
083    import org.deegree.model.crs.CoordinateSystem;
084    import org.deegree.model.feature.FeatureCollection;
085    import org.deegree.model.filterencoding.Filter;
086    import org.deegree.model.spatialschema.Geometry;
087    import org.deegree.model.spatialschema.GeometryException;
088    import org.deegree.ogcbase.SortProperty;
089    import org.deegree.ogcwebservices.wfs.operation.Query;
090    
091    /**
092     * {@link Datastore} implementation for Oracle Spatial database systems. Supports Oracle Spatial for Oracle 10g.
093     * 
094     * TODO Which Oracle spatial versions are supported exactly?
095     * 
096     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
097     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
098     * @author last edited by: $Author: apoth $
099     * 
100     * @version $Revision: 27361 $, $Date: 2010-10-18 20:38:36 +0200 (Mo, 18 Okt 2010) $
101     */
102    public class OracleDatastore extends AbstractSQLDatastore {
103    
104        private static final ILogger LOG = LoggerFactory.getLogger( OracleDatastore.class );
105    
106        private static final String SRS_CODE_PROP_FILE = "srs_codes_oracle.properties";
107    
108        private static Map<String, Integer> nativeSrsCodeMap = new HashMap<String, Integer>();
109    
110        // used for identifying the last active connection (if changed, Reijer's JGeometry store workaround patch is
111        // applied)
112        private static Connection lastStoreConnection;
113    
114        static {
115            try {
116                initSRSCodeMap();
117            } catch ( IOException e ) {
118                String msg = "Cannot load native srs code file '" + SRS_CODE_PROP_FILE + "'.";
119                LOG.logError( msg, e );
120            }
121        }
122    
123        /**
124         * Returns the database connection requested for.
125         * 
126         * @return Connection
127         * @throws DatastoreException
128         */
129        protected Connection acquireConnection()
130                                throws DatastoreException {
131            JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
132            Connection conn = null;
133            try {
134                Properties props = new Properties();
135                props.put( "user", jdbcConnection.getUser() );
136                props.put( "password", jdbcConnection.getPassword() );
137                props.put( "SetBigStringTryClob", "true" );
138                conn = pool.acquireConnection( jdbcConnection.getDriver(), jdbcConnection.getURL(), props );
139            } catch ( Exception e ) {
140                String msg = "Cannot acquire database connection: " + e.getMessage();
141                LOG.logError( msg, e );
142                throw new DatastoreException( msg, e );
143            }
144            return conn;
145        }
146    
147        /**
148         * Releases the connection.
149         * 
150         * @param conn
151         *            connection to be released.
152         * @throws DatastoreException
153         */
154        public void releaseConnection( Connection conn )
155                                throws DatastoreException {
156            LOG.logDebug( "Releasing JDBCConnection." );
157            JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
158            try {
159                Properties props = new Properties();
160                props.put( "user", jdbcConnection.getUser() );
161                props.put( "password", jdbcConnection.getPassword() );
162                props.put( "SetBigStringTryClob", "true" );
163                pool.releaseConnection( conn, jdbcConnection.getDriver(), jdbcConnection.getURL(), props );
164            } catch ( Exception e ) {
165                String msg = "Cannot release database connection: " + e.getMessage();
166                LOG.logError( msg, e );
167                throw new DatastoreException( msg, e );
168            }
169        }
170    
171        /**
172         * @param code
173         *            an EPSG code
174         * @return the oracle code as stored in srs_codes_oracle.properties
175         */
176        public static int getOracleSRIDCode( String code ) {
177            Integer res = nativeSrsCodeMap.get( code );
178            if ( res != null ) {
179                return res.intValue();
180            }
181    
182            // only in Oracle 10, but what else to do?
183            return Integer.parseInt( code.split( ":" )[1] );
184        }
185    
186        /**
187         * @param srid
188         * @return an EPSG code or "-1", if none was found
189         */
190        public static String fromOracleSRIDCode( int srid ) {
191            for ( String k : nativeSrsCodeMap.keySet() ) {
192                if ( nativeSrsCodeMap.get( k ).intValue() == srid ) {
193                    return k;
194                }
195            }
196    
197            return "-1";
198        }
199    
200        /**
201         * Returns a specific {@link WhereBuilder} implementation for Oracle Spatial.
202         * 
203         * @param rootFts
204         *            involved (requested) feature types
205         * @param aliases
206         *            aliases for the feature types, may be null
207         * @param filter
208         *            filter that restricts the matched features
209         * @param sortProperties
210         *            sort criteria for the result, may be null or empty
211         * @param aliasGenerator
212         *            used to generate unique table aliases
213         * @param vcProvider
214         * @return <code>WhereBuilder</code> implementation for Oracle Spatial
215         * @throws DatastoreException
216         */
217        @Override
218        public WhereBuilder getWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter,
219                                             SortProperty[] sortProperties, TableAliasGenerator aliasGenerator,
220                                             VirtualContentProvider vcProvider )
221                                throws DatastoreException {
222            return new OracleSpatialWhereBuilder( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider );
223        }
224    
225        /**
226         * Converts an Oracle specific geometry <code>Object</code> from the <code>ResultSet</code> to a deegree
227         * <code>Geometry</code>.
228         * 
229         * @param value
230         * @param targetCS
231         * @param conn
232         * @return corresponding deegree geometry
233         * @throws SQLException
234         */
235        @Override
236        public Geometry convertDBToDeegreeGeometry( Object value, CoordinateSystem targetCS, Connection conn )
237                                throws SQLException {
238            Geometry geometry = null;
239            if ( value != null ) {
240                LOG.logDebug( "Converting STRUCT to JGeometry." );
241                JGeometry jGeometry = JGeometry.load( (STRUCT) value );
242                try {
243                    LOG.logDebug( "Converting JGeometry to deegree geometry ('" + targetCS + "')" );
244                    geometry = JGeometryAdapter.wrap( jGeometry, targetCS );
245                } catch ( Exception e ) {
246                    LOG.logError( "Error while converting STRUCT to Geometry: ", e );
247                    throw new SQLException( "Error converting STRUCT to Geometry: " + e.getMessage() );
248                }
249            }
250            return geometry;
251        }
252    
253        /**
254         * Converts a deegree <code>Geometry</code> to an Oracle specific geometry object.
255         * 
256         * @param geometry
257         * @param nativeSRSCode
258         * @param conn
259         * @return corresponding Oracle specific geometry object
260         * @throws DatastoreException
261         */
262        @Override
263        public STRUCT convertDeegreeToDBGeometry( Geometry geometry, int nativeSRSCode, Connection conn )
264                                throws DatastoreException {
265    
266            JGeometry jGeometry = null;
267            LOG.logDebug( "Converting deegree geometry to JGeometry." );
268            try {
269                jGeometry = JGeometryAdapter.export( geometry, nativeSRSCode );
270            } catch ( GeometryException e ) {
271                throw new DatastoreException( "Error converting deegree geometry to JGeometry: " + e.getMessage(), e );
272            }
273    
274            LOG.logDebug( "Converting JGeometry to STRUCT." );
275            STRUCT struct = null;
276            try {
277                struct = storeGeometryWithMultiConnHack( jGeometry, conn );
278            } catch ( SQLException e ) {
279                throw new DatastoreException( "Error converting JGeometry to STRUCT: " + e.getMessage(), e );
280            }
281            return struct;
282        }
283    
284        /**
285         * Workaround for a known Oracle JDBC driver problem.
286         * <p>
287         * JGeometry#store() isn't working when invoked successively using different connections. This method applies a
288         * workaround (based on undocumented behaviour of the Oracle driver) to solve this problem.
289         * http://forums.oracle.com/forums/thread.jspa?messageID=1273670
290         * </p>
291         * 
292         * @param geometry
293         *            geometry to be stored
294         * @param connection
295         *            jdbc connection
296         * @return a {@link STRUCT} to be used as query parameter
297         * @throws SQLException
298         */
299        private STRUCT storeGeometryWithMultiConnHack( JGeometry geometry, Connection connection )
300                                throws SQLException {
301            synchronized ( JGeometry.class ) {
302                if ( lastStoreConnection != null && lastStoreConnection != connection ) {
303                    LOG.logDebug( "JGeometry#store(...) workaround (lastStoreConnection != connection)" );
304                    try {
305                        Field geomDesc = JGeometry.class.getDeclaredField( "geomDesc" );
306                        geomDesc.setAccessible( true );
307                        geomDesc.set( null, null );
308                    } catch ( Exception e ) {
309                        LOG.logWarning( "Exception caught applying JGeometr#store(...) workaround: " + e.getMessage(), e );
310                    }
311                }
312                lastStoreConnection = connection;
313                return JGeometry.store( geometry, connection );
314            }
315        }
316    
317        /**
318         * Converts the given object from a <code>java.sql.ResultSet</code> column to the common type to be used as a
319         * feature property.
320         * <p>
321         * NOTE: String- and boolean-valued results have a special conversion handling:
322         * <ul>
323         * <li><code>Strings:</code> because we encountered difficulties when inserting empty strings "" into String-type
324         * columns with NOT NULL constraints (for example in VARCHAR2 fields), "$EMPTY_STRING$" is used to mark them.</li>
325         * <li><code>Boolean:<code>because Oracle has no special boolean type, it is assumed that a CHAR(1) column is used
326         * instead (with values 'Y'=true and 'N'=false)</li>
327         * </ul>
328         * 
329         * @param rsObject
330         * @param sqlTypeCode
331         * @return an object that is suitable for a table column of the specified SQL type
332         * @throws DatastoreException
333         */
334        @Override
335        public Object convertFromDBType( Object rsObject, int sqlTypeCode )
336                                throws DatastoreException {
337            Object propertyValue = rsObject;
338            try {
339                if ( rsObject instanceof TIMESTAMP ) {
340                    propertyValue = ( (TIMESTAMP) rsObject ).timestampValue();
341                } else if ( rsObject instanceof String ) {
342                    if ( rsObject.equals( "$EMPTY_STRING$" ) ) {
343                        propertyValue = "";
344                    }
345                    if ( sqlTypeCode == Types.BOOLEAN ) {
346                        String val = rsObject.toString();
347    
348                        if ( val.length() == 1 && val.charAt( 0 ) == 'Y' ) {
349                            propertyValue = Boolean.TRUE;
350                        }
351                        if ( val.length() == 1 && val.charAt( 0 ) == 'N' ) {
352                            propertyValue = Boolean.FALSE;
353                        }
354                    }
355                } else if ( rsObject instanceof oracle.sql.CLOB ) {
356                    try {
357                        propertyValue = FileUtils.readTextFile( ( (CLOB) rsObject ).getCharacterStream() ).toString();
358                    } catch ( IOException e ) {
359                        // TODO Auto-generated catch block
360                        e.printStackTrace();
361                    }
362                }
363            } catch ( SQLException e ) {
364                throw new DatastoreException( e.getMessage(), e );
365            }
366            return propertyValue;
367        }
368    
369        /**
370         * Returns the next value of the given SQL sequence.
371         * 
372         * @param conn
373         *            JDBC connection to be used.
374         * @param sequence
375         *            name of the SQL sequence
376         * @return next value of the given SQL sequence
377         * @throws DatastoreException
378         *             if the value could not be retrieved
379         */
380        @Override
381        public Object getSequenceNextVal( Connection conn, String sequence )
382                                throws DatastoreException {
383    
384            Object nextVal = null;
385            Statement stmt = null;
386            ResultSet rs = null;
387    
388            try {
389                try {
390                    stmt = conn.createStatement();
391                    rs = stmt.executeQuery( "SELECT " + sequence + ".nextval FROM dual" );
392                    if ( rs.next() ) {
393                        nextVal = rs.getObject( 1 );
394                    }
395                } finally {
396                    try {
397                        if ( rs != null ) {
398                            rs.close();
399                        }
400                    } finally {
401                        if ( stmt != null ) {
402                            stmt.close();
403                        }
404                    }
405                }
406            } catch ( SQLException e ) {
407                String msg = "Could not retrieve value for sequence '" + sequence + "': " + e.getMessage();
408                throw new DatastoreException( msg, e );
409            }
410            return nextVal;
411        }
412    
413        /**
414         * Returns the current value (plus an offset) of the given SQL sequence.
415         * 
416         * @param conn
417         *            JDBC connection to be used.
418         * @param sequence
419         *            name of the SQL sequence
420         * @param offset
421         *            offset added to the sequence value
422         * @return current value (plus offset) of the given SQL sequence
423         * @throws DatastoreException
424         *             if the value could not be retrieved
425         */
426        @Override
427        public Object getSequenceCurrValPlusOffset( Connection conn, String sequence, int offset )
428                                throws DatastoreException {
429    
430            Object nextVal = null;
431            Statement stmt = null;
432            ResultSet rs = null;
433    
434            try {
435                try {
436                    stmt = conn.createStatement();
437                    rs = stmt.executeQuery( "SELECT " + sequence + ".currval + " + offset + " FROM dual" );
438                    if ( rs.next() ) {
439                        nextVal = rs.getObject( 1 );
440                    }
441                } finally {
442                    try {
443                        if ( rs != null ) {
444                            rs.close();
445                        }
446                    } finally {
447                        if ( stmt != null ) {
448                            stmt.close();
449                        }
450                    }
451                }
452            } catch ( SQLException e ) {
453                String msg = "Could not retrieve value for sequence '" + sequence + "': " + e.getMessage();
454                throw new DatastoreException( msg, e );
455            }
456            return nextVal;
457        }
458    
459        /**
460         * Converts the {@link StatementBuffer} into a {@link PreparedStatement}, which is initialized and ready to be
461         * performed.
462         * 
463         * TODO remove this method (use super class method instead), change handling of JGeometry NOTE: String- and
464         * boolean-valued results have a special conversion handling:
465         * <ul>
466         * <li><code>Strings:</code> because we encountered difficulties when inserting empty strings "" into String-type
467         * columns with NOT NULL constraints (for example in VARCHAR2 fields), "$EMPTY_STRING$" is used to mark them.</li>
468         * <li><code>Boolean:<code>because Oracle has no special boolean type, it is assumed that a CHAR(1) column is used
469         * instead (with values 'Y'=true and 'N'=false)</li>
470         * </ul>
471         * 
472         * @param conn
473         *            connection to be used to create the <code>PreparedStatement</code>
474         * @param statementBuffer
475         * @return the <code>PreparedStatment</code>, ready to be performed
476         * @throws SQLException
477         *             if a JDBC related error occurs
478         */
479        @Override
480        public PreparedStatement prepareStatement( Connection conn, StatementBuffer statementBuffer )
481                                throws SQLException {
482            LOG.logDebug( "Preparing statement: " + statementBuffer.getQueryString() );
483    
484            PreparedStatement preparedStatement = conn.prepareStatement( statementBuffer.getQueryString() );
485    
486            Iterator<StatementArgument> it = statementBuffer.getArgumentsIterator();
487            int i = 1;
488            while ( it.hasNext() ) {
489                StatementArgument argument = it.next();
490                Object parameter = argument.getArgument();
491                int targetSqlType = argument.getTypeCode();
492                if ( parameter != null ) {
493                    if ( targetSqlType == Types.DATE ) {
494                        if ( parameter instanceof String ) {
495                            parameter = TimeTools.createCalendar( (String) parameter ).getTime();
496                        }
497                        parameter = new java.sql.Date( ( (Date) parameter ).getTime() );
498                    } else if ( targetSqlType == Types.TIMESTAMP ) {
499                        if ( parameter instanceof String ) {
500                            parameter = TimeTools.createCalendar( (String) parameter ).getTime();
501                        }
502                        parameter = new java.sql.Timestamp( ( (Date) parameter ).getTime() );
503                    } else if ( parameter != null && parameter instanceof JGeometry ) {
504                        parameter = storeGeometryWithMultiConnHack( (JGeometry) parameter, conn );
505                    } else if ( targetSqlType == Types.INTEGER || targetSqlType == Types.SMALLINT
506                                || targetSqlType == Types.TINYINT ) {
507                        parameter = Integer.parseInt( parameter.toString() );
508                    } else if ( targetSqlType == Types.DECIMAL || targetSqlType == Types.DOUBLE
509                                || targetSqlType == Types.REAL || targetSqlType == Types.FLOAT ) {
510                        parameter = Double.parseDouble( parameter.toString() );
511                    } else if ( targetSqlType == Types.NUMERIC ) {
512                        try {
513                            parameter = Integer.parseInt( parameter.toString() );
514                        } catch ( Exception e ) {
515                            parameter = Double.parseDouble( parameter.toString() );
516                        }
517                    } else if ( targetSqlType == Types.BOOLEAN ) {
518                        // Oracle does not have a BOOLEAN datatype
519                        // default maping to column of type CHAR(1)
520                        // http://thinkoracle.blogspot.com/2005/07/oracle-boolean.html
521                        targetSqlType = Types.CHAR;
522                        if ( Boolean.parseBoolean( parameter.toString() ) ) {
523                            parameter = "Y";
524                        } else {
525                            parameter = "N";
526                        }
527                    } else if ( parameter instanceof String ) {
528                        // Using the empty string ("") for NOT NULL columns fails
529                        // (at least using PreparedStatements)
530                        // TODO implement a proper solution
531                        if ( ( (String) parameter ).length() == 0 ) {
532                            parameter = "$EMPTY_STRING$";
533                        }
534                    }
535                    if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
536                        try {
537                            String typeName = Types.getTypeNameForSQLTypeCode( targetSqlType );
538                            LOG.logDebug( "Setting argument " + i + ": type=" + typeName + ", value class="
539                                          + parameter.getClass() );
540                            if ( parameter instanceof String || parameter instanceof Number
541                                 || parameter instanceof java.sql.Date ) {
542                                LOG.logDebug( "Value: '" + parameter + "'" );
543                            }
544                        } catch ( UnknownTypeException e ) {
545                            throw new SQLException( e.getMessage() );
546                        }
547                    }
548                    preparedStatement.setObject( i, parameter, targetSqlType );
549                } else {
550                    setNullValue( preparedStatement, i, targetSqlType );
551                }
552                i++;
553            }
554            return preparedStatement;
555        }
556    
557        /**
558         * Transforms the incoming {@link Query} so that the {@link CoordinateSystem} of all spatial arguments (BBOX, etc.)
559         * in the {@link Filter} match the SRS of the targeted {@link MappingGeometryField}s.
560         * <p>
561         * NOTE: If this transformation can be performed by the backend (e.g. by Oracle Spatial), this method should be
562         * overwritten to return the original input {@link Query}.
563         * 
564         * @param query
565         *            query to be transformed
566         * @return query with spatial arguments transformed to target SRS
567         */
568        @Override
569        protected Query transformQuery( Query query ) {
570            return query;
571        }
572    
573        /**
574         * Transforms the {@link FeatureCollection} so that the geometries of all contained geometry properties use the
575         * requested SRS.
576         * 
577         * @param fc
578         *            feature collection to be transformed
579         * @param targetSRS
580         *            requested SRS
581         * @return transformed FeatureCollection
582         */
583        @Override
584        protected FeatureCollection transformResult( FeatureCollection fc, String targetSRS ) {
585            return fc;
586        }
587    
588        /**
589         * Returns whether the datastore is capable of performing a native coordinate transformation (using an SQL function
590         * call for example) into the given SRS.
591         * 
592         * @param targetSRS
593         *            target spatial reference system (usually "EPSG:XYZ")
594         * @return true, if the datastore can perform the coordinate transformation, false otherwise
595         */
596        @Override
597        protected boolean canTransformTo( String targetSRS ) {
598            return getNativeSRSCode( targetSRS ) != SRS_UNDEFINED;
599        }
600    
601        /**
602         * Returns an {@link SQLFunctionCall} that refers to the given {@link MappingGeometryField} in the specified target
603         * SRS using a database specific SQL function.
604         * 
605         * @param geoProperty
606         *            geometry property
607         * @param targetSRS
608         *            target spatial reference system (usually "EPSG:XYZ")
609         * @return an {@link SQLFunctionCall} that refers to the geometry in the specified srs
610         * @throws DatastoreException
611         */
612        @Override
613        public SQLFunctionCall buildSRSTransformCall( MappedGeometryPropertyType geoProperty, String targetSRS )
614                                throws DatastoreException {
615    
616            int nativeSRSCode = getNativeSRSCode( targetSRS );
617            if ( nativeSRSCode == SRS_UNDEFINED ) {
618                String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS", this.getClass().getName(),
619                                                  targetSRS );
620                throw new DatastoreException( msg );
621            }
622    
623            MappingGeometryField field = geoProperty.getMappingField();
624            FunctionParam param1 = new FieldContent( field, new TableRelation[0] );
625            FunctionParam param2 = new ConstantContent( "" + nativeSRSCode );
626    
627            SQLFunctionCall transformCall = new SQLFunctionCall( "SDO_CS.TRANSFORM($1,$2)", field.getType(), param1, param2 );
628            return transformCall;
629        }
630    
631        @Override
632        public String buildSRSTransformCall( String geomIdentifier, int nativeSRSCode )
633                                throws DatastoreException {
634            String call = "SDO_CS.TRANSFORM(" + geomIdentifier + "," + nativeSRSCode + ")";
635            return call;
636        }
637    
638        @Override
639        public int getNativeSRSCode( String srsName ) {
640            Integer nativeSRSCode = nativeSrsCodeMap.get( srsName );
641            if ( nativeSRSCode == null ) {
642                return SRS_UNDEFINED;
643            }
644            return nativeSRSCode;
645        }
646    
647        private void setNullValue( PreparedStatement preparedStatement, int i, int targetSqlType )
648                                throws SQLException {
649            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
650                try {
651                    String typeName = Types.getTypeNameForSQLTypeCode( targetSqlType );
652                    LOG.logDebug( "Setting argument " + i + ": type=" + typeName );
653                    LOG.logDebug( "Value: null" );
654                } catch ( UnknownTypeException e ) {
655                    throw new SQLException( e.getMessage() );
656                }
657            }
658            preparedStatement.setNull( i, targetSqlType );
659        }
660    
661        private static void initSRSCodeMap()
662                                throws IOException {
663            InputStream is = OracleDatastore.class.getResourceAsStream( SRS_CODE_PROP_FILE );
664            Properties props = new Properties();
665            props.load( is );
666            for ( Object key : props.keySet() ) {
667                String nativeCodeStr = props.getProperty( (String) key ).trim();
668                try {
669                    int nativeCode = Integer.parseInt( nativeCodeStr );
670                    nativeSrsCodeMap.put( (String) key, nativeCode );
671                } catch ( NumberFormatException e ) {
672                    String msg = Messages.getMessage( "DATASTORE_SRS_CODE_INVALID", SRS_CODE_PROP_FILE, nativeCodeStr, key );
673                    throw new IOException( msg );
674                }
675            }
676        }
677    }