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