001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) Copyright IBM Corporation, 2005-2007. All rights reserved.
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.data.db2;
018:
019: import com.vividsolutions.jts.geom.LineString;
020: import com.vividsolutions.jts.geom.MultiLineString;
021: import com.vividsolutions.jts.geom.MultiPoint;
022: import com.vividsolutions.jts.geom.MultiPolygon;
023: import com.vividsolutions.jts.geom.Point;
024: import com.vividsolutions.jts.geom.Polygon;
025: import org.geotools.data.DataSourceException;
026: import org.geotools.data.DataUtilities;
027: import org.geotools.data.DefaultQuery;
028: import org.geotools.data.EmptyFeatureReader;
029: import org.geotools.data.FeatureReader;
030: import org.geotools.data.FeatureSource;
031: import org.geotools.data.Query;
032: import org.geotools.data.ReTypeFeatureReader;
033: import org.geotools.data.Transaction;
034: import org.geotools.data.db2.filter.SQLEncoderDB2;
035: import org.geotools.data.db2.filter.SQLEncoderDB2;
036: import org.geotools.data.jdbc.ConnectionPool;
037: import org.geotools.data.jdbc.FeatureTypeHandler;
038: import org.geotools.data.jdbc.FeatureTypeInfo;
039: import org.geotools.data.jdbc.FilterToSQL;
040: import org.geotools.data.jdbc.JDBCDataStore;
041: import org.geotools.data.jdbc.JDBCDataStoreConfig;
042: import org.geotools.data.jdbc.JDBCFeatureWriter;
043: import org.geotools.data.jdbc.JDBCUtils;
044: import org.geotools.data.jdbc.QueryData;
045: import org.geotools.data.jdbc.SQLBuilder;
046: import org.geotools.data.jdbc.attributeio.AttributeIO;
047: import org.geotools.data.jdbc.attributeio.WKTAttributeIO;
048: import org.geotools.data.jdbc.fidmapper.FIDMapper;
049: import org.geotools.data.jdbc.fidmapper.FIDMapperFactory;
050: import org.geotools.feature.AttributeType;
051: import org.geotools.feature.AttributeTypeFactory;
052: import org.geotools.feature.FeatureType;
053: import org.geotools.feature.GeometryAttributeType;
054:
055: import org.opengis.filter.Filter;
056: import org.opengis.referencing.crs.CoordinateReferenceSystem;
057: import java.io.IOException;
058: import java.sql.Connection;
059: import java.sql.ResultSet;
060: import java.sql.SQLException;
061: import java.sql.Statement;
062: import java.sql.Types;
063: import java.util.HashMap;
064: import java.util.Map;
065: import java.util.logging.Level;
066: import java.util.logging.Logger;
067:
068: import javax.sql.DataSource;
069:
070: /**
071: * DB2 DataStore implementation.
072: *
073: * <p>
074: * Instances of this class should only be obtained via
075: * DB2DataStoreFactory.createDataStore or DataStoreFinder.getDataStore.
076: * </p>
077: *
078: * @author David Adler - IBM Corporation
079: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/db2/src/main/java/org/geotools/data/db2/DB2DataStore.java $
080: */
081: public class DB2DataStore extends JDBCDataStore {
082: private static final Logger LOGGER = org.geotools.util.logging.Logging
083: .getLogger("org.geotools.data.db2");
084:
085: /**
086: * List of all the DB2 geometry type names and the corresponding JTS class
087: */
088: private static final Map DB2_GEOM_TYPE_MAPPING = new HashMap();
089:
090: /**
091: * Populate list of geometry classes supported by DB2
092: */
093: static {
094: DB2_GEOM_TYPE_MAPPING.put("ST_POINT", Point.class);
095: DB2_GEOM_TYPE_MAPPING.put("ST_LINESTRING", LineString.class);
096: DB2_GEOM_TYPE_MAPPING.put("ST_POLYGON", Polygon.class);
097: DB2_GEOM_TYPE_MAPPING.put("ST_MULTIPOINT", MultiPoint.class);
098: DB2_GEOM_TYPE_MAPPING.put("ST_MULTILINESTRING",
099: MultiLineString.class);
100: DB2_GEOM_TYPE_MAPPING
101: .put("ST_MULTIPOLYGON", MultiPolygon.class);
102: }
103:
104: /** Reference to DB2 in-memory spatial catalog */
105: private DB2SpatialCatalog catalog;
106:
107: /** The URL for this particular data store */
108: private String dbURL = null;
109:
110: /** Time since catalog was last reloaded */
111: private long lastTypeNameRequestTime = 0;
112:
113: /**
114: * The only supported constructor for a DB2DataStore. This constructor is
115: * mainly intended to be called from DB2DataStoreFactory.
116: *
117: * @param connectionPool the initialized DB2 connection pool
118: * @param config the JDBCDataStoreConfiguration
119: * @param dbURL the database URL of the form
120: * <code>jdbc:db2://hostname:hostport/dbname </code>
121: *
122: * @throws IOException
123: */
124: public DB2DataStore(DataSource dataSource,
125: JDBCDataStoreConfig config, String dbURL)
126: throws IOException {
127: super (dataSource, config);
128:
129: if (dataSource == null) {
130: throw new IOException("DataSource pool is null");
131: }
132:
133: this .dbURL = dbURL;
134:
135: // Get an instance of the DB2 spatial catalog for this database.
136: // We need to provide a database connection in case the spatial catalog instance
137: // does not already exist and we need to issue SQL against the database.
138: Connection conn = this .getConnection(Transaction.AUTO_COMMIT);
139:
140: try {
141: this .catalog = DB2SpatialCatalog.getInstance(dbURL, config
142: .getDatabaseSchemaName(), conn);
143: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
144: } catch (SQLException e) {
145: LOGGER.info("DB2SpatialCatalog create failed: " + e);
146: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, e);
147: throw new IOException("DB2SpatialCatalog create failed");
148: }
149: }
150:
151: /**
152: * Handles DB2-specific geometry types. If it isn't one, just let the
153: * parent method handle it.
154: *
155: * @param rs The ResultSet containing the result of a
156: * DatabaseMetaData.getColumns call.
157: *
158: * @return The AttributeType built from the ResultSet.
159: *
160: * @throws IOException If an error occurs processing the ResultSet.
161: */
162: protected AttributeType buildAttributeType(ResultSet rs)
163: throws IOException {
164: try {
165: String spatialTypePrefix = "\"DB2GSE\".\"ST_";
166:
167: int dataType = rs.getInt("DATA_TYPE");
168: String typeName = rs.getString("TYPE_NAME");
169: String tableSchema = rs.getString("TABLE_SCHEM");
170: String tableName = rs.getString("TABLE_NAME");
171: String columnName = rs.getString("COLUMN_NAME");
172: if (!typeName.startsWith(spatialTypePrefix)) {
173: return super .buildAttributeType(rs);
174: }
175:
176: String geomTypeName = this .catalog.getDB2GeometryTypeName(
177: tableSchema, tableName, columnName);
178:
179: // Look up the geometry type we just got back and create the
180: // corresponding attribute type for the geometry class, if it was found.
181: Class geomClass = (Class) DB2_GEOM_TYPE_MAPPING
182: .get(geomTypeName);
183:
184: if (geomClass != null) {
185: CoordinateReferenceSystem crs;
186:
187: try {
188: crs = this .catalog.getCRS(tableSchema, tableName,
189: columnName);
190: } catch (Exception e) {
191: throw new IOException("Exception: "
192: + e.getMessage());
193: }
194:
195: GeometryAttributeType geometryAttribute = (GeometryAttributeType) AttributeTypeFactory
196: .newAttributeType(columnName, geomClass, true,
197: 0, null, crs);
198:
199: return geometryAttribute;
200: }
201:
202: // It is some other unrecognized structured type - log what we found and return null
203: LOGGER
204: .fine("Type '" + geomTypeName
205: + "' is not recognized");
206:
207: return null;
208: } catch (SQLException e) {
209: throw new IOException("SQL exception occurred: "
210: + e.getMessage());
211: }
212: }
213:
214: /**
215: * Creates a DB2-specific FIDMapperFactory.
216: *
217: * @param config not used.
218: *
219: * @return a DB2FIDMapperFactory
220: */
221: protected FIDMapperFactory buildFIDMapperFactory(
222: JDBCDataStoreConfig config) {
223: return new DB2FIDMapperFactory(config.getDatabaseSchemaName());
224: }
225:
226: /**
227: * Get the SRID associated with a geometry column.
228: *
229: * <p>
230: * The value returned is the EPSG coordinate system identifier, not the DB2
231: * srs_id.
232: * </p>
233: *
234: * @param tableName The name of the table to get the SRID for.
235: * @param geometryColumnName The name of the geometry column within the
236: * table to get SRID for.
237: *
238: * @return The SRID for the geometry column or -1.
239: *
240: * @throws IOException
241: */
242: protected int determineSRID(String tableName,
243: String geometryColumnName) throws IOException {
244: int srid = -1;
245:
246: // Not sure that this is actually the right value to use
247: String tableSchema = this .config.getDatabaseSchemaName();
248: srid = this .catalog.getCsId(tableSchema, tableName,
249: geometryColumnName);
250: LOGGER.fine(DB2SpatialCatalog.geomID(tableSchema, tableName,
251: geometryColumnName)
252: + " srid=" + srid);
253:
254: return srid;
255: }
256:
257: /**
258: * Gets the database URL.
259: *
260: * @return the database URL.
261: */
262: String getDbURL() {
263: return this .dbURL;
264: }
265:
266: /**
267: * Create a DB2-specific FeatureTypeHandler.
268: *
269: * @param config a JDBCDataStoreConfig.
270: *
271: * @return a DB2FeatureTypeHandler.
272: *
273: * @throws IOException if the feature type handler could not be created.
274: */
275: protected FeatureTypeHandler getFeatureTypeHandler(
276: JDBCDataStoreConfig config) throws IOException {
277: return new DB2FeatureTypeHandler(this ,
278: buildFIDMapperFactory(config), config
279: .getTypeHandlerTimeout());
280: }
281:
282: /**
283: * Gets the handler to convert a geometry database value to a JTS geometry.
284: *
285: * @param type not used.
286: * @param queryData not used.
287: *
288: * @return AttributIO
289: */
290: protected AttributeIO getGeometryAttributeIO(AttributeType type,
291: QueryData queryData) {
292: return new WKTAttributeIO();
293: }
294:
295: /**
296: * Gets the instance of the DB2SpatialCatalog associated with this data
297: * store.
298: *
299: * @return a DB2SpatialCatalog
300: */
301: DB2SpatialCatalog getSpatialCatalog() {
302: return this .catalog;
303: }
304:
305: /**
306: * Gets the DB2-specific SQL builder object.
307: *
308: * @param typeName Name of the type to build the SQL for.
309: *
310: * @return DB2SQLBuilder
311: *
312: * @throws IOException
313: */
314: public SQLBuilder getSqlBuilder(String typeName) throws IOException {
315: FeatureTypeInfo info = this .typeHandler
316: .getFeatureTypeInfo(typeName);
317: int srid = 0;
318: SQLEncoderDB2 encoder = new SQLEncoderDB2();
319: encoder.setSqlNameEscape("\"");
320: FIDMapper mapper = getFIDMapper(typeName);
321: encoder.setFIDMapper(mapper);
322:
323: if (info.getSchema().getDefaultGeometry() != null) {
324: String geom = info.getSchema().getDefaultGeometry()
325: .getName();
326: srid = this .catalog.getSRID(getTableSchema(), typeName,
327: geom);
328: }
329:
330: encoder.setSRID(srid);
331:
332: // We should probably get the table schema name from the feature type
333: // information - not sure that it exists there.
334: return new DB2SQLBuilder((FilterToSQL) encoder,
335: getTableSchema(), info.getSchema());
336: }
337:
338: /**
339: * Gets the names of tables (types) that contain a spatial column. Note
340: * that there is still an issue concerning the ambiguity of spatial tables
341: * that have the same table name but different table schema names.
342: *
343: * @return Array of type names as Strings.
344: *
345: * @throws IOException if the spatial catalog can not be accessed.
346: */
347: public String[] getTypeNames() throws IOException {
348:
349: long lastTime = lastTypeNameRequestTime;
350: long now = System.currentTimeMillis();
351:
352: lastTypeNameRequestTime = now;
353:
354: if (lastTime < (now - config.getTypeHandlerTimeout())) {
355: refreshCatalog();
356: }
357: return getSpatialCatalog().getTypeNames();
358: }
359:
360: /**
361: * Reloads the spatial catalog from the database.
362: * <p>
363: * This is useful in case anything changed after the datastore was initialized.
364: * @throws IOException
365: * @throws SQLException
366: */
367: public void refreshCatalog() throws IOException {
368: try {
369: Connection conn = getConnection(Transaction.AUTO_COMMIT);
370: getSpatialCatalog().loadCatalog(conn, getTableSchema());
371: lastTypeNameRequestTime = System.currentTimeMillis();
372: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
373: LOGGER.fine("Refreshing spatial catalog");
374: } catch (SQLException e) {
375: throw (new IOException(e.getLocalizedMessage()));
376: }
377: }
378:
379: /**
380: * Gets the table schema associated with this data store.
381: *
382: * <p>
383: * At some point this may change if multiple schemas are supported by a
384: * data store.
385: * </p>
386: *
387: * @return the schema name that will prefix table names.
388: */
389: public String getTableSchema() {
390: return this .config.getDatabaseSchemaName();
391: }
392:
393: /**
394: * Gets a DB2-specific feature source.
395: *
396: * @param typeName
397: *
398: * @return a DB2Feature Source, Store or Locking
399: *
400: * @throws IOException if the feature source could not be created.
401: *
402: * @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
403: */
404: public FeatureSource getFeatureSource(String typeName)
405: throws IOException {
406: if (!this .typeHandler.getFIDMapper(typeName).isVolatile()
407: || this .allowWriteOnVolatileFIDs) {
408: if (getLockingManager() != null) {
409: // Use default JDBCFeatureLocking that delegates all locking
410: // the getLockingManager
411: //
412: return new DB2FeatureLocking(this , getSchema(typeName));
413: } else {
414: // subclass should provide a FeatureLocking implementation
415: // but for now we will simply forgo all locking
416: return new DB2FeatureStore(this , getSchema(typeName));
417: }
418: } else {
419: return new DB2FeatureSource(this , getSchema(typeName));
420: }
421: }
422:
423: /**
424: * Overrides the method in JDBCDataStore because it includes
425: * PostGIS-specific handling to setAutoCommit(false) which causes problems
426: * for DB2 because the transaction is still uncommitted when the
427: * connection is closed.
428: *
429: * @param featureTypeInfo
430: * @param tableName
431: * @param sqlQuery The SQL query to execute.
432: * @param transaction The Transaction is included here for handling
433: * transaction connections at a later stage. It is not currently
434: * used.
435: * @param forWrite
436: *
437: * @return The QueryData object that contains the resources for the query.
438: *
439: * @throws IOException
440: * @throws DataSourceException If an error occurs performing the query.
441: */
442: protected QueryData executeQuery(FeatureTypeInfo featureTypeInfo,
443: String tableName, String sqlQuery, Transaction transaction,
444: boolean forWrite) throws IOException {
445: LOGGER.fine("About to execute query: " + sqlQuery);
446:
447: Connection conn = null;
448: Statement statement = null;
449: ResultSet rs = null;
450:
451: try {
452: conn = getConnection(transaction);
453: statement = conn.createStatement(
454: getResultSetType(forWrite),
455: getConcurrency(forWrite));
456:
457: statement.setFetchSize(200);
458:
459: int rsc1 = statement.getResultSetConcurrency();
460: rs = statement.executeQuery(sqlQuery);
461:
462: int rsc2 = statement.getResultSetConcurrency();
463: int c = rs.getConcurrency();
464: int update = ResultSet.CONCUR_UPDATABLE;
465: int read = ResultSet.CONCUR_READ_ONLY;
466:
467: return new QueryData(featureTypeInfo, this , conn,
468: statement, rs, transaction);
469: } catch (SQLException e) {
470: // if an error occurred we close the resources
471: String msg = "Error Performing SQL query: " + sqlQuery;
472: LOGGER.log(Level.SEVERE, msg, e);
473: JDBCUtils.close(rs);
474: JDBCUtils.close(statement);
475: JDBCUtils.close(conn, transaction, e);
476: throw new DataSourceException(msg, e);
477: }
478: }
479:
480: /**
481: * Overrides the method in JDBCDataStore so that a DB2FeatureWriter is
482: * created.
483: *
484: * @param featureReader
485: * @param queryData
486: *
487: * @return The DB2FeatureWriter
488: *
489: * @throws IOException
490: *
491: * @see org.geotools.data.jdbc.JDBCDataStore#createFeatureWriter(org.geotools.data.FeatureReader,
492: * org.geotools.data.jdbc.QueryData)
493: */
494: protected JDBCFeatureWriter createFeatureWriter(
495: FeatureReader featureReader, QueryData queryData)
496: throws IOException {
497: String featureName = queryData.getFeatureType().getTypeName();
498:
499: return new DB2FeatureWriter(featureReader, queryData,
500: (DB2SQLBuilder) getSqlBuilder(featureName));
501: }
502:
503: /**
504: * This is a public entry point to the DataStore.
505: *
506: * <p>
507: * We have given some though to changing this api to be based on query.
508: * </p>
509: *
510: * <p>
511: * Currently the is is the only way to retype your features to different
512: * name spaces.
513: * </p>
514: * (non-Javadoc)
515: *
516: * @see org.geotools.data.DataStore#getFeatureReader(org.geotools.feature.FeatureType,
517: * org.geotools.filter.Filter, org.geotools.data.Transaction)
518: */
519: public FeatureReader getFeatureReader(
520: final FeatureType requestType, final Filter filter,
521: final Transaction transaction) throws IOException {
522: String typeName = requestType.getTypeName();
523: FeatureType schemaType = getSchema(typeName);
524: LOGGER.fine("requestType: " + requestType);
525: LOGGER.fine("schemaType: " + schemaType);
526:
527: int compare = DataUtilities.compare(requestType, schemaType);
528:
529: Query query;
530:
531: if (compare == 0) {
532: // they are the same type
533: //
534: query = new DefaultQuery(typeName, filter);
535: } else if (compare == 1) {
536: // featureType is a proper subset and will require reTyping
537: //
538: String[] names = attributeNames(requestType, filter);
539: query = new DefaultQuery(typeName, filter,
540: Query.DEFAULT_MAX, names, "getFeatureReader");
541: } else {
542: // featureType is not compatiable
543: //
544: throw new IOException("Type " + typeName
545: + " does match request");
546: }
547:
548: if ((filter == Filter.INCLUDE) || filter.equals(Filter.INCLUDE)) {
549: return new EmptyFeatureReader(requestType);
550: }
551:
552: FeatureReader reader = getFeatureReader(query, transaction);
553:
554: if (compare == 1) {
555: reader = new ReTypeFeatureReader(reader, requestType);
556: }
557:
558: return reader;
559: }
560:
561: }
|