001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-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.mysql;
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.Types;
023: import java.util.Arrays;
024: import java.util.Collections;
025: import java.util.HashSet;
026: import java.util.Set;
027: import java.util.logging.Logger;
028:
029: import javax.sql.DataSource;
030:
031: import org.geotools.data.DataSourceException;
032: import org.geotools.data.FeatureReader;
033: import org.geotools.data.FeatureWriter;
034: import org.geotools.data.Transaction;
035: import org.geotools.data.jdbc.JDBCDataStore;
036: import org.geotools.data.jdbc.JDBCDataStoreConfig;
037: import org.geotools.data.jdbc.JDBCFeatureWriter;
038: import org.geotools.data.jdbc.QueryData;
039: import org.geotools.data.jdbc.SQLBuilder;
040: import org.geotools.data.jdbc.attributeio.AttributeIO;
041: import org.geotools.data.jdbc.attributeio.WKBAttributeIO;
042: import org.geotools.data.jdbc.attributeio.WKTAttributeIO;
043: import org.geotools.data.jdbc.datasource.DataSourceUtil;
044: import org.geotools.factory.Hints;
045: import org.geotools.feature.AttributeType;
046: import org.geotools.feature.AttributeTypeFactory;
047: import org.geotools.filter.Filter;
048: import org.geotools.filter.SQLEncoderMySQL;
049:
050: import com.vividsolutions.jts.geom.Geometry;
051: import com.vividsolutions.jts.geom.GeometryCollection;
052: import com.vividsolutions.jts.geom.LineString;
053: import com.vividsolutions.jts.geom.MultiLineString;
054: import com.vividsolutions.jts.geom.MultiPoint;
055: import com.vividsolutions.jts.geom.MultiPolygon;
056: import com.vividsolutions.jts.geom.Point;
057: import com.vividsolutions.jts.geom.Polygon;
058:
059: /**
060: * An implementation of the GeoTools Data Store API for the MySQL database platform.
061: * The plan is to support traditional MySQL datatypes, as well as the new geometric
062: * datatypes provided with MySQL 4.1 and higher.<br>
063: * <br>
064: * TODO: MySQLDataStore is not yet tested for MySQL 4.1's geometric datatypes.<br>
065: * <br>
066: * Please see {@link org.geotools.data.jdbc.JDBCDataStore class JDBCDataStore} and
067: * {@link org.geotools.data.DataStore interface DataStore} for DataStore usage details.
068: * @author Gary Sheppard garysheppard@psu.edu
069: * @author Andrea Aime aaime@users.sourceforge.net
070: * @author Debasish Sahu debasish.sahu@rmsi.com
071: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/mysql/src/main/java/org/geotools/data/mysql/MySQLDataStore.java $
072: */
073:
074: public class MySQLDataStore extends JDBCDataStore {
075: /** The logger for the mysql module. */
076: private static final Logger LOGGER = org.geotools.util.logging.Logging
077: .getLogger("org.geotools.data.mysql");
078:
079: /**
080: * When true wkb encoding will be used to transfer geometries over the wire
081: */
082: protected boolean wkbEnabled;
083:
084: /**
085: * Basic constructor for MySQLDataStore.
086: * be done similar to the following:<br>
087: * @param dataSource A source of connections for this datastore
088: * @throws IOException if the database cannot be properly accessed
089: * @see javax.sql.DataSource
090: * @see org.geotools.data.mysql.MySQLConnectionFactory
091: */
092: public MySQLDataStore(DataSource dataSource) throws IOException {
093: this (dataSource, null);
094: }
095:
096: /**
097: * Constructor for MySQLDataStore where the database schema name is provided.
098: * @param dataSource A source of connections for this datastore
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 MySQLDataStore(DataSource dataSource,
103: String databaseSchemaName) throws IOException {
104: this (dataSource, databaseSchemaName, null);
105: }
106:
107: /**
108: * Constructor for MySQLDataStore where the database schema name is provided.
109: * @param dataSource A source of connections for this datastore
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 MySQLDataStore(DataSource dataSource,
115: String databaseSchemaName, String namespace)
116: throws IOException {
117: super (dataSource, JDBCDataStoreConfig
118: .createWithNameSpaceAndSchemaName(namespace,
119: databaseSchemaName));
120: }
121:
122: /**
123: * A utility method for creating a MySQLDataStore from database connection parameters,
124: * using the default port (3306) for MySQL.
125: * @param host the host name or IP address of the database server
126: * @param schema the name of the database instance
127: * @param username the database username
128: * @param password the password corresponding to <code>username</code>
129: * @return a MySQLDataStore for the specified parameters
130: */
131: public static MySQLDataStore getInstance(String host,
132: String schema, String username, String password)
133: throws IOException, SQLException {
134: return getInstance(host, 3306, schema, username, password);
135: }
136:
137: /**
138: * Utility method for creating a MySQLDataStore from database connection parameters.
139: * @param host the host name or IP address of the database server
140: * @param port the port number of the database
141: * @param schema the name of the database instance
142: * @param username the database username
143: * @param password the password corresponding to <code>username</code>
144: * @throws IOException if the MySQLDataStore cannot be created because the database cannot be properly accessed
145: * @throws SQLException if a MySQL connection pool cannot be established
146: */
147: public static MySQLDataStore getInstance(String host, int port,
148: String schema, String username, String password)
149: throws IOException, SQLException {
150: String url = "jdbc:mysql://" + host + ":" + port + "/" + schema;
151: String driver = "com.mysql.jdbc.Driver";
152: return new MySQLDataStore(DataSourceUtil
153: .buildDefaultDataSource(url, driver, username,
154: password, "select version()"));
155: }
156:
157: /**
158: * Utility method for getting a FeatureWriter for modifying existing features,
159: * using no feature filtering and auto-committing. Not used for adding new
160: * features.
161: * @param typeName the feature type name (the table name)
162: * @return a FeatureWriter for modifying existing features
163: * @throws IOException if the database cannot be properly accessed
164: */
165: public FeatureWriter getFeatureWriter(String typeName)
166: throws IOException {
167: return getFeatureWriter(typeName, Filter.INCLUDE,
168: Transaction.AUTO_COMMIT);
169: }
170:
171: /**
172: * Utility method for getting a FeatureWriter for adding new features, using
173: * auto-committing. Not used for modifying existing features.
174: * @param typeName the feature type name (the table name)
175: * @return a FeatureWriter for adding new features
176: * @throws IOException if the database cannot be properly accessed
177: */
178: public FeatureWriter getFeatureWriterAppend(String typeName)
179: throws IOException {
180: return getFeatureWriterAppend(typeName, Transaction.AUTO_COMMIT);
181: }
182:
183: /**
184: * Constructs an AttributeType from a row in a ResultSet. The ResultSet
185: * contains the information retrieved by a call to getColumns() on the
186: * DatabaseMetaData object. This information can be used to construct an
187: * Attribute Type.
188: *
189: * <p>
190: * In addition to standard SQL types, this method identifies MySQL 4.1's geometric
191: * datatypes and creates attribute types accordingly. This happens when the
192: * datatype, identified by column 5 of the ResultSet parameter, is equal to
193: * java.sql.Types.OTHER. If a Types.OTHER ends up not being geometric, this
194: * method simply calls the parent class's buildAttributeType method to do something
195: * with it.
196: * </p>
197: *
198: * <p>
199: * Note: Overriding methods must never move the current row pointer in the
200: * result set.
201: * </p>
202: *
203: * @param rs The ResultSet containing the result of a
204: * DatabaseMetaData.getColumns call.
205: *
206: * @return The AttributeType built from the ResultSet.
207: *
208: * @throws SQLException If an error occurs processing the ResultSet.
209: * @throws DataSourceException Provided for overriding classes to wrap
210: * exceptions caused by other operations they may perform to
211: * determine additional types. This will only be thrown by the
212: * default implementation if a type is present that is not present
213: * in the TYPE_MAPPINGS.
214: */
215: protected AttributeType buildAttributeType(ResultSet rs)
216: throws IOException {
217: final int COLUMN_NAME = 4;
218: final int DATA_TYPE = 5;
219: final int TYPE_NAME = 6;
220:
221: try {
222: int dataType = rs.getInt(DATA_TYPE);
223: if (dataType == Types.OTHER || dataType == Types.BINARY) {
224: //this is MySQL-specific; handle it
225: String typeName = rs.getString(TYPE_NAME);
226: String typeNameLower = typeName.toLowerCase();
227:
228: if ("geometry".equals(typeNameLower)) {
229: return AttributeTypeFactory.newAttributeType(rs
230: .getString(COLUMN_NAME), Geometry.class);
231: } else if ("point".equals(typeNameLower)) {
232: return AttributeTypeFactory.newAttributeType(rs
233: .getString(COLUMN_NAME), Point.class);
234: } else if ("linestring".equals(typeNameLower)) {
235: return AttributeTypeFactory.newAttributeType(rs
236: .getString(COLUMN_NAME), LineString.class);
237: } else if ("polygon".equals(typeNameLower)) {
238: return AttributeTypeFactory.newAttributeType(rs
239: .getString(COLUMN_NAME), Polygon.class);
240: } else if ("multipoint".equals(typeNameLower)) {
241: return AttributeTypeFactory.newAttributeType(rs
242: .getString(COLUMN_NAME), MultiPoint.class);
243: } else if ("multilinestring".equals(typeNameLower)) {
244: return AttributeTypeFactory.newAttributeType(rs
245: .getString(COLUMN_NAME),
246: MultiLineString.class);
247: } else if ("multipolygon".equals(typeNameLower)) {
248: return AttributeTypeFactory
249: .newAttributeType(
250: rs.getString(COLUMN_NAME),
251: MultiPolygon.class);
252: } else if ("geometrycollection".equals(typeNameLower)) {
253: return AttributeTypeFactory.newAttributeType(rs
254: .getString(COLUMN_NAME),
255: GeometryCollection.class);
256: } else {
257: //nothing else we can do
258: return super .buildAttributeType(rs);
259: }
260: } else {
261: return super .buildAttributeType(rs);
262: }
263: } catch (SQLException e) {
264: throw new IOException("SQL exception occurred: "
265: + e.getMessage());
266: }
267: }
268:
269: /**
270: * @see org.geotools.data.jdbc.JDBCDataStore#getSqlBuilder(java.lang.String)
271: */
272: public SQLBuilder getSqlBuilder(String typeName) throws IOException {
273: //SQLEncoder encoder = new SQLEncoderMySQL(); replace with this once
274: //it is fully tested, the test cases work, but I don't have a live
275: //mysql database. -ch
276: //SQLEncoder encoder = new SQLEncoder();
277: SQLEncoderMySQL encoder = new SQLEncoderMySQL();
278: encoder.setFIDMapper(getFIDMapper(typeName));
279: MySQLSQLBuilder builder = new MySQLSQLBuilder(encoder,
280: getSchema(typeName));
281: builder.setWKBEnabled(wkbEnabled);
282: return builder;
283: }
284:
285: /**
286: * @see org.geotools.data.jdbc.JDBCDataStore#getGeometryAttributeIO(org.geotools.feature.AttributeType)
287: */
288: protected AttributeIO getGeometryAttributeIO(AttributeType type,
289: QueryData queryData) {
290: if (wkbEnabled)
291: return new WKBAttributeIO(queryData.getHints());
292: else
293: return new WKTAttributeIO();
294: }
295:
296: protected JDBCFeatureWriter createFeatureWriter(
297: FeatureReader reader, QueryData queryData)
298: throws IOException {
299: LOGGER.fine("returning jdbc feature writer");
300:
301: return new MySQLFeatureWriter(reader, queryData);
302: }
303:
304: private static final Set MYSQL_HINTS = Collections
305: .unmodifiableSet(new HashSet(Arrays.asList(new Object[] {
306: Hints.FEATURE_DETACHED,
307: Hints.JTS_COORDINATE_SEQUENCE_FACTORY,
308: Hints.JTS_GEOMETRY_FACTORY })));
309:
310: public Set getSupportedHints() {
311: return MYSQL_HINTS;
312: }
313:
314: protected void setAutoCommit(boolean forWrite, Connection conn)
315: throws SQLException {
316: // override, do nothing
317: }
318:
319: /**
320: * Returns true if the WKB format is used to transfer geometries, false
321: * otherwise
322: *
323: */
324: public boolean isWKBEnabled() {
325: return wkbEnabled;
326: }
327:
328: /**
329: * If turned on, WKB will be used to transfer geometry data instead of WKT
330: *
331: * @param enabled
332: */
333: public void setWKBEnabled(boolean enabled) {
334: wkbEnabled = enabled;
335: }
336: }
|