//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/datastore/sql/postgis/PostGISDatastore.java,v 1.32 2006/11/29 16:59:54 mschneider Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Jens Fitzke
lat/lon GmbH
Aennchenstraße 19
53177 Bonn
Germany
E-Mail: jens.fitzke@uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.io.datastore.sql.postgis;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.i18n.Messages;
import org.deegree.io.JDBCConnection;
import org.deegree.io.datastore.Datastore;
import org.deegree.io.datastore.DatastoreException;
import org.deegree.io.datastore.schema.MappedFeatureType;
import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
import org.deegree.io.datastore.schema.TableRelation;
import org.deegree.io.datastore.schema.content.ConstantContent;
import org.deegree.io.datastore.schema.content.FieldContent;
import org.deegree.io.datastore.schema.content.FunctionParam;
import org.deegree.io.datastore.schema.content.MappingGeometryField;
import org.deegree.io.datastore.schema.content.SQLFunctionCall;
import org.deegree.io.datastore.sql.AbstractSQLDatastore;
import org.deegree.io.datastore.sql.SQLDatastoreConfiguration;
import org.deegree.io.datastore.sql.TableAliasGenerator;
import org.deegree.io.datastore.sql.VirtualContentProvider;
import org.deegree.io.datastore.sql.wherebuilder.WhereBuilder;
import org.deegree.model.crs.CoordinateSystem;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.filterencoding.Filter;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.model.spatialschema.GeometryException;
import org.deegree.ogcbase.SortProperty;
import org.deegree.ogcwebservices.wfs.operation.Query;
import org.postgis.PGgeometry;
import org.postgresql.PGConnection;
/**
* {@link Datastore} implementation for PostGIS/PostgreSQL databases.
*
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
* @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.32 $, $Date: 2006/11/29 16:59:54 $
*/
public class PostGISDatastore extends AbstractSQLDatastore {
protected static final ILogger LOG = LoggerFactory.getLogger( PostGISDatastore.class );
private static final String GEOMETRY_DATATYPE_NAME = "geometry";
private static final String BOX3D_DATATYPE_NAME = "box3d";
private static final String PG_GEOMETRY_CLASS_NAME = "org.postgis.PGgeometry";
private static final String PG_BOX3D_CLASS_NAME = "org.postgis.PGbox3d";
private static Class pgGeometryClass;
private static Class pgBox3dClass;
private static final String SRS_CODE_PROP_FILE = "srs_codes_postgis.properties";
private static Map<String, Integer> nativeSrsCodeMap = new HashMap<String, Integer>();
private static final int SRS_UNDEFINED = -1;
static {
try {
pgGeometryClass = Class.forName( PG_GEOMETRY_CLASS_NAME );
} catch ( ClassNotFoundException e ) {
LOG.logError( "Cannot find class '" + PG_GEOMETRY_CLASS_NAME + "'.", e );
}
try {
pgBox3dClass = Class.forName( PG_BOX3D_CLASS_NAME );
} catch ( ClassNotFoundException e ) {
LOG.logError( "Cannot find class '" + PG_BOX3D_CLASS_NAME + "'.", e );
}
try {
initSRSCodeMap();
} catch ( IOException e ) {
String msg = "Cannot load native srs code file '" + SRS_CODE_PROP_FILE + "'.";
LOG.logError( msg, e );
}
}
/**
* Returns a specific {@link WhereBuilder} implementation for PostGIS.
*
* @param ft
* requested feature type
* @param filter
* filter that restricts the matched features
* @param sortProperties
* sort criteria for the result, may be null or empty
* @param aliasGenerator
* used to generate unique table aliases
* @param vcProvider
* @return <code>WhereBuilder</code> implementation for PostGIS
* @throws DatastoreException
*/
@Override
public PostGISWhereBuilder getWhereBuilder( MappedFeatureType ft, Filter filter,
SortProperty[] sortProperties,
TableAliasGenerator aliasGenerator,
VirtualContentProvider vcProvider )
throws DatastoreException {
return new PostGISWhereBuilder( ft, filter, sortProperties, aliasGenerator, vcProvider );
}
/**
* Converts a PostGIS specific geometry <code>Object</code> from the <code>ResultSet</code>
* to a deegree <code>Geometry</code>.
*
* @param value
* @param targetCS
* @param conn
* @return corresponding deegree geometry
* @throws SQLException
*/
@Override
public Geometry convertDBToDeegreeGeometry( Object value, CoordinateSystem targetCS,
Connection conn )
throws SQLException {
Geometry geometry = null;
if ( value != null && value instanceof PGgeometry ) {
try {
LOG.logDebug( "Converting PostGIS geometry to deegree geometry ('"
+ targetCS.getName() + "')" );
geometry = PGgeometryAdapter.wrap( (PGgeometry) value, targetCS );
} catch ( Exception e ) {
throw new SQLException( "Error converting PostGIS geometry to deegree geometry: "
+ e.getMessage() );
}
}
return geometry;
}
/**
* Converts a deegree <code>Geometry</code> to a PostGIS specific geometry object.
*
* @param geometry
* @param targetSRS
* @param conn
* @return corresponding PostGIS specific geometry object
* @throws DatastoreException
*/
@Override
public PGgeometry convertDeegreeToDBGeometry( Geometry geometry, int targetSRS,
Connection conn )
throws DatastoreException {
PGgeometry pgGeometry;
try {
pgGeometry = PGgeometryAdapter.export( geometry, targetSRS );
} catch ( GeometryException e ) {
throw new DatastoreException( "Error converting deegree geometry to PostGIS geometry: "
+ e.getMessage(), e );
}
return pgGeometry;
}
@Override
protected Connection acquireConnection()
throws DatastoreException {
JDBCConnection jdbcConnection = ( (SQLDatastoreConfiguration) this.getConfiguration() ).getJDBCConnection();
Connection conn = null;
try {
conn = pool.acquireConnection( jdbcConnection.getDriver(), jdbcConnection.getURL(),
jdbcConnection.getUser(), jdbcConnection.getPassword() );
PGConnection pgConn = (PGConnection) conn;
pgConn.addDataType( GEOMETRY_DATATYPE_NAME, pgGeometryClass );
pgConn.addDataType( BOX3D_DATATYPE_NAME, pgBox3dClass );
} catch ( Exception e ) {
String msg = "Cannot acquire database connection: " + e.getMessage();
LOG.logInfo( msg );
throw new DatastoreException( msg, e );
}
return conn;
}
/**
* Returns the next value of the given SQL sequence.
*
* @param conn
* JDBC connection to be used
* @param sequence
* name of the SQL sequence
* @return next value of the given SQL sequence
* @throws DatastoreException
* if the value could not be retrieved
*/
@Override
public Object getSequenceNextVal( Connection conn, String sequence )
throws DatastoreException {
Object nextVal = null;
Statement stmt = null;
ResultSet rs = null;
try {
try {
stmt = conn.createStatement();
rs = stmt.executeQuery( "SELECT NEXTVAL('" + sequence + "')" );
if ( rs.next() ) {
nextVal = rs.getObject( 1 );
}
} finally {
try {
if ( rs != null ) {
rs.close();
}
} finally {
if ( stmt != null ) {
stmt.close();
}
}
}
} catch ( SQLException e ) {
String msg = "Could not retrieve value for sequence '" + sequence + "': "
+ e.getMessage();
throw new DatastoreException( msg, e );
}
return nextVal;
}
/**
* Transforms the incoming {@link Query} so that the {@link CoordinateSystem} of all spatial
* arguments (BBOX, etc.) in the {@link Filter} match the SRS of the targeted
* {@link MappingGeometryField}s.
* <p>
* NOTE: If this transformation can be performed by the backend (e.g. by Oracle Spatial), this
* method should be overwritten to return the original input {@link Query}.
*
* @param query
* query to be transformed
* @return query with spatial arguments transformed to target SRS
*/
@Override
protected Query transformQuery( Query query ) {
return query;
}
/**
* Transforms the {@link FeatureCollection} so that the geometries of all contained geometry
* properties use the requested SRS.
*
* @param fc
* feature collection to be transformed
* @param targetSRS
* requested SRS
* @return transformed FeatureCollection
*/
@Override
protected FeatureCollection transformResult( FeatureCollection fc, String targetSRS ) {
return fc;
}
/**
* Returns whether the datastore is capable of performing a native coordinate transformation
* (using an SQL function call for example) into the given SRS.
*
* @param targetSRS
* target spatial reference system (usually "EPSG:XYZ")
* @return true, if the datastore can perform the coordinate transformation, false otherwise
*/
@Override
protected boolean canTransformTo( String targetSRS ) {
return getNativeSRSCode( targetSRS ) != SRS_UNDEFINED;
}
/**
* Returns an {@link SQLFunctionCall} that refers to the given {@link MappingGeometryField} in
* the specified target SRS using a database specific SQL function.
*
* @param geoProperty
* geometry property
* @param targetSRS
* target spatial reference system (usually "EPSG:XYZ")
* @return an {@link SQLFunctionCall} that refers to the geometry in the specified srs
* @throws DatastoreException
*/
@Override
public SQLFunctionCall buildSRSTransformCall( MappedGeometryPropertyType geoProperty, String targetSRS )
throws DatastoreException {
int nativeSRSCode = getNativeSRSCode( targetSRS );
if ( nativeSRSCode == SRS_UNDEFINED ) {
String msg = Messages.getMessage( "DATASTORE_SQL_NATIVE_CT_UNKNOWN_SRS",
this.getClass().getName(), targetSRS );
throw new DatastoreException( msg );
}
MappingGeometryField field = geoProperty.getMappingField();
FunctionParam param1 = new FieldContent( field, new TableRelation[0] );
FunctionParam param2 = new ConstantContent( "" + nativeSRSCode );
SQLFunctionCall transformCall = new SQLFunctionCall( "transform($1,$2)", field.getType(),
param1, param2 );
return transformCall;
}
@Override
public String buildSRSTransformCall( String geomIdentifier, int nativeSRSCode )
throws DatastoreException {
String call = "transform(" + geomIdentifier + "," + nativeSRSCode + ")";
return call;
}
/**
* Returns the database specific code for the given SRS name.
*
* @param srsName
* spatial reference system name (usually "EPSG:XYZ")
* @return the database specific SRS code, or -1 if no corresponding native code is known
*/
int getNativeSRSCode( String srsName ) {
Integer nativeSRSCode = nativeSrsCodeMap.get( srsName );
if ( nativeSRSCode == null ) {
return SRS_UNDEFINED;
}
return nativeSRSCode;
}
private static void initSRSCodeMap()
throws IOException {
InputStream is = PostGISDatastore.class.getResourceAsStream( SRS_CODE_PROP_FILE );
Properties props = new Properties();
props.load( is );
for ( Object key : props.keySet() ) {
String nativeCodeStr = props.getProperty( (String) key ).trim();
try {
int nativeCode = Integer.parseInt( nativeCodeStr );
nativeSrsCodeMap.put( (String) key, nativeCode );
} catch ( NumberFormatException e ) {
String msg = Messages.getMessage( "DATASTORE_SRS_CODE_INVALID", SRS_CODE_PROP_FILE,
nativeCodeStr, key );
throw new IOException( msg );
}
}
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: PostGISDatastore.java,v $
Revision 1.32 2006/11/29 16:59:54 mschneider
Improved handling of native coordinate transformation.
Revision 1.31 2006/11/16 08:54:48 mschneider
Javadoc improvements.
Revision 1.30 2006/11/09 17:48:52 mschneider
Implemented native coordinate transformations. Needs testing.
Revision 1.29 2006/11/02 14:29:41 mschneider
Javadoc / formatting fixes.
Revision 1.28 2006/09/22 11:23:37 mschneider
Javadoc fixes.
Revision 1.27 2006/09/19 14:55:16 mschneider
Cleaned up handling of VirtualContent, i.e. properties that are mapped to SQLFunctionCalls.
Revision 1.26 2006/08/24 06:40:05 poth
File header corrected
Revision 1.25 2006/08/21 15:46:31 mschneider
Javadoc improvements.
Revision 1.24 2006/08/14 16:50:55 mschneider
Changed to respect (optional) SortProperties.
Revision 1.23 2006/07/26 18:56:36 mschneider
Fixed spelling in method name.
Revision 1.22 2006/06/01 12:17:40 mschneider
Fixed header + footer.
********************************************************************** */