001    //$HeadURL$
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.databaseloader;
038    
039    import java.net.URI;
040    import java.sql.Connection;
041    import java.sql.PreparedStatement;
042    import java.sql.ResultSet;
043    import java.sql.ResultSetMetaData;
044    import java.sql.SQLException;
045    import java.util.UUID;
046    
047    import org.deegree.datatypes.QualifiedName;
048    import org.deegree.datatypes.Types;
049    import org.deegree.framework.log.ILogger;
050    import org.deegree.framework.log.LoggerFactory;
051    import org.deegree.io.DBConnectionPool;
052    import org.deegree.io.DBPoolException;
053    import org.deegree.io.JDBCConnection;
054    import org.deegree.io.datastore.sql.postgis.PGgeometryAdapter;
055    import org.deegree.model.crs.CoordinateSystem;
056    import org.deegree.model.feature.FeatureCollection;
057    import org.deegree.model.feature.FeatureFactory;
058    import org.deegree.model.feature.FeatureProperty;
059    import org.deegree.model.feature.schema.FeatureType;
060    import org.deegree.model.feature.schema.PropertyType;
061    import org.deegree.model.spatialschema.Envelope;
062    import org.deegree.model.spatialschema.GeometryException;
063    import org.deegree.model.spatialschema.GeometryFactory;
064    import org.deegree.model.spatialschema.Surface;
065    import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
066    import org.postgis.PGboxbase;
067    import org.postgis.PGgeometry;
068    import org.postgresql.PGConnection;
069    
070    /**
071     * class for loading data as feature collection from a postgis database
072     *
073     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
074     * @author last edited by: $Author: poth $
075     *
076     * @version $Revision: 6251 $, $Date: 2007-03-19 16:59:28 +0100 (Mo, 19 Mrz 2007) $
077     */
078    public class PostgisDataLoader {
079    
080        private static final ILogger LOG = LoggerFactory.getLogger( PostgisDataLoader.class );
081    
082        private static final String GEOMETRY_DATATYPE_NAME = "geometry";
083    
084        private static final String BOX3D_DATATYPE_NAME = "box3d";
085    
086        private static final String PG_GEOMETRY_CLASS_NAME = "org.postgis.PGgeometry";
087    
088        private static final String PG_BOX3D_CLASS_NAME = "org.postgis.PGbox3d";
089    
090        private static Class<?> pgGeometryClass;
091    
092        private static Class<?> pgBox3dClass;
093    
094        private static URI namespace;
095        static {
096            try {
097                namespace = new URI( "http://www.deegree.org/database" );
098            } catch ( Exception e ) {
099                LOG.logError( e.getMessage(), e );
100            }
101            try {
102                pgGeometryClass = Class.forName( PG_GEOMETRY_CLASS_NAME );
103            } catch ( ClassNotFoundException e ) {
104                LOG.logError( "Cannot find class '" + PG_GEOMETRY_CLASS_NAME + "'.", e );
105            }
106            try {
107                pgBox3dClass = Class.forName( PG_BOX3D_CLASS_NAME );
108            } catch ( ClassNotFoundException e ) {
109                LOG.logError( "Cannot find class '" + PG_BOX3D_CLASS_NAME + "'.", e );
110            }
111        }
112    
113        /**
114         * @param datasource
115         * @param envelope
116         * @param sql
117         * @return the fc
118         * @throws Exception
119         */
120        public static FeatureCollection load( DatabaseDataSource datasource, Envelope envelope, String sql )
121                                throws Exception {
122            return load( datasource, envelope, sql, null );
123        }
124    
125        /**
126         * @param datasource
127         * @param envelope
128         * @param sql
129         * @param extraClauses
130         * @return the feature collection directly from the db
131         * @throws Exception
132         */
133        public static FeatureCollection load( DatabaseDataSource datasource, Envelope envelope, String sql,
134                                              String extraClauses )
135                                throws Exception {
136            if ( sql == null ) {
137                sql = datasource.getSqlTemplate();
138            }
139    
140            JDBCConnection jdbc = datasource.getJDBCConnection();
141            PreparedStatement stmt = null;
142            Connection conn = null;
143            ResultSet rs = null;
144            FeatureCollection fc = FeatureFactory.createFeatureCollection( UUID.randomUUID().toString(), 10000 );
145            try {
146                CoordinateSystem crs = datasource.getNativeCRS();
147                conn = acquireConnection( jdbc );
148                stmt = createPreparedStatement( datasource, envelope, conn, crs, sql, extraClauses );
149    
150                rs = stmt.executeQuery();
151    
152                LOG.logDebug( "performing database query: " + sql );
153                ResultSetMetaData rsmd = rs.getMetaData();
154                FeatureType featureType = createFeatureType( datasource, datasource.getGeometryFieldName(), rsmd );
155                int ccnt = rsmd.getColumnCount();
156                int k = 0;
157    
158                // read each line from database and create a feature from it
159                while ( rs.next() ) {
160                    FeatureProperty[] properties = new FeatureProperty[ccnt];
161                    for ( int i = 0; i < ccnt; i++ ) {
162                        String name = rsmd.getColumnName( i + 1 );
163                        Object value = rs.getObject( i + 1 );
164                        // if column name equals geometry field name the value read from
165                        // database must be converted into a deegree geometry
166                        if ( name.equalsIgnoreCase( datasource.getGeometryFieldName() ) ) {
167                            value = PGgeometryAdapter.wrap( (PGgeometry) value, crs );
168                        }
169                        properties[i] = FeatureFactory.createFeatureProperty( featureType.getPropertyName( i ), value );
170                    }
171                    // because feature IDs are not important in case of database datasource
172                    // it is just 'ID' as prefix plus a number of current row
173                    fc.add( FeatureFactory.createFeature( "ID" + k++, featureType, properties ) );
174                }
175                LOG.logDebug( k + " features loaded from database" );
176            } catch ( Exception e ) {
177                LOG.logError( e.getMessage(), e );
178                throw e;
179            } finally {
180                try {
181                    if ( rs != null ) {
182                        rs.close();
183                    }
184                } catch ( Exception e ) {
185                    // what to do here anyway
186                }
187                try {
188                    if ( stmt != null ) {
189                        stmt.close();
190                    }
191                } catch ( SQLException e ) {
192                    // what to do here anyway
193                }
194                releaseConnection( jdbc, conn );
195            }
196            return fc;
197        }
198    
199        /**
200         *
201         * @param datasource
202         * @param envelope
203         * @return featurecollection loaded from a postgis database
204         * @throws Exception
205         */
206        public static FeatureCollection load( DatabaseDataSource datasource, Envelope envelope )
207                                throws Exception {
208            return load( datasource, envelope, null );
209        }
210    
211        private static PreparedStatement createPreparedStatement( DatabaseDataSource datasource, Envelope envelope,
212                                                                  Connection conn, CoordinateSystem crs, String sql,
213                                                                  String extraClauses )
214                                throws GeometryException, SQLException {
215            PreparedStatement stmt;
216    
217            String nativeCRS = getSRSCode( crs );
218            String envCRS;
219            if ( envelope.getCoordinateSystem() != null ) {
220                envCRS = getSRSCode( envelope.getCoordinateSystem() );
221            } else {
222                envCRS = nativeCRS;
223            }
224    
225            // use the bbox operator (&&) to filter using the spatial index
226            PGboxbase box = PGgeometryAdapter.export( envelope );
227            Surface surface = GeometryFactory.createSurface( envelope, envelope.getCoordinateSystem() );
228            PGgeometry pggeom = PGgeometryAdapter.export( surface, Integer.parseInt( envCRS ) );
229            StringBuffer query = new StringBuffer( 1000 );
230            query.append( " (" );
231            query.append( datasource.getGeometryFieldName() );
232            query.append( " && transform(SetSRID( ?, " );
233            query.append( envCRS );
234            query.append( "), " );
235            query.append( nativeCRS );
236            query.append( ")) AND intersects(" );
237            query.append( datasource.getGeometryFieldName() );
238            query.append( ",transform(?, " );
239            query.append( nativeCRS );
240            query.append( "))" );
241    
242            if ( extraClauses != null ) {
243                query.append( extraClauses );
244            }
245    
246            if ( sql.indexOf( "$BBOX" ) == -1 ) {
247                if ( sql.trim().toUpperCase().endsWith( " WHERE" ) ) {
248                    LOG.logDebug( "performed SQL: ", sql + query );
249                    stmt = conn.prepareStatement( sql + query );
250                } else {
251                    LOG.logDebug( "performed SQL: ", sql + " AND " + query );
252                    stmt = conn.prepareStatement( sql + " AND " + query );
253                }
254            } else {
255                if ( sql.substring( 0, sql.indexOf( "$BBOX" ) ).trim().toUpperCase().endsWith( " WHERE" ) ) {
256                    stmt = conn.prepareStatement( sql.replace( "$BBOX", query ) );
257                } else {
258                    stmt = conn.prepareStatement( sql.replace( "$BBOX", " and " + query ) );
259                }
260                LOG.logDebug( "performed SQL: ", stmt );
261            }
262            stmt.setObject( 1, box, java.sql.Types.OTHER );
263            stmt.setObject( 2, pggeom, java.sql.Types.OTHER );
264            return stmt;
265        }
266    
267        private static String getSRSCode( CoordinateSystem crs ) {
268            return crs.getLocalName();
269        }
270    
271        /**
272         *
273         * @param geometryFiedName
274         * @param rsmd
275         * @return {@link FeatureType} created from column names and types
276         * @throws SQLException
277         */
278        private static FeatureType createFeatureType( DatabaseDataSource datasource, String geometryFiedName,
279                                                      ResultSetMetaData rsmd )
280                                throws SQLException {
281            int ccnt = rsmd.getColumnCount();
282            QualifiedName name = new QualifiedName( datasource.getName().getLocalName(), namespace );
283            PropertyType[] properties = new PropertyType[ccnt];
284            for ( int i = 0; i < ccnt; i++ ) {
285                QualifiedName propName = new QualifiedName( rsmd.getColumnName( i + 1 ), namespace );
286                LOG.logDebug( "propertyname: ", propName );
287                int typeCode = getTypeCode( geometryFiedName, rsmd.getColumnName( i + 1 ), rsmd.getColumnType( i + 1 ) );
288                properties[i] = FeatureFactory.createSimplePropertyType( propName, typeCode, true );
289            }
290            return FeatureFactory.createFeatureType( name, false, properties );
291        }
292    
293        private static int getTypeCode( String geometryFiedName, String columnName, int columnType ) {
294            if ( columnName.equalsIgnoreCase( geometryFiedName ) ) {
295                return Types.GEOMETRY;
296            }
297            return columnType;
298        }
299    
300        private static void releaseConnection( JDBCConnection jdbc, Connection conn ) {
301            try {
302                DBConnectionPool pool = DBConnectionPool.getInstance();
303                pool.releaseConnection( conn, jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
304            } catch ( DBPoolException e ) {
305                // what to do here anyway
306            }
307        }
308    
309        private static Connection acquireConnection( JDBCConnection jdbc )
310                                throws DBPoolException, SQLException {
311            Connection conn;
312            DBConnectionPool pool = DBConnectionPool.getInstance();
313            conn = pool.acquireConnection( jdbc.getDriver(), jdbc.getURL(), jdbc.getUser(), jdbc.getPassword() );
314            PGConnection pgConn = (PGConnection) conn;
315            pgConn.addDataType( GEOMETRY_DATATYPE_NAME, pgGeometryClass );
316            pgConn.addDataType( BOX3D_DATATYPE_NAME, pgBox3dClass );
317            return conn;
318        }
319    
320    }