001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
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: package org.geotools.data.hsql;
017:
018: import java.io.IOException;
019: import java.sql.Connection;
020: import java.sql.ResultSet;
021: import java.sql.SQLException;
022: import java.sql.Statement;
023: import java.util.Map;
024: import java.util.NoSuchElementException;
025: import java.util.logging.Logger;
026:
027: import org.geotools.data.DataSourceException;
028: import org.geotools.data.DataStore;
029: import org.geotools.data.DataUtilities;
030: import org.geotools.data.Diff;
031: import org.geotools.data.DiffFeatureReader;
032: import org.geotools.data.FeatureReader;
033: import org.geotools.data.FeatureStore;
034: import org.geotools.data.FeatureWriter;
035: import org.geotools.data.FilteringFeatureReader;
036: import org.geotools.data.Query;
037: import org.geotools.data.Transaction;
038: import org.geotools.data.hsql.fidmapper.HsqlFIDMapperFactory;
039: import org.geotools.data.jdbc.JDBC1DataStore;
040: import org.geotools.data.jdbc.JDBCDataStoreConfig;
041: import org.geotools.data.jdbc.JDBCFeatureWriter;
042: import org.geotools.data.jdbc.QueryData;
043: import org.geotools.data.jdbc.SQLBuilder;
044: import org.geotools.data.jdbc.attributeio.AttributeIO;
045: import org.geotools.data.jdbc.attributeio.WKTAttributeIO;
046: import org.geotools.data.jdbc.fidmapper.FIDMapperFactory;
047: import org.geotools.feature.AttributeType;
048: import org.geotools.feature.AttributeTypeFactory;
049: import org.geotools.feature.Feature;
050: import org.geotools.feature.FeatureType;
051: import org.geotools.feature.IllegalAttributeException;
052: import org.geotools.filter.Filter;
053: import org.geotools.filter.SQLEncoderHsql;
054:
055: import com.vividsolutions.jts.geom.Geometry;
056:
057: /**
058: * An implementation of the GeoTools Data Store API for the HSQL database platform.
059: * <br>
060: * Please see {@link org.geotools.data.jdbc.JDBC1DataStore class JDBC1DataStore} and
061: * {@link org.geotools.data.DataStore interface DataStore} for DataStore usage details.
062: *
063: * @author Amr Alam, Refractions Research, aalam@refractions.net
064: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/hsql/src/main/java/org/geotools/data/hsql/HsqlDataStore.java $
065: */
066: public class HsqlDataStore extends JDBC1DataStore implements DataStore {
067: /** The logger for the hsql module. */
068: private static final Logger LOGGER = org.geotools.util.logging.Logging
069: .getLogger("org.geotools.data.hsql");
070: private Connection connection;
071: private HsqlConnectionFactory hsqlConnFactory;
072: private boolean typeTableExists;
073:
074: /**
075: * Basic constructor for HsqlDataStore. Requires creation of a
076: * {@link org.geotools.data.hsql.HsqlConnectionFactory HsqlConnectionFactory}, which could
077: * be done similar to the following:<br>
078: * <br>
079: * <code>HsqlConnectionFactory connectionFactory = new HsqlConnectionFactory("dbFileName", "username", "password");</code><br>
080: * <code>DataStore dataStore = new HsqlDataStore(connectionFactory);</code><br>
081: *
082: * @param connectionFactory an HSQL {@link org.geotools.data.hsql.HsqlConnectionFactory HsqlConnectionFactory}
083: * @throws IOException if the database cannot be properly accessed
084: * @see org.geotools.data.hsql.HsqlConnectionFactory
085: */
086: public HsqlDataStore(HsqlConnectionFactory connectionFactory)
087: throws IOException {
088: this (connectionFactory, null);
089: }
090:
091: protected boolean requireAutoCommit() {
092: //hsql is wacky in that it wants to autocommit, but also have a state object?
093: return true;
094: }
095:
096: /**
097: * Constructor for HSQLDataStore where the database schema name is provided.
098: * @param connectionFactory an HSQL {@link org.geotools.data.hsql.HsqlConnectionFactory HsqlConnectionFactory}
099: * @param databaseSchemaName the database schema. Can be null. See the comments for the parameter schemaPattern in {@link java.sql.DatabaseMetaData#getTables(String, String, String, String[]) DatabaseMetaData.getTables}, because databaseSchemaName behaves in the same way.
100: * @throws IOException if the database cannot be properly accessed
101: */
102: public HsqlDataStore(HsqlConnectionFactory connectionFactory,
103: String databaseSchemaName) throws IOException {
104: this (connectionFactory, databaseSchemaName, null);
105: }
106:
107: /**
108: * Constructor for HSQLDataStore where the database schema name is provided.
109: * @param connectionFactory an HSQL {@link org.geotools.data.hsql.HsqlConnectionFactory HsqlConnectionFactory}
110: * @param databaseSchemaName the database schema. Can be null. See the comments for the parameter schemaPattern in {@link java.sql.DatabaseMetaData#getTables(String, String, String, String[]) DatabaseMetaData.getTables}, because databaseSchemaName behaves in the same way.
111: * @param namespace the namespace for this data store. Can be null, in which case the namespace will simply be the schema name.
112: * @throws IOException if the database cannot be properly accessed
113: */
114: public HsqlDataStore(HsqlConnectionFactory connectionFactory,
115: String databaseSchemaName, String namespace)
116: throws IOException {
117: super (JDBCDataStoreConfig.createWithNameSpaceAndSchemaName(
118: namespace, databaseSchemaName));
119: this .hsqlConnFactory = connectionFactory;
120: }
121:
122: /**
123: * Provides FeatureReader over the entire contents of <code>typeName</code>.
124: *
125: * <p>
126: * Implements getFeatureReader contract for AbstractDataStore.
127: * </p>
128: *
129: * @param typeName
130: *
131: * @return a featureReader
132: *
133: * @throws IOException If typeName could not be found
134: */
135: public FeatureReader getFeatureReader(final String typeName)
136: throws IOException {
137: FeatureType featureType = getSchema(typeName);
138: return getFeatureReader(featureType, Filter.INCLUDE,
139: Transaction.AUTO_COMMIT);
140: }
141:
142: /**
143: * Provides a featureReader over the query results using the given transaction
144: *
145: * @param query the Query object we want to narrow the results down by
146: * @param transaction the transaction object to be operated on
147: *
148: * @return a featureReader based on the given query and transaction
149: * @see org.geotools.data.jdbc.JDBC1DataStore#getFeatureReader(org.geotools.data.Query, org.geotools.data.Transaction)
150: */
151: public FeatureReader getFeatureReader(Query query,
152: Transaction transaction) throws IOException {
153: FeatureReader reader = super .getFeatureReader(query,
154: transaction);
155:
156: if (transaction != Transaction.AUTO_COMMIT) {
157: String typeName = query.getTypeName();
158: Diff diff = state(transaction).diff(typeName);
159: reader = new DiffFeatureReader(reader, diff);
160: }
161:
162: if ((query.getFilter() != null)
163: && (query.getFilter() != Filter.INCLUDE)) {
164: reader = new FilteringFeatureReader(reader, query
165: .getFilter());
166: }
167:
168: return reader;
169: }
170:
171: HsqlTransactionStateDiff state(Transaction transaction) {
172: synchronized (transaction) {
173: HsqlTransactionStateDiff state = (HsqlTransactionStateDiff) transaction
174: .getState(this );
175:
176: if (state == null) {
177: try {
178: state = new HsqlTransactionStateDiff(this ,
179: createConnection());
180: transaction.putState(this , state);
181: } catch (IOException e) {
182: // TODO Auto-generated catch block
183: e.printStackTrace();
184: } catch (SQLException e) {
185: // TODO Auto-generated catch block
186: e.printStackTrace();
187: }
188: }
189:
190: return state;
191: }
192: }
193:
194: /**
195: * Override the default FIDMapperFactory since it doesn't work well with HSQL
196: *
197: * @see org.geotools.data.jdbc.JDBCDataStore#buildFIDMapperFactory(org.geotools.data.jdbc.JDBCDataStoreConfig)
198: */
199: protected FIDMapperFactory buildFIDMapperFactory(
200: JDBCDataStoreConfig config) {
201: return new HsqlFIDMapperFactory();
202: }
203:
204: /**
205: * Convenience method to add feature to the datastore
206: *
207: * @param features an array of features that should be added to the datastore
208: * @throws IOException
209: *
210: public void addFeatures(final Feature[] features) throws IOException {
211: if( features.length == 0 ) return;
212:
213: FeatureStore fs = (FeatureStore)getFeatureSource(features[0].getFeatureType().getTypeName());
214: fs.addFeatures( DataUtilities.collection( features ));
215: }*/
216:
217: /**
218: * Utility method for getting a FeatureWriter for modifying existing features,
219: * using no feature filtering and auto-committing. Not used for adding new
220: * features.
221: * @param typeName the feature type name (the table name)
222: * @return a FeatureWriter for modifying existing features
223: * @throws IOException if the database cannot be properly accessed
224: */
225: public FeatureWriter getFeatureWriter(String typeName)
226: throws IOException {
227: return getFeatureWriter(typeName, Filter.INCLUDE,
228: Transaction.AUTO_COMMIT);
229: }
230:
231: /**
232: * Acquire FeatureWriter for modification of contents specifed by filter.
233: *
234: * @param typeName
235: * @param filter
236: * @param transaction
237: *
238: *
239: * @throws IOException If typeName could not be located
240: * @throws NullPointerException If the provided filter is null
241: *
242: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String,
243: * org.geotools.filter.Filter, org.geotools.data.Transaction,
244: * org.geotools.data.jdbc.JDBC1DataStore#getFeatureWriter)
245: */
246: public FeatureWriter getFeatureWriter(String typeName,
247: Filter filter, Transaction transaction) throws IOException {
248: FeatureWriter writer;
249:
250: if (transaction == null) {
251: throw new NullPointerException(
252: "getFeatureWriter requires Transaction: "
253: + "did you mean to use Transaction.AUTO_COMMIT?");
254: }
255:
256: if (transaction == Transaction.AUTO_COMMIT) {
257: writer = super .getFeatureWriter(typeName, filter,
258: transaction);
259: } else {
260: writer = state(transaction).writer(typeName, filter);
261: }
262:
263: return writer;
264: }
265:
266: /**
267: * Utility method for getting a FeatureWriter for adding new features, using
268: * auto-committing. Not used for modifying existing features.
269: * @param typeName the feature type name (the table name)
270: * @return a FeatureWriter for adding new features
271: * @throws IOException if the database cannot be properly accessed
272: */
273: public FeatureWriter getFeatureWriterAppend(String typeName)
274: throws IOException {
275: return getFeatureWriterAppend(typeName, Transaction.AUTO_COMMIT);
276: }
277:
278: /**
279: * Constructs an AttributeType from a row in a ResultSet. The ResultSet
280: * contains the information retrieved by a call to getColumns() on the
281: * DatabaseMetaData object. This information can be used to construct an
282: * Attribute Type.
283: *
284: * <p>
285: * In addition to standard SQL types, this method identifies MySQL 4.1's geometric
286: * datatypes and creates attribute types accordingly. This happens when the
287: * datatype, identified by column 5 of the ResultSet parameter, is equal to
288: * java.sql.Types.OTHER. If a Types.OTHER ends up not being geometric, this
289: * method simply calls the parent class's buildAttributeType method to do something
290: * with it.
291: * </p>
292: *
293: * <p>
294: * Note: Overriding methods must never move the current row pointer in the
295: * result set.
296: * </p>
297: *
298: * @param rs The ResultSet containing the result of a
299: * DatabaseMetaData.getColumns call.
300: *
301: * @return The AttributeType built from the ResultSet.
302: *
303: * @throws SQLException If an error occurs processing the ResultSet.
304: * @throws DataSourceException Provided for overriding classes to wrap
305: * exceptions caused by other operations they may perform to
306: * determine additional types. This will only be thrown by the
307: * default implementation if a type is present that is not present
308: * in the TYPE_MAPPINGS.
309: */
310: protected AttributeType buildAttributeType(ResultSet rs)
311: throws IOException {
312: final int COLUMN_NAME = 4;
313: final int TABLE_NAME = 3; //Position of table name in the ResultSet
314:
315: try {
316: String tableName = rs.getString(TABLE_NAME);
317: String type = findType(tableName, rs.getString(COLUMN_NAME));
318:
319: return AttributeTypeFactory.newAttributeType(rs
320: .getString(COLUMN_NAME), Class.forName(type));
321: } catch (SQLException e) {
322: throw new IOException("SQL exception occurred: "
323: + e.getMessage());
324: } catch (ClassNotFoundException e) {
325: // TODO Auto-generated catch block
326: e.printStackTrace();
327: }
328: return super .buildAttributeType(rs);
329: }
330:
331: /**
332: * @see org.geotools.data.jdbc.JDBC1DataStore#getSqlBuilder(java.lang.String)
333: */
334: public SQLBuilder getSqlBuilder(String typeName) throws IOException {
335: SQLEncoderHsql encoder = new SQLEncoderHsql();
336: encoder.setFIDMapper(getFIDMapper(typeName));
337: return new HsqlSQLBuilder(encoder, getSchema(typeName));
338: }
339:
340: /**
341: * @see org.geotools.data.jdbc.JDBC1DataStore#getGeometryAttributeIO(org.geotools.feature.AttributeType)
342: */
343: protected AttributeIO getGeometryAttributeIO(AttributeType type,
344: QueryData queryData) {
345: return new WKTAttributeIO();
346: }
347:
348: /**
349: * @see org.geotools.data.jdbc.JDBC1DataStore#createFeatureWriter(org.geotools.data.FeatureReader, org.geotools.data.jdbc.QueryData)
350: */
351: protected JDBCFeatureWriter createFeatureWriter(
352: FeatureReader reader, QueryData queryData)
353: throws IOException {
354: LOGGER.fine("returning jdbc feature writer");
355:
356: return new HsqlFeatureWriter(reader, queryData);
357: }
358:
359: /**
360: * @see org.geotools.data.jdbc.JDBC1DataStore#createConnection()
361: */
362: protected Connection createConnection() throws SQLException {
363: //HsqlConnectionFactory hcf = new HsqlConnectionFactory();
364: if (connection == null
365: || (connection.isClosed() && hsqlConnFactory != null)) {
366: connection = hsqlConnFactory.getConnection();
367: return connection;
368: }
369: return connection;
370: }
371:
372: /**
373: * Gets a connection for the provided transaction.
374: *
375: * @param transaction
376: * @return A single use connection.
377: *
378: * @throws IOException
379: * @throws DataSourceException If the connection can not be obtained.
380: */
381: protected Connection getConnection(Transaction transaction)
382: throws IOException {
383: if (transaction != Transaction.AUTO_COMMIT) {
384: // we will need to save a JDBC connection is
385: // transaction.putState( connectionPool, JDBCState )
386: //throw new UnsupportedOperationException("Transactions not supported yet");
387: HsqlTransactionStateDiff state = (HsqlTransactionStateDiff) transaction
388: .getState(this );
389:
390: if (state == null) {
391: try {
392: state = new HsqlTransactionStateDiff(this ,
393: createConnection());
394: transaction.putState(this , state);
395: } catch (SQLException eep) {
396: throw new DataSourceException("Connection failed:"
397: + eep, eep);
398: }
399: }
400: return state.getConnection();
401: }
402:
403: try {
404: if (connection == null || connection.isClosed())
405: return createConnection();
406: else
407: return connection;
408: } catch (SQLException sqle) {
409: throw new DataSourceException("Connection failed:" + sqle,
410: sqle);
411: }
412: }
413:
414: /**
415: * Adds support for a new featureType to HsqlDataStore.
416: *
417: * <p>
418: * FeatureTypes are stored by typeName (in this case, table name = typeName),
419: * an IOException will be thrown if the requested typeName
420: * is already in use.
421: * </p>
422: *
423: * @param featureType FeatureType to be added
424: *
425: * @throws IOException If featureType already exists
426: *
427: * @see org.geotools.data.DataStore#createSchema(org.geotools.feature.FeatureType)
428: */
429: public void createSchema(FeatureType featureType)
430: throws IOException {
431: String typeName = featureType.getTypeName();
432: String namespace = featureType.getNamespace().toString();
433: String colName = null;
434: Class colClass = null;
435: String colType = null;
436:
437: AttributeType[] atts = featureType.getAttributeTypes();
438: try {
439: createConnection();
440: Statement st = connection.createStatement();
441: String sql = "CREATE CACHED TABLE " + typeName + "( ";
442:
443: //Add fid column right at the start...auto-increment PK
444: sql += "_FID INTEGER IDENTITY";
445: addTypeTable(typeName, namespace, "_FID",
446: "java.lang.Integer");
447:
448: for (int i = 0; i < atts.length; i++) {
449: //if( i != 0 )
450: sql += ",";
451: colName = atts[i].getName();
452: colClass = atts[i].getType();
453: if (colClass.isAssignableFrom(int.class)
454: || colClass.isAssignableFrom(Integer.class)) {
455: colType = "integer";
456: } else if (colClass.isAssignableFrom(String.class)) {
457: colType = "varchar";
458: } else if (colClass.isAssignableFrom(double.class)
459: || colClass.isAssignableFrom(Double.class)) {
460: colType = "double";
461: } else if (colClass.isAssignableFrom(Geometry.class)) {
462: colType = "varchar";
463: } else if (Geometry.class.isAssignableFrom(colClass)) {
464: colType = "varchar";
465: }
466: sql += " " + colName + " " + colType;
467: addTypeTable(typeName, namespace, atts[i]);
468: }
469: sql += " )";
470: st.execute(sql);
471: typeHandler.forceRefresh();
472: } catch (SQLException e) {
473: // Attempted to re-create typeTable table...OK
474: }
475: }
476:
477: protected boolean allowTable(String tablename) {
478: return !tablename.equalsIgnoreCase("TYPETABLE");
479: }
480:
481: /**
482: * Removes support for the featureType schema to HsqlDataStore. (Drops the table)
483: *
484: * <p>
485: * FeatureTypes are stored by typeName (in this case, table name = typeName).
486: * </p>
487: *
488: * @param featureType FeatureType to be removed
489: */
490: public void removeSchema(FeatureType featureType) {
491: String typeName = featureType.getTypeName();
492:
493: try {
494: createConnection();
495: Statement st = connection.createStatement();
496: String sql = "DROP TABLE " + typeName;
497: st.execute(sql);
498:
499: } catch (SQLException e) {
500: //e.printStackTrace();
501: LOGGER.fine("Table does not exist.");
502: }
503: }
504:
505: private String findType(String typeName, String columnName) {
506: try {
507: if (connection == null)
508: createConnection();
509:
510: Statement st = connection.createStatement();
511: String sql = "SELECT typeName, columnName, class FROM typeTable "
512: + "WHERE TYPENAME = '"
513: + typeName
514: + "' AND COLUMNNAME = '" + columnName + "'";
515:
516: ResultSet rs = st.executeQuery(sql);
517: rs.next();
518: String type = rs.getString(3);
519: return type;
520: } catch (SQLException e) {
521: // TODO Auto-generated catch block
522: e.printStackTrace();
523: }
524: return "";
525: }
526:
527: /**
528: * Adds the given attribute with it's type into the typeTable
529: *
530: * @param typeName the featureType name
531: * @param namespace the featureType namespace
532: * @param attribute the attribute we want to store info about
533: */
534: private void addTypeTable(String typeName, String namespace,
535: AttributeType attribute) {
536: addTypeTable(typeName, namespace, attribute.getName()
537: .toUpperCase(), attribute.getType().getName());
538: }
539:
540: /**
541: * Adds the given attribute with it's type into the typeTable
542: *
543: * @param typeName the featureType name
544: * @param namespace the featureType namespace
545: * @param name the entry's name
546: * @param type the entry's class
547: */
548: private void addTypeTable(String typeName, String namespace,
549: String name, String type) {
550: try {
551: //Might want to add the CRS into the type table...
552: // FeatureType featureType;
553: // int SRID = -1;
554: // try {
555: // featureType = getSchema(typeName);
556: // CoordinateReferenceSystem refSys = featureType.getDefaultGeometry().getCoordinateSystem();
557: //
558: // // so for now we just use -1
559: // if (refSys != null) {
560: // SRID = -1;
561: // } else {
562: // SRID = -1;
563: // }
564: // } catch (IOException e) {
565: // // TODO Auto-generated catch block
566: // e.printStackTrace();
567: // }
568:
569: if (connection == null)
570: createConnection();
571: createTypeTable();
572: Statement st = connection.createStatement();
573: String sql = "INSERT INTO typeTable (typeName, namespace, columnName, class) "
574: + "VALUES( "
575: + "'"
576: + typeName.toUpperCase()
577: + "', "
578: + "'"
579: + namespace
580: + "', "
581: + "'"
582: + name
583: + "', "
584: + "'" + type + "'" + ")";
585:
586: st.execute(sql);
587: } catch (SQLException e) {
588: // TODO Auto-generated catch block
589: //e.printStackTrace();
590: // System.out.println("INSERTING into typtTable failed due to duplicates...OK");
591: }
592: }
593:
594: //Will this be needed at some point?
595: // private void removeTypeTable() {
596: // if(!typeTableExists) return;
597: // try {
598: // if( connection == null )
599: // createConnection();
600: // Statement st = connection.createStatement();
601: // String sql = "DROP TABLE typeTable";
602: //
603: // st.execute(sql);
604: // typeTableExists = false;
605: //
606: // } catch (SQLException e) {
607: // // TODO Auto-generated catch block
608: // e.printStackTrace();
609: // }
610: // }
611:
612: private void createTypeTable() {
613: if (typeTableExists)
614: return;
615: try {
616: if (connection == null)
617: createConnection();
618: typeTableExists = true;
619: Statement st = connection.createStatement();
620: String sql = "CREATE CACHED TABLE typeTable( "
621: + "typeName varchar, namespace varchar, columnName varchar, "
622: + "class varchar, encoding varchar, srid integer, "
623: + "PRIMARY KEY(typeName, namespace, columnName))";
624:
625: st.execute(sql);
626:
627: } catch (SQLException e) {
628: // TODO Auto-generated catch block
629: //e.printStackTrace();
630: // System.out.println("Attempted to re-create typeTable table...OK");
631: }
632: }
633:
634: protected void setAutoCommit(boolean arg0, Connection arg1)
635: throws SQLException {
636: // do nothing
637: }
638:
639: }
|