001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/postgis/PostGISDatastore.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     Aennchenstr. 19
030     53115 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     ---------------------------------------------------------------------------*/
044    
045    package org.deegree.io.datastore.sql.postgis;
046    
047    import java.io.IOException;
048    import java.io.InputStream;
049    import java.sql.Connection;
050    import java.sql.ResultSet;
051    import java.sql.SQLException;
052    import java.sql.Statement;
053    import java.util.HashMap;
054    import java.util.Map;
055    import java.util.Properties;
056    
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    import org.deegree.i18n.Messages;
060    import org.deegree.io.JDBCConnection;
061    import org.deegree.io.datastore.Datastore;
062    import org.deegree.io.datastore.DatastoreException;
063    import org.deegree.io.datastore.schema.MappedFeatureType;
064    import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
065    import org.deegree.io.datastore.schema.TableRelation;
066    import org.deegree.io.datastore.schema.content.ConstantContent;
067    import org.deegree.io.datastore.schema.content.FieldContent;
068    import org.deegree.io.datastore.schema.content.FunctionParam;
069    import org.deegree.io.datastore.schema.content.MappingGeometryField;
070    import org.deegree.io.datastore.schema.content.SQLFunctionCall;
071    import org.deegree.io.datastore.sql.AbstractSQLDatastore;
072    import org.deegree.io.datastore.sql.SQLDatastoreConfiguration;
073    import org.deegree.io.datastore.sql.TableAliasGenerator;
074    import org.deegree.io.datastore.sql.VirtualContentProvider;
075    import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
076    import org.deegree.model.crs.CoordinateSystem;
077    import org.deegree.model.feature.FeatureCollection;
078    import org.deegree.model.filterencoding.Filter;
079    import org.deegree.model.spatialschema.Geometry;
080    import org.deegree.model.spatialschema.GeometryException;
081    import org.deegree.ogcbase.SortProperty;
082    import org.deegree.ogcwebservices.wfs.operation.Query;
083    import org.postgis.PGgeometry;
084    import org.postgresql.PGConnection;
085    
086    /**
087     * {@link Datastore} implementation for PostGIS/PostgreSQL databases.
088     * 
089     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
090     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
091     * @author last edited by: $Author: apoth $
092     * 
093     * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
094     */
095    public class PostGISDatastore extends AbstractSQLDatastore {
096    
097        protected static final ILogger LOG = LoggerFactory.getLogger( PostGISDatastore.class );
098    
099        private static final String GEOMETRY_DATATYPE_NAME = "geometry";
100    
101        private static final String BOX3D_DATATYPE_NAME = "box3d";
102    
103        private static final String PG_GEOMETRY_CLASS_NAME = "org.postgis.PGgeometry";
104    
105        private static final String PG_BOX3D_CLASS_NAME = "org.postgis.PGbox3d";
106    
107        private static Class pgGeometryClass;
108    
109        private static Class pgBox3dClass;
110    
111        private static final String SRS_CODE_PROP_FILE = "srs_codes_postgis.properties";
112    
113        private static Map<String, Integer> nativeSrsCodeMap = new HashMap<String, Integer>();
114    
115        private static final int SRS_UNDEFINED = -1;
116    
117        static {
118            try {
119                pgGeometryClass = Class.forName( PG_GEOMETRY_CLASS_NAME );
120            } catch ( ClassNotFoundException e ) {
121                LOG.logError( "Cannot find class '" + PG_GEOMETRY_CLASS_NAME + "'.", e );
122            }
123            try {
124                pgBox3dClass = Class.forName( PG_BOX3D_CLASS_NAME );
125            } catch ( ClassNotFoundException e ) {
126                LOG.logError( "Cannot find class '" + PG_BOX3D_CLASS_NAME + "'.", e );
127            }
128            try {
129                initSRSCodeMap();
130            } catch ( IOException e ) {
131                String msg = "Cannot load native srs code file '" + SRS_CODE_PROP_FILE + "'.";
132                LOG.logError( msg, e );
133            }
134        }
135    
136        /**
137         * Returns a specific {@link WhereBuilder} implementation for PostGIS.
138         * 
139         * @param rootFts
140         *            involved (requested) feature types
141         * @param aliases
142         *            aliases for the feature types, may be null
143         * @param filter
144         *            filter that restricts the matched features
145         * @param sortProperties
146         *            sort criteria for the result, may be null or empty
147         * @param aliasGenerator
148         *            used to generate unique table aliases
149         * @param vcProvider
150         * @return <code>WhereBuilder</code> implementation for PostGIS
151         * @throws DatastoreException
152         */
153        @Override
154        public PostGISWhereBuilder getWhereBuilder( MappedFeatureType[] rootFts, String[] aliases, Filter filter,
155                                                    SortProperty[] sortProperties, TableAliasGenerator aliasGenerator,
156                                                    VirtualContentProvider vcProvider )
157                                throws DatastoreException {
158            return new PostGISWhereBuilder( rootFts, aliases, filter, sortProperties, aliasGenerator, vcProvider );
159        }
160    
161        /**
162         * Converts a PostGIS specific geometry <code>Object</code> from the <code>ResultSet</code>
163         * to a deegree <code>Geometry</code>.
164         * 
165         * @param value
166         * @param targetCS
167         * @param conn
168         * @return corresponding deegree geometry
169         * @throws SQLException
170         */
171        @Override
172        public Geometry convertDBToDeegreeGeometry( Object value, CoordinateSystem targetCS, Connection conn )
173                                throws SQLException {
174            Geometry geometry = null;
175            if ( value != null && value instanceof PGgeometry ) {
176                try {
177                    LOG.logDebug( "Converting PostGIS geometry to deegree geometry ('" + targetCS.getName() + "')" );
178                    geometry = PGgeometryAdapter.wrap( (PGgeometry) value, targetCS );
179                } catch ( Exception e ) {
180                    throw new SQLException( "Error converting PostGIS geometry to deegree geometry: " + e.getMessage() );
181                }
182            }
183            return geometry;
184        }
185    
186        /**
187         * Converts a deegree <code>Geometry</code> to a PostGIS specific geometry object.
188         * 
189         * @param geometry
190         * @param targetSRS
191         * @param conn
192         * @return corresponding PostGIS specific geometry object
193         * @throws DatastoreException
194         */
195        @Override
196        public PGgeometry convertDeegreeToDBGeometry( Geometry geometry, int targetSRS, Connection conn )
197                                throws DatastoreException {
198            PGgeometry pgGeometry;
199            try {
200                pgGeometry = PGgeometryAdapter.export( geometry, targetSRS );
201            } catch ( GeometryException e ) {
202                throw new DatastoreException( "Error converting deegree geometry to PostGIS geometry: " + e.getMessage(), e );
203            }
204            return pgGeometry;
205        }
206    
207        @Override
208        protected Connection acquireConnection()
209                                throws DatastoreException {
210            JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
211            Connection conn = null;
212            try {
213                conn = pool.acquireConnection( jdbcConnection.getDriver(), jdbcConnection.getURL(),
214                                               jdbcConnection.getUser(), jdbcConnection.getPassword() );
215                PGConnection pgConn = (PGConnection) conn;
216                pgConn.addDataType( GEOMETRY_DATATYPE_NAME, pgGeometryClass );
217                pgConn.addDataType( BOX3D_DATATYPE_NAME, pgBox3dClass );
218            } catch ( Exception e ) {
219                String msg = "Cannot acquire database connection: " + e.getMessage();
220                LOG.logInfo( msg );
221                throw new DatastoreException( msg, e );
222            }
223            return conn;
224        }
225    
226        /**
227         * Returns the next value of the given SQL sequence.
228         * 
229         * @param conn
230         *            JDBC connection to be used
231         * @param sequence
232         *            name of the SQL sequence
233         * @return next value of the given SQL sequence
234         * @throws DatastoreException
235         *             if the value could not be retrieved
236         */
237        @Override
238        public Object getSequenceNextVal( Connection conn, String sequence )
239                                throws DatastoreException {
240    
241            Object nextVal = null;
242            Statement stmt = null;
243            ResultSet rs = null;
244    
245            try {
246                try {
247                    stmt = conn.createStatement();
248                    rs = stmt.executeQuery( "SELECT NEXTVAL('" + sequence + "')" );
249                    if ( rs.next() ) {
250                        nextVal = rs.getObject( 1 );
251                    }
252                } finally {
253                    try {
254                        if ( rs != null ) {
255                            rs.close();
256                        }
257                    } finally {
258                        if ( stmt != null ) {
259                            stmt.close();
260                        }
261                    }
262                }
263            } catch ( SQLException e ) {
264                String msg = "Could not retrieve value for sequence '" + sequence + "': " + e.getMessage();
265                throw new DatastoreException( msg, e );
266            }
267            return nextVal;
268        }
269    
270        /**
271         * Transforms the incoming {@link Query} so that the {@link CoordinateSystem} of all spatial
272         * arguments (BBOX, etc.) in the {@link Filter} match the SRS of the targeted
273         * {@link MappingGeometryField}s.
274         * <p>
275         * NOTE: If this transformation can be performed by the backend (e.g. by Oracle Spatial), this
276         * method should be overwritten to return the original input {@link Query}.
277         * 
278         * @param query
279         *            query to be transformed
280         * @return query with spatial arguments transformed to target SRS
281         */
282        @Override
283        protected Query transformQuery( Query query ) {
284            return query;
285        }
286    
287        /**
288         * Transforms the {@link FeatureCollection} so that the geometries of all contained geometry
289         * properties use the requested SRS.
290         * 
291         * @param fc
292         *            feature collection to be transformed
293         * @param targetSRS
294         *            requested SRS
295         * @return transformed FeatureCollection
296         */
297        @Override
298        protected FeatureCollection transformResult( FeatureCollection fc, String targetSRS ) {
299            return fc;
300        }
301    
302        /**
303         * Returns whether the datastore is capable of performing a native coordinate transformation
304         * (using an SQL function call for example) into the given SRS.
305         * 
306         * @param targetSRS
307         *            target spatial reference system (usually "EPSG:XYZ")
308         * @return true, if the datastore can perform the coordinate transformation, false otherwise
309         */
310        @Override
311        protected boolean canTransformTo( String targetSRS ) {
312            return getNativeSRSCode( targetSRS ) != SRS_UNDEFINED;
313        }
314    
315        /**
316         * Returns an {@link SQLFunctionCall} that refers to the given {@link MappingGeometryField} in
317         * the specified target SRS using a database specific SQL function.
318         * 
319         * @param geoProperty
320         *            geometry property
321         * @param targetSRS
322         *            target spatial reference system (usually "EPSG:XYZ")
323         * @return an {@link SQLFunctionCall} that refers to the geometry in the specified srs
324         * @throws DatastoreException
325         */
326        @Override
327        public SQLFunctionCall buildSRSTransformCall( MappedGeometryPropertyType geoProperty, String targetSRS )
328                                throws DatastoreException {
329    
330            int nativeSRSCode = getNativeSRSCode( targetSRS );
331            if ( nativeSRSCode == SRS_UNDEFINED ) {
332                String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS", this.getClass().getName(),
333                                                  targetSRS );
334                throw new DatastoreException( msg );
335            }
336    
337            MappingGeometryField field = geoProperty.getMappingField();
338            FunctionParam param1 = new FieldContent( field, new TableRelation[0] );
339            FunctionParam param2 = new ConstantContent( "" + nativeSRSCode );
340    
341            SQLFunctionCall transformCall = new SQLFunctionCall( "transform($1,$2)", field.getType(), param1, param2 );
342            return transformCall;
343        }
344    
345        @Override
346        public String buildSRSTransformCall( String geomIdentifier, int nativeSRSCode )
347                                throws DatastoreException {
348            String call = "transform(" + geomIdentifier + "," + nativeSRSCode + ")";
349            return call;
350        }
351    
352        @Override
353        public int getNativeSRSCode( String srsName ) {
354            Integer nativeSRSCode = nativeSrsCodeMap.get( srsName );
355            if ( nativeSRSCode == null ) {
356                return SRS_UNDEFINED;
357            }
358            return nativeSRSCode;
359        }
360    
361        private static void initSRSCodeMap()
362                                throws IOException {
363            InputStream is = PostGISDatastore.class.getResourceAsStream( SRS_CODE_PROP_FILE );
364            Properties props = new Properties();
365            props.load( is );
366            for ( Object key : props.keySet() ) {
367                String nativeCodeStr = props.getProperty( (String) key ).trim();
368                try {
369                    int nativeCode = Integer.parseInt( nativeCodeStr );
370                    nativeSrsCodeMap.put( (String) key, nativeCode );
371                } catch ( NumberFormatException e ) {
372                    String msg = Messages.getMessage( "DATASTORE_SRS_CODE_INVALID", SRS_CODE_PROP_FILE, nativeCodeStr, key );
373                    throw new IOException( msg );
374                }
375            }
376        }
377    }