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