0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2002-2006, GeoTools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation;
0009: * version 2.1 of the License.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.data.postgis;
0017:
0018: import java.io.IOException;
0019: import java.math.BigDecimal;
0020: import java.sql.Connection;
0021: import java.sql.DatabaseMetaData;
0022: import java.sql.ResultSet;
0023: import java.sql.SQLException;
0024: import java.sql.Statement;
0025: import java.util.ArrayList;
0026: import java.util.Arrays;
0027: import java.util.Collections;
0028: import java.util.HashMap;
0029: import java.util.HashSet;
0030: import java.util.Iterator;
0031: import java.util.List;
0032: import java.util.Map;
0033: import java.util.Set;
0034: import java.util.logging.Level;
0035: import java.util.logging.Logger;
0036:
0037: import javax.sql.DataSource;
0038:
0039: import org.geotools.data.DataSourceException;
0040: import org.geotools.data.DataStore;
0041: import org.geotools.data.DataUtilities;
0042: import org.geotools.data.DefaultQuery;
0043: import org.geotools.data.EmptyFeatureReader;
0044: import org.geotools.data.FeatureReader;
0045: import org.geotools.data.FeatureSource;
0046: import org.geotools.data.FeatureWriter;
0047: import org.geotools.data.InProcessLockingManager;
0048: import org.geotools.data.LockingManager;
0049: import org.geotools.data.Query;
0050: import org.geotools.data.ReTypeFeatureReader;
0051: import org.geotools.data.Transaction;
0052: import org.geotools.data.jdbc.ConnectionPool;
0053: import org.geotools.data.jdbc.FeatureTypeInfo;
0054: import org.geotools.data.jdbc.JDBCDataStore;
0055: import org.geotools.data.jdbc.JDBCDataStoreConfig;
0056: import org.geotools.data.jdbc.JDBCFeatureLocking;
0057: import org.geotools.data.jdbc.JDBCFeatureSource;
0058: import org.geotools.data.jdbc.JDBCFeatureStore;
0059: import org.geotools.data.jdbc.JDBCFeatureWriter;
0060: import org.geotools.data.jdbc.JDBCUtils;
0061: import org.geotools.data.jdbc.QueryData;
0062: import org.geotools.data.jdbc.SQLBuilder;
0063: import org.geotools.data.jdbc.attributeio.AttributeIO;
0064: import org.geotools.data.jdbc.attributeio.WKTAttributeIO;
0065: import org.geotools.data.jdbc.fidmapper.FIDMapper;
0066: import org.geotools.data.jdbc.fidmapper.FIDMapperFactory;
0067: import org.geotools.data.postgis.attributeio.EWKTAttributeIO;
0068: import org.geotools.data.postgis.attributeio.PgWKBAttributeIO;
0069: import org.geotools.data.postgis.fidmapper.PostgisFIDMapperFactory;
0070: import org.geotools.data.postgis.referencing.PostgisAuthorityFactory;
0071: import org.geotools.factory.GeoTools;
0072: import org.geotools.factory.Hints;
0073: import org.geotools.feature.AttributeType;
0074: import org.geotools.feature.AttributeTypeFactory;
0075: import org.geotools.feature.FeatureType;
0076: import org.geotools.feature.GeometryAttributeType;
0077: import org.geotools.filter.CompareFilter;
0078: import org.opengis.filter.Filter;
0079: import org.opengis.filter.PropertyIsLessThan;
0080: import org.opengis.filter.PropertyIsLessThanOrEqualTo;
0081: import org.geotools.filter.FilterType;
0082: import org.geotools.filter.LengthFunction;
0083: import org.geotools.filter.LiteralExpression;
0084: import org.geotools.filter.SQLEncoderPostgis;
0085: import org.geotools.referencing.CRS;
0086: import org.geotools.referencing.NamedIdentifier;
0087: import org.geotools.referencing.crs.DefaultGeographicCRS;
0088: import org.opengis.referencing.FactoryException;
0089: import org.opengis.referencing.NoSuchAuthorityCodeException;
0090: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0091:
0092: import com.vividsolutions.jts.geom.Envelope;
0093: import com.vividsolutions.jts.geom.Geometry;
0094: import com.vividsolutions.jts.geom.GeometryCollection;
0095: import com.vividsolutions.jts.geom.GeometryFactory;
0096: import com.vividsolutions.jts.geom.LineString;
0097: import com.vividsolutions.jts.geom.MultiLineString;
0098: import com.vividsolutions.jts.geom.MultiPoint;
0099: import com.vividsolutions.jts.geom.MultiPolygon;
0100: import com.vividsolutions.jts.geom.Point;
0101: import com.vividsolutions.jts.geom.Polygon;
0102: import com.vividsolutions.jts.io.WKTReader;
0103:
0104: /**
0105: * Postgis DataStore implementation.
0106: *
0107: * <p>
0108: * This datastore by default will read/write geometries in WKT format.<br>
0109: * Optionally use of WKB can be turned on, in which case you may want to turn
0110: * on also the use of the bytea function, that fasten the data trasfer, but
0111: * that it's available only from version 0.7.2 onwards.
0112: * </p>
0113: *
0114: * @author Chris Holmes, TOPP
0115: * @author Andrea Aime
0116: * @author Paolo Rizzi
0117: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/postgis/src/main/java/org/geotools/data/postgis/PostgisDataStore.java $
0118: * @version $Id: PostgisDataStore.java 29506 2008-02-29 09:27:36Z aaime $
0119: *
0120: * @task REVISIT: So Paolo Rizzi has a number of improvements in
0121: * http://jira.codehuas.org/browse/GEOT-379 I rolled in a few of them,
0122: * but some beg more fundamental questions - like the use of primary
0123: * keys - in the geotools model. See the issue for a bit more
0124: * discussion, and I will attempt to write my thoughts up on wiki soon.
0125: * -ch
0126: */
0127: public class PostgisDataStore extends JDBCDataStore implements
0128: DataStore {
0129:
0130: /** The logger for the postgis module. */
0131: protected static final Logger LOGGER = org.geotools.util.logging.Logging
0132: .getLogger("org.geotools.data.postgis");
0133:
0134: /** Factory for producing geometries (from JTS). */
0135: protected static GeometryFactory geometryFactory = new GeometryFactory();
0136:
0137: /** Well Known Text reader (from JTS). */
0138: protected static WKTReader geometryReader = new WKTReader(
0139: geometryFactory);
0140:
0141: /** Map of postgis geometries to jts geometries */
0142: private static Map GEOM_TYPE_MAP = new HashMap();
0143:
0144: static {
0145: GEOM_TYPE_MAP.put("GEOMETRY", Geometry.class);
0146: GEOM_TYPE_MAP.put("POINT", Point.class);
0147: GEOM_TYPE_MAP.put("POINTM", Point.class);
0148: GEOM_TYPE_MAP.put("LINESTRING", LineString.class);
0149: GEOM_TYPE_MAP.put("LINESTRINGM", LineString.class);
0150: GEOM_TYPE_MAP.put("POLYGON", Polygon.class);
0151: GEOM_TYPE_MAP.put("POLYGONM", Polygon.class);
0152: GEOM_TYPE_MAP.put("MULTIPOINT", MultiPoint.class);
0153: GEOM_TYPE_MAP.put("MULTIPOINTM", MultiPoint.class);
0154: GEOM_TYPE_MAP.put("MULTILINESTRING", MultiLineString.class);
0155: GEOM_TYPE_MAP.put("MULTILINESTRINGM", MultiLineString.class);
0156: GEOM_TYPE_MAP.put("MULTIPOLYGON", MultiPolygon.class);
0157: GEOM_TYPE_MAP.put("MULTIPOLYGONM", MultiPolygon.class);
0158: GEOM_TYPE_MAP.put("GEOMETRYCOLLECTION",
0159: GeometryCollection.class);
0160: GEOM_TYPE_MAP.put("GEOMETRYCOLLECTIONM",
0161: GeometryCollection.class);
0162: }
0163:
0164: private static Map CLASS_MAPPINGS = new HashMap();
0165:
0166: static {
0167: CLASS_MAPPINGS.put(String.class, "VARCHAR");
0168:
0169: CLASS_MAPPINGS.put(Boolean.class, "BOOLEAN");
0170:
0171: CLASS_MAPPINGS.put(Integer.class, "INTEGER");
0172:
0173: CLASS_MAPPINGS.put(Float.class, "REAL");
0174: CLASS_MAPPINGS.put(Double.class, "DOUBLE PRECISION");
0175:
0176: CLASS_MAPPINGS.put(BigDecimal.class, "DECIMAL");
0177:
0178: CLASS_MAPPINGS.put(java.sql.Date.class, "DATE");
0179: CLASS_MAPPINGS.put(java.util.Date.class, "DATE");
0180: CLASS_MAPPINGS.put(java.sql.Time.class, "TIME");
0181: CLASS_MAPPINGS.put(java.sql.Timestamp.class, "TIMESTAMP");
0182: }
0183:
0184: private static Map GEOM_CLASS_MAPPINGS = new HashMap();
0185:
0186: //why don't we just stick this in with the non-geom class mappings?
0187: static {
0188: // init the inverse map
0189: Set keys = GEOM_TYPE_MAP.keySet();
0190:
0191: for (Iterator it = keys.iterator(); it.hasNext();) {
0192: String name = (String) it.next();
0193: Class geomClass = (Class) GEOM_TYPE_MAP.get(name);
0194: GEOM_CLASS_MAPPINGS.put(geomClass, name);
0195: }
0196: }
0197:
0198: /** OPTIMIZE_MODE constants */
0199: public static final int OPTIMIZE_SAFE = 0;
0200: public static final int OPTIMIZE_SQL = 1;
0201:
0202: /** Maximum string size for postgres */
0203: private static final int MAX_ALLOWED_VALUE = 10485760;
0204:
0205: //JD: GEOT-723, keeping this reference static allows the authority factory
0206: // to hold onto a stale connection pool when a new datastore is created.
0207: //private static PostgisAuthorityFactory paf = null;
0208: private PostgisAuthorityFactory paf = null;
0209:
0210: /** PostGIS version information (persisted here so we don't have to keep asking the database what version it is, in perpituity. */
0211: protected PostgisDBInfo dbInfo;
0212:
0213: /** Enables the use of geos operators */
0214: protected boolean useGeos;
0215:
0216: /**
0217: * Current optimize mode
0218: * @deprecated Dot not use this directly, use {@link #getOptimizeMode()}.
0219: */
0220: public int OPTIMIZE_MODE;
0221:
0222: /** If true, WKB format is used instead of WKT */
0223: protected boolean WKBEnabled = false;
0224:
0225: /**
0226: * If true, the bytea function will be used to optimize even further data
0227: * loading when using WKB format
0228: */
0229: protected boolean byteaEnabled = false;
0230:
0231: /**
0232: * postgis 1.0 changed the way WKB is handled, this needs to be
0233: * set if version >1.
0234: * (it affects the way you send WKB to the database)
0235: */
0236: protected boolean byteaWKB = false;
0237:
0238: /**
0239: * If true then the bounding box filters will use the && postgis operator,
0240: * which uses the spatial index and performs against the envelope of the
0241: * geom, leading to greater speed and slightly less accuracy.
0242: */
0243: protected boolean looseBbox;
0244:
0245: /**
0246: * set to true if the bounds for a table should be computed using the
0247: * estimated_extent' function, but beware that this function is less accurate
0248: * and in some cases *far* less accurate if the data within the actual bounds
0249: * does not follow a uniform distribution.
0250: */
0251: protected boolean estimatedExtent;
0252:
0253: /** Flag indicating whether schema support **/
0254: protected boolean schemaEnabled = true;
0255:
0256: protected PostgisDataStore(DataSource dataSource)
0257: throws IOException {
0258: this (dataSource, (String) null);
0259: }
0260:
0261: protected PostgisDataStore(DataSource dataSource, String namespace)
0262: throws IOException {
0263: this (dataSource, schema(null), namespace);
0264: }
0265:
0266: protected PostgisDataStore(DataSource dataSource, String schema,
0267: String namespace) throws IOException {
0268: this (dataSource, new JDBCDataStoreConfig(namespace,
0269: schema(schema), new HashMap(), new HashMap()),
0270: OPTIMIZE_SQL);
0271: }
0272:
0273: protected PostgisDataStore(DataSource dataSource, String schema,
0274: String namespace, int optimizeMode) throws IOException {
0275: this (dataSource, new JDBCDataStoreConfig(namespace,
0276: schema(schema), new HashMap(), new HashMap()),
0277: optimizeMode);
0278: }
0279:
0280: /**
0281: * Simple helper method to ensure that a schema is always set.
0282: */
0283: protected static String schema(String schema) {
0284: if (schema != null && !"".equals(schema))
0285: return schema;
0286:
0287: return (String) PostgisDataStoreFactory.SCHEMA.sample;
0288: }
0289:
0290: public PostgisDataStore(DataSource dataSource,
0291: JDBCDataStoreConfig config, int optimizeMode)
0292: throws IOException {
0293: super (dataSource, config);
0294: guessDataStoreOptions();
0295: OPTIMIZE_MODE = optimizeMode;
0296:
0297: // use the specific postgis fid mapper factory
0298: setFIDMapperFactory(buildFIDMapperFactory(config));
0299: }
0300:
0301: /**
0302: * Allows subclass to create LockingManager to support their needs.
0303: *
0304: */
0305: protected LockingManager createLockingManager() {
0306: return new InProcessLockingManager();
0307: }
0308:
0309: /**
0310: * Creates a new sql builder for encoding raw sql statements;
0311: *
0312: */
0313: protected PostgisSQLBuilder createSQLBuilder() {
0314: PostgisSQLBuilder builder = new PostgisSQLBuilder(
0315: new SQLEncoderPostgis(), config);
0316: initBuilder(builder);
0317: return builder;
0318: }
0319:
0320: /**
0321: * Attempts to figure out some optimization options, based on some postgis
0322: * metadata. If the version is later than 0.7.2 then bytea will be used
0323: * to read geometries if WKB is enabled. And it will read if GEOS is
0324: * enabled from the version string as well.
0325: *
0326: * @throws IOException
0327: */
0328: protected void guessDataStoreOptions() throws IOException {
0329: PostgisDBInfo dbInfo = getDBInfo();
0330: if (dbInfo == null) { //assume
0331: LOGGER.severe("Could not obtain PostgisDBInfo");
0332: byteaEnabled = true;
0333: byteaWKB = false;
0334: useGeos = true;
0335: schemaEnabled = true;
0336: } else {
0337: byteaEnabled = dbInfo.isByteaEnabled();
0338: if (dbInfo.getMajorVersion() >= 1) {
0339: byteaWKB = true; // force ew wkb writing format
0340: }
0341: useGeos = dbInfo.isGeosEnabled();
0342: schemaEnabled = dbInfo.isSchemaEnabled();
0343: }
0344: }
0345:
0346: /*
0347: * (non-Javadoc)
0348: *
0349: * @see org.geotools.data.DataStore#getTypeNames()
0350: */
0351: public String[] getTypeNames() throws IOException {
0352:
0353: final int TABLE_NAME_COL = 3;
0354: Connection conn = null;
0355: List list = new ArrayList();
0356:
0357: try {
0358: conn = getConnection(Transaction.AUTO_COMMIT);
0359:
0360: DatabaseMetaData meta = conn.getMetaData(); // DB: shouldnt this be done by looking at geometry_columns? or are you trying to allow non-spatial tables in as well?
0361: String[] tableType = { "TABLE", "VIEW" };
0362: ResultSet tables = meta.getTables(null, config
0363: .getDatabaseSchemaName(), "%", tableType);
0364:
0365: while (tables.next()) {
0366: String tableName = tables.getString(TABLE_NAME_COL);
0367:
0368: if (allowTable(tableName)) {
0369: list.add(tableName);
0370: }
0371: }
0372: tables.close();
0373:
0374: return (String[]) list.toArray(new String[list.size()]);
0375: } catch (SQLException sqlException) {
0376: JDBCUtils
0377: .close(conn, Transaction.AUTO_COMMIT, sqlException);
0378: conn = null;
0379:
0380: String message = "Error querying database for list of tables:"
0381: + sqlException.getMessage();
0382: throw new DataSourceException(message, sqlException);
0383: } finally {
0384: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
0385: }
0386:
0387: /*
0388: //Justin's patch from uDig, should be faster, but untested.
0389: Connection conn = null;
0390: String namespace = config.getNamespace();
0391: try {
0392: conn = getConnection(Transaction.AUTO_COMMIT);
0393:
0394: PreparedStatement st = null;
0395:
0396: if (namespace != null && !namespace.trim().equals("")) { //$NON-NLS-1$
0397: st = conn.prepareStatement(
0398: "SELECT distinct a.relname " //$NON-NLS-1$
0399: + "FROM pg_class a, pg_attribute b, pg_namespace c, pg_type d " //$NON-NLS-1$
0400: + "WHERE a.oid = b.attrelid " //$NON-NLS-1$
0401: + "AND b.atttypid = d.oid " //$NON-NLS-1$
0402: + "AND a.relnamespace = c.oid " //$NON-NLS-1$
0403: + "AND c.nspname = ? " //$NON-NLS-1$
0404: + "AND d.typname = ? " //$NON-NLS-1$
0405: + "AND a.relname in (SELECT f_table_name FROM geometry_columns)" //$NON-NLS-1$
0406: );
0407: st.setString(1, namespace);
0408: st.setString(2, "geometry"); //$NON-NLS-1$
0409: }
0410: else {
0411: st = conn.prepareStatement(
0412: "SELECT distinct a.relname " //$NON-NLS-1$
0413: + "FROM pg_class a, pg_attribute b, pg_type d " //$NON-NLS-1$
0414: + "WHERE a.oid = b.attrelid " //$NON-NLS-1$
0415: + "AND b.atttypid = d.oid " //$NON-NLS-1$
0416: + "AND d.typname = ? " //$NON-NLS-1$
0417: + "AND a.relname in (SELECT f_table_name FROM geometry_columns)" //$NON-NLS-1$
0418: );
0419: st.setString(1, "geometry"); //$NON-NLS-1$
0420: }
0421:
0422: ResultSet rs = st.executeQuery();
0423: ArrayList names = new ArrayList();
0424: while(rs.next()) {
0425: String table = rs.getString(1);
0426: if (allowTable(table)){
0427: names.add(table);
0428: }
0429:
0430: }
0431:
0432: return (String[])names.toArray(new String[names.size()]);
0433: }
0434: catch (SQLException sqlException) {
0435: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, sqlException);
0436: conn = null;
0437: throw new DataSourceException( sqlException );
0438: }
0439: finally {
0440: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
0441: }
0442: */
0443: }
0444:
0445: /**
0446: * Retrieve approx bounds of all Features.
0447: * <p>
0448: * This result is suitable for a quick map display, illustrating the data.
0449: * This value is often stored as metadata in databases such as oraclespatial.
0450: * </p>
0451: * @return null as a generic implementation is not provided.
0452: */
0453: public Envelope getEnvelope(String typeName) {
0454: Connection conn = null;
0455:
0456: try {
0457: conn = createConnection();
0458: Statement st = null;
0459: ResultSet rs = null;
0460: Envelope envelope = null;
0461:
0462: FeatureType schema = getSchema(typeName);
0463: String geomName = schema.getDefaultGeometry().getName();
0464:
0465: // optimization, postgis version >= 1.0 contains estimated_extent
0466: // function to query the stats of the table to determine the bbox,
0467: // however, it may return null
0468: if (getDBInfo().getMajorVersion() >= 1) {
0469: //try the estimated_extent([schema], table, geocolumn) function
0470: String q;
0471: String dbSchema = config.getDatabaseSchemaName();
0472: if (!schemaEnabled || dbSchema == null
0473: || "".equals(dbSchema)) {
0474: q = "SELECT AsText(force_2d(envelope(estimated_extent('"
0475: + typeName + "','" + geomName + "'))))";
0476: } else {
0477: q = "SELECT AsText(force_2d(envelope(estimated_extent('"
0478: + dbSchema
0479: + "','"
0480: + typeName
0481: + "','"
0482: + geomName + "'))))";
0483: }
0484: st = conn.createStatement();
0485: rs = st.executeQuery(q);
0486:
0487: if (rs.next()) {
0488: //parse return value
0489: String wkt = rs.getString(1);
0490: if (wkt != null && !wkt.trim().equals("")) { //$NON-NLS-1$
0491: envelope = geometryReader.read(wkt)
0492: .getEnvelopeInternal();
0493:
0494: // expand the bounds by 20% (10% in each direction)
0495: // Works whether or not the bounds are at the origin
0496: double minX = envelope.getMinX();
0497: double minY = envelope.getMinY();
0498: double maxX = envelope.getMaxX();
0499: double maxY = envelope.getMaxY();
0500: double deltaX = (maxX - minX) * 0.1;
0501: double deltaY = (maxY - minY) * 0.1;
0502: envelope.expandToInclude(minX - deltaX, minY
0503: - deltaY);
0504: envelope.expandToInclude(maxX + deltaX, maxY
0505: + deltaY);
0506: } else {
0507: LOGGER
0508: .warning("PostGIS estimated_extent function did not return a result."
0509: + "\nPerhaps 'ANALYZE "
0510: + typeName
0511: + ";' needs to be run or the table is empty?");
0512: }
0513: }
0514:
0515: rs.close();
0516: st.close();
0517: }
0518:
0519: if (envelope == null) {
0520:
0521: //try to generate an approximation
0522: envelope = new Envelope();
0523: //this is an attempt to grab a handful of envelopes without counting the features
0524: final int blockSize = 10; //how many features to grab on each postgis hit
0525: final int fetchAllLimit = 99; //if we don't exceed this value, just fetch all features
0526: //final int upperLimit = 1000000; //aim for this many features
0527: //final int nBlocks = 7; //number of times to hit postgis
0528: //int[] offset = new int[nBlocks];
0529: //automatic range calculation (for tweaking)
0530: //once we hit 100,000 features in our scan, things get really slow
0531: //therefore we'll stop around 50k
0532: //offset[0] = 1;
0533: //double magicNumber = Math.pow(upperLimit, 1.0 / (nBlocks - 1));
0534: //for (int i = 1; i < nBlocks; i++) {
0535: // offset[i] = (int) Math.ceil(offset[i-1] * magicNumber);
0536: // System.out.println(offset[i]);
0537: //}
0538: //offset[0] = 0;
0539: int[] offset = new int[] { 0, 10, 100, 1000, 10000,
0540: 20000, 40000 };
0541:
0542: int hits = 0;
0543: int misses = 0;
0544: for (int i = 0; i < offset.length && misses < 4; i++) {
0545: String limit = " LIMIT " + blockSize + " OFFSET "
0546: + offset[i];
0547: ;
0548: if (i + 1 < offset.length
0549: && offset[i + 1] - offset[i] <= blockSize) {
0550: limit = " LIMIT " + blockSize * 2 + " OFFSET "
0551: + offset[i];
0552: offset[i + 1] = offset[i] + blockSize;
0553: i++;
0554: }
0555: String q = "SELECT AsText(force_2d(envelope("
0556: + geomName + "))) FROM " + typeName;
0557: if (offset[i] > -1) {
0558: q = q + limit;
0559: }
0560: st = conn.createStatement();
0561: rs = st.executeQuery(q);
0562: boolean gotEnvelope = false;
0563: while (rs.next()) {
0564: gotEnvelope = true;
0565: String wkt = rs.getString(1);
0566: if (wkt != null && !wkt.trim().equals("")) { //$NON-NLS-1$
0567: Envelope e = geometryReader.read(wkt)
0568: .getEnvelopeInternal();
0569:
0570: if (envelope.isNull())
0571: envelope.init(e);
0572: else
0573: envelope.expandToInclude(e);
0574: }
0575: }
0576: if (gotEnvelope) {
0577: hits++;
0578: } else {
0579: misses++;
0580: if (hits == 0) { //there are no features!
0581: rs.close();
0582: st.close();
0583: return new Envelope();
0584: }
0585: if (offset[i - 1] < fetchAllLimit) { //just fetch everything
0586: offset[i] = -1;
0587: } else {
0588: //went beyond the last feature
0589: //on our first miss, we move back 50% and try again
0590: //on our second miss, we stop guessing and look between last 2 hits
0591: int min = offset[i - 1];
0592: int max = offset[i];
0593: if (misses == 2) {
0594: min = offset[i - 2];
0595: max = offset[i - 1];
0596: }
0597: if (misses < 3) {
0598: offset[i] = (int) ((min + max) / 2.0);
0599: int width = (int) ((max - min) / (double) (offset.length - i));
0600: for (int j = i + 1; j < offset.length; j++) {
0601: offset[j] = min + (width * (j - i));
0602: }
0603: } else {
0604: rs.close();
0605: st.close();
0606: break;
0607: }
0608: }
0609: i--;
0610: }
0611: rs.close();
0612: st.close();
0613: if (offset[i] == -1)
0614: break;
0615: }
0616:
0617: // expand since this is an approximation
0618: // Works whether or not the bounds are at the origin
0619: double minX = envelope.getMinX();
0620: double minY = envelope.getMinY();
0621: double maxX = envelope.getMaxX();
0622: double maxY = envelope.getMaxY();
0623: double deltaX = (maxX - minX) * 1.0;
0624: double deltaY = (maxY - minY) * 1.0;
0625: envelope.expandToInclude(minX - deltaX, minY - deltaY);
0626: envelope.expandToInclude(maxX + deltaX, maxY + deltaY);
0627:
0628: }
0629: return envelope;
0630: } catch (Exception ignore) {
0631: return null;
0632: } finally {
0633: if (conn != null) {
0634: try {
0635: conn.close();
0636: } catch (SQLException e) {
0637: // I give up
0638: }
0639: }
0640: }
0641: }
0642:
0643: protected boolean allowTable(String tablename) {
0644: if (tablename.equals("geometry_columns")) {
0645: return false;
0646: } else if (tablename.startsWith("spatial_ref_sys")) {
0647: return false;
0648: }
0649:
0650: //others?
0651: return true;
0652: }
0653:
0654: /**
0655: * Override this method to perform a few permission checks before the super
0656: * class has a chance to do its thing.
0657: */
0658: protected FeatureType buildSchema(String typeName, FIDMapper mapper)
0659: throws IOException {
0660: //be sure we can query the necessary tables
0661: //TODO: should spatial_ref_sys be in here?
0662: Connection conn = getConnection(Transaction.AUTO_COMMIT);
0663:
0664: try {
0665: Statement st = conn.createStatement();
0666:
0667: try {
0668: st.execute("SELECT * FROM geometry_columns LIMIT 0;");
0669: } catch (Throwable t) {
0670: String msg = "Error querying relation: geometry_columns."
0671: + " Possible cause:" + t.getLocalizedMessage();
0672: throw new DataSourceException(msg, t);
0673: }
0674: try {
0675: SQLEncoderPostgis encoder = new SQLEncoderPostgis(-1);
0676: encoder.setSupportsGEOS(useGeos);
0677: PostgisSQLBuilder builder = new PostgisSQLBuilder(
0678: encoder, config);
0679: initBuilder(builder);
0680:
0681: st.execute("SELECT * FROM "
0682: + builder.encodeTableName(typeName)
0683: + " LIMIT 0;");
0684: } catch (Throwable t) {
0685: String msg = "Error querying relation:" + typeName
0686: + "." + " Possible cause:"
0687: + t.getLocalizedMessage();
0688: throw new DataSourceException(msg, t);
0689: }
0690: st.close();
0691: } catch (SQLException e) {
0692: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, e);
0693: throw new DataSourceException(e);
0694: } finally {
0695: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
0696: }
0697:
0698: //everything is cool, keep going
0699: return super .buildSchema(typeName, mapper);
0700: }
0701:
0702: /**
0703: * This is a public entry point to the DataStore.
0704: *
0705: * <p>
0706: * We have given some though to changing this api to be based on query.
0707: * </p>
0708: *
0709: * <p>
0710: * Currently this is the only way to retype your features to different
0711: * name spaces.
0712: * </p>
0713: * (non-Javadoc)
0714: *
0715: * @see org.geotools.data.DataStore#getFeatureReader(org.geotools.feature.FeatureType,
0716: * org.geotools.filter.Filter, org.geotools.data.Transaction)
0717: */
0718: public FeatureReader getFeatureReader(
0719: final FeatureType requestType, final Filter filter,
0720: final Transaction transaction) throws IOException {
0721: String typeName = requestType.getTypeName();
0722: FeatureType schemaType = getSchema(typeName);
0723:
0724: int compare = DataUtilities.compare(requestType, schemaType);
0725:
0726: Query query;
0727:
0728: if (compare == 0) {
0729: // they are the same type
0730: //
0731: query = new DefaultQuery(typeName, filter);
0732: } else if (compare == 1) {
0733: // featureType is a proper subset and will require reTyping
0734: //
0735: String[] names = attributeNames(requestType, filter);
0736: query = new DefaultQuery(typeName, filter,
0737: Query.DEFAULT_MAX, names, "getFeatureReader");
0738: } else {
0739: // featureType is not compatiable
0740: //
0741: throw new IOException("Type " + typeName
0742: + " does match request");
0743: }
0744:
0745: if ((filter == Filter.EXCLUDE) || filter.equals(Filter.EXCLUDE)) {
0746: return new EmptyFeatureReader(requestType);
0747: }
0748:
0749: FeatureReader reader = getFeatureReader(query, transaction);
0750:
0751: if (compare == 1) {
0752: reader = new ReTypeFeatureReader(reader, requestType, false);
0753: }
0754:
0755: return reader;
0756: }
0757:
0758: /**
0759: * Gets the list of attribute names required for both featureType and
0760: * filter
0761: *
0762: * @param featureType The FeatureType to get attribute names for.
0763: * @param filter The filter which needs attributes to filter.
0764: *
0765: * @return The list of attribute names required by a filter.
0766: *
0767: * @throws IOException If we can't get the schema.
0768: */
0769: protected String[] attributeNames(FeatureType featureType,
0770: Filter filter) throws IOException {
0771: String typeName = featureType.getTypeName();
0772: FeatureType original = getSchema(typeName);
0773: SQLBuilder sqlBuilder = getSqlBuilder(typeName);
0774:
0775: if (featureType.getAttributeCount() == original
0776: .getAttributeCount()) {
0777: // featureType is complete (so filter must require subset
0778: return DataUtilities.attributeNames(featureType);
0779: }
0780:
0781: String[] typeAttributes = DataUtilities
0782: .attributeNames(featureType);
0783: String[] filterAttributes = DataUtilities
0784: .attributeNames(sqlBuilder.getPostQueryFilter(filter));
0785:
0786: if ((filterAttributes == null)
0787: || (filterAttributes.length == 0)) {
0788: // no filter attributes required
0789: return typeAttributes;
0790: }
0791:
0792: Set set = new HashSet();
0793: set.addAll(Arrays.asList(typeAttributes));
0794: set.addAll(Arrays.asList(filterAttributes));
0795:
0796: if (set.size() == typeAttributes.length) {
0797: // filter required a subset of featureType attributes
0798: return typeAttributes;
0799: } else {
0800: return (String[]) set.toArray(new String[set.size()]);
0801: }
0802: }
0803:
0804: /**
0805: * DOCUMENT ME!
0806: *
0807: * @param typeName
0808: *
0809: * @return DOCUMENT ME!
0810: *
0811: * @throws IOException DOCUMENT ME!
0812: */
0813: public SQLBuilder getSqlBuilder(String typeName) throws IOException {
0814: FeatureTypeInfo info = typeHandler.getFeatureTypeInfo(typeName);
0815: int srid = -1;
0816:
0817: SQLEncoderPostgis encoder = new SQLEncoderPostgis();
0818: encoder.setSupportsGEOS(useGeos);
0819: encoder.setFIDMapper(typeHandler.getFIDMapper(typeName));
0820:
0821: if (info.getSchema().getDefaultGeometry() != null) {
0822: String geom = info.getSchema().getDefaultGeometry()
0823: .getName();
0824: srid = info.getSRID(geom);
0825: encoder.setDefaultGeometry(geom);
0826: }
0827:
0828: encoder.setFeatureType(info.getSchema());
0829: encoder.setSRID(srid);
0830: encoder.setLooseBbox(looseBbox);
0831:
0832: PostgisSQLBuilder builder = new PostgisSQLBuilder(encoder,
0833: config, info.getSchema());
0834: initBuilder(builder);
0835:
0836: return builder;
0837: }
0838:
0839: protected void initBuilder(PostgisSQLBuilder builder) {
0840: builder.setWKBEnabled(WKBEnabled);
0841: builder.setByteaEnabled(byteaEnabled);
0842: builder.setSchemaEnabled(schemaEnabled);
0843: }
0844:
0845: /**
0846: * DOCUMENT ME!
0847: *
0848: * @param tableName
0849: * @param geometryColumnName
0850: *
0851: *
0852: * @throws IOException DOCUMENT ME!
0853: * @throws DataSourceException DOCUMENT ME!
0854: */
0855: protected int determineSRID(String tableName,
0856: String geometryColumnName) throws IOException {
0857: Connection dbConnection = null;
0858:
0859: try {
0860: String dbSchema = config.getDatabaseSchemaName();
0861: StringBuffer sql = new StringBuffer();
0862: sql.append("SELECT srid FROM geometry_columns WHERE ");
0863: if (schemaEnabled && dbSchema != null
0864: && dbSchema.length() > 0) {
0865: sql.append("f_table_schema='");
0866: sql.append(dbSchema);
0867: sql.append("' AND ");
0868: }
0869: sql.append("f_table_name='");
0870: sql.append(tableName);
0871: sql.append("' AND f_geometry_column='");
0872: sql.append(geometryColumnName);
0873: sql.append("';");
0874:
0875: String sqlStatement = sql.toString();
0876: LOGGER.fine("srid statement is " + sqlStatement);
0877:
0878: dbConnection = getConnection(Transaction.AUTO_COMMIT);
0879: Statement statement = dbConnection.createStatement();
0880: ResultSet result = statement.executeQuery(sqlStatement);
0881:
0882: if (result.next()) {
0883: int retSrid = result.getInt("srid");
0884: JDBCUtils.close(statement);
0885:
0886: return retSrid;
0887: }
0888: result.close();
0889:
0890: //try asking the first feature for its srid
0891: sql = new StringBuffer();
0892: sql.append("SELECT SRID(\"");
0893: sql.append(geometryColumnName);
0894: sql.append("\") FROM \"");
0895: if (schemaEnabled && dbSchema != null
0896: && dbSchema.length() > 0) {
0897: sql.append(dbSchema);
0898: sql.append("\".\"");
0899: }
0900: sql.append(tableName);
0901: sql.append("\" LIMIT 1");
0902: sqlStatement = sql.toString();
0903: result = statement.executeQuery(sqlStatement);
0904: if (result.next()) {
0905: int retSrid = result.getInt(1);
0906: JDBCUtils.close(statement);
0907: return retSrid;
0908: }
0909:
0910: String mesg = "No geometry column row for srid in table: "
0911: + tableName + ", geometry column "
0912: + geometryColumnName;
0913: throw new DataSourceException(mesg);
0914: } catch (SQLException sqle) {
0915: String message = sqle.getMessage();
0916: throw new DataSourceException(message, sqle);
0917: } finally {
0918: JDBCUtils
0919: .close(dbConnection, Transaction.AUTO_COMMIT, null);
0920: }
0921: }
0922:
0923: /**
0924: * Provides the default implementation of determining the FID column.
0925: *
0926: * <p>
0927: * The default implementation of determining the FID column name is to use
0928: * the primary key as the FID column. If no primary key is present, null
0929: * will be returned. Sub classes can override this behaviour to define
0930: * primary keys for vendor specific cases.
0931: * </p>
0932: *
0933: * <p>
0934: * There is an unresolved issue as to what to do when there are multiple
0935: * primary keys. Maybe a restriction that table much have a single column
0936: * primary key is appropriate.
0937: * </p>
0938: *
0939: * <p>
0940: * This should not be called by subclasses to retreive the FID column name.
0941: * Instead, subclasses should call getFeatureTypeInfo(String) to get the
0942: * FeatureTypeInfo for a feature type and get the fidColumn name from the
0943: * fidColumn name memeber.
0944: * </p>
0945: *
0946: * @param array DOCUMENT ME!
0947: * @param value DOCUMENT ME!
0948: *
0949: * @return The name of the primay key column or null if one does not exist.
0950: */
0951:
0952: // protected String determineFidColumnName(String typeName)
0953: // throws IOException {
0954: // String fidColumn = super.determineFidColumnName(typeName);
0955: //
0956: // if(fidColumn == null)
0957: // fidColumn = DEFAULT_FID_COLUMN;
0958: //
0959: // return fidColumn;
0960: // }
0961: /*
0962: private static boolean isPresent(String[] array, String value) {
0963: if (array != null) {
0964: for (int i = 0; i < array.length; i++) {
0965: if ((array[i] != null) && (array[i].equals(value))) {
0966: return (true);
0967: }
0968: }
0969: }
0970:
0971: return (false);
0972: }
0973: */
0974: /**
0975: * Constructs an AttributeType from a row in a ResultSet. The ResultSet
0976: * contains the information retrieved by a call to getColumns() on the
0977: * DatabaseMetaData object. This information can be used to construct an
0978: * Attribute Type.
0979: *
0980: * <p>
0981: * This implementation construct an AttributeType using the default JDBC
0982: * type mappings defined in JDBCDataStore. These type mappings only handle
0983: * native Java classes and SQL standard column types. If a geometry type
0984: * is found then getGeometryAttribute is called.
0985: * </p>
0986: *
0987: * <p>
0988: * Note: Overriding methods must never move the current row pointer in the
0989: * result set.
0990: * </p>
0991: *
0992: * @param metadataRs The ResultSet containing the result of a
0993: * DatabaseMetaData.getColumns call.
0994: *
0995: * @return The AttributeType built from the ResultSet.
0996: *
0997: * @throws IOException If an error occurs processing the ResultSet.
0998: */
0999: protected AttributeType buildAttributeType(ResultSet metadataRs)
1000: throws IOException {
1001: try {
1002: final int TABLE_NAME = 3;
1003: final int COLUMN_NAME = 4;
1004: final int TYPE_NAME = 6;
1005: final int NULLABLE = 11;
1006: String typeName = metadataRs.getString(TYPE_NAME);
1007:
1008: if (typeName.equals("geometry")) {
1009: String tableName = metadataRs.getString(TABLE_NAME);
1010: String columnName = metadataRs.getString(COLUMN_NAME);
1011:
1012: // check for nullability
1013: int nullCode = metadataRs.getInt(NULLABLE);
1014: boolean nillable = true;
1015: switch (nullCode) {
1016: case DatabaseMetaData.columnNoNulls:
1017: nillable = false;
1018: break;
1019:
1020: case DatabaseMetaData.columnNullable:
1021: nillable = true;
1022: break;
1023:
1024: case DatabaseMetaData.columnNullableUnknown:
1025: nillable = true;
1026: break;
1027: }
1028:
1029: return getGeometryAttribute(tableName, columnName,
1030: nillable);
1031: } else {
1032: return super .buildAttributeType(metadataRs);
1033: }
1034: } catch (SQLException e) {
1035: throw new IOException("Sql error occurred: "
1036: + e.getMessage());
1037: }
1038: }
1039:
1040: /**
1041: * @see org.geotools.data.jdbc.JDBCDataStore#buildFIDMapperFactory(org.geotools.data.jdbc.JDBCDataStoreConfig)
1042: */
1043: protected FIDMapperFactory buildFIDMapperFactory(
1044: JDBCDataStoreConfig config) {
1045: return new PostgisFIDMapperFactory(config);
1046: }
1047:
1048: protected FIDMapper buildFIDMapper(String typeName,
1049: FIDMapperFactory factory) throws IOException {
1050: Connection conn = null;
1051:
1052: try {
1053: conn = getConnection(Transaction.AUTO_COMMIT);
1054:
1055: String dbSchema = config.getDatabaseSchemaName();
1056: FIDMapper mapper = factory.getMapper(null, dbSchema,
1057: typeName, conn);
1058:
1059: return mapper;
1060: } finally {
1061: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
1062: }
1063: }
1064:
1065: /**
1066: * Returns an attribute type for a geometry column in a feature table.
1067: *
1068: * @param tableName The feature table name.
1069: * @param columnName The geometry column name.
1070: * @param nillable
1071: *
1072: * @return Geometric attribute.
1073: *
1074: * @throws IOException DOCUMENT ME!
1075: *
1076: * @task REVISIT: combine with querySRID, as they use the same select
1077: * statement.
1078: * @task This should probably take a Transaction, so if things mess up then
1079: * we can rollback.
1080: */
1081: AttributeType getGeometryAttribute(String tableName,
1082: String columnName, boolean nillable) throws IOException {
1083: Connection dbConnection = null;
1084: Class type = null;
1085: int srid = 0;
1086: try {
1087: dbConnection = getConnection(Transaction.AUTO_COMMIT);
1088: StringBuffer sql = new StringBuffer();
1089: sql.append("SELECT type FROM geometry_columns WHERE ");
1090: String dbSchema = config.getDatabaseSchemaName();
1091: if (schemaEnabled && dbSchema != null
1092: && dbSchema.length() > 0) {
1093: sql.append("f_table_schema='");
1094: sql.append(dbSchema);
1095: sql.append("' AND ");
1096: }
1097: sql.append("f_table_name='");
1098: sql.append(tableName);
1099: sql.append("' AND f_geometry_column='");
1100: sql.append(columnName);
1101: sql.append("';");
1102:
1103: String sqlStatement = sql.toString();
1104: LOGGER.fine("geometry type sql statement is "
1105: + sqlStatement);
1106:
1107: String geometryType = null;
1108:
1109: // retrieve the result set from the JDBC driver
1110: Statement statement = dbConnection.createStatement();
1111: ResultSet result = statement.executeQuery(sqlStatement);
1112:
1113: if (result.next()) {
1114: geometryType = result.getString("type");
1115: LOGGER.fine("geometry type is: " + geometryType);
1116: }
1117: result.close();
1118:
1119: if (geometryType == null) {
1120: //no geometry_columns entry, try grabbing a feature
1121: sql = new StringBuffer();
1122: if (WKBEnabled) {
1123: sql.append("SELECT encode(AsBinary(force_2d(\"");
1124: sql.append(columnName);
1125: sql.append("\"), 'XDR'),'base64') FROM \"");
1126: } else {
1127: sql.append("SELECT AsText(\"");
1128: sql.append(columnName);
1129: sql.append("\") FROM \"");
1130: }
1131: if (schemaEnabled && dbSchema != null
1132: && dbSchema.length() > 0) {
1133: sql.append(dbSchema);
1134: sql.append("\".\"");
1135: }
1136: sql.append(tableName);
1137: sql.append("\" LIMIT 1");
1138: sqlStatement = sql.toString();
1139: result = statement.executeQuery(sqlStatement);
1140: if (result.next()) {
1141: AttributeIO attrIO = getGeometryAttributeIO(null,
1142: null);
1143: Object object = attrIO.read(result, 1);
1144: if (object instanceof Geometry) {
1145: Geometry geom = (Geometry) object;
1146: geometryType = geom.getGeometryType()
1147: .toUpperCase();
1148: type = geom.getClass();
1149: srid = geom.getSRID(); //will return 0 unless we support EWKB
1150: }
1151: }
1152: result.close();
1153: }
1154: statement.close();
1155:
1156: if (geometryType == null) {
1157: String msg = " no geometry found in the GEOMETRY_COLUMNS table"
1158: + " for "
1159: + tableName
1160: + " of the postgis install. A row"
1161: + " for "
1162: + columnName
1163: + " is required"
1164: + " for geotools to work correctly";
1165: throw new DataSourceException(msg);
1166: }
1167:
1168: if (type == null) {
1169: type = (Class) GEOM_TYPE_MAP.get(geometryType);
1170: }
1171:
1172: } catch (SQLException sqe) {
1173: throw new IOException("An SQL exception occurred: "
1174: + sqe.getMessage());
1175: } finally {
1176: JDBCUtils
1177: .close(dbConnection, Transaction.AUTO_COMMIT, null);
1178: }
1179:
1180: if (srid < 1) {
1181: //try again
1182: srid = determineSRID(tableName, columnName);
1183: }
1184: CoordinateReferenceSystem crs = null;
1185:
1186: try {
1187: crs = getPostgisAuthorityFactory().createCRS(srid);
1188: } catch (FactoryException e) {
1189: // use EPSG code as fallback
1190: try {
1191: crs = CRS.decode("EPSG:" + srid);
1192: } catch (NoSuchAuthorityCodeException e1) {
1193: crs = null;
1194: } catch (FactoryException e1) {
1195: crs = null;
1196: }
1197: }
1198:
1199: return AttributeTypeFactory.newAttributeType(columnName, type,
1200: nillable, 0, null, crs);
1201: }
1202:
1203: private PostgisAuthorityFactory getPostgisAuthorityFactory() {
1204: if (paf == null) {
1205: paf = new PostgisAuthorityFactory(dataSource);
1206: }
1207:
1208: return paf;
1209: }
1210:
1211: /**
1212: * Gets the sql geometry column name for this type.
1213: *
1214: * @param type DOCUMENT ME!
1215: *
1216: * @return DOCUMENT ME!
1217: *
1218: * @throws RuntimeException DOCUMENT ME!
1219: *
1220: * @task TODO: test this, I can just make sure it compiles.
1221: */
1222: private String getGeometrySQLTypeName(Class type) {
1223: String res = (String) GEOM_CLASS_MAPPINGS.get(type);
1224:
1225: if (res == null) {
1226: throw new RuntimeException("Unknown type name for class "
1227: + type + " please update GEOMETRY_MAPPINGS");
1228: }
1229:
1230: return res;
1231: }
1232:
1233: /**
1234: * Creates a FeatureType in this instance of the PostgisDataStore. Since we
1235: * don't yet know which attribute in the FeatureType is the primary key, we
1236: * will create our own called "fid_tablename", which has its own sequence
1237: * called "tablename_fid_seq". The user should not interact with this
1238: * column, although its value will be the FID. This method currently assumes
1239: * there are only 2 dimensions.
1240: *
1241: * @throws IOException
1242: * if something goes horribly wrong or the table already exists
1243: * @see org.geotools.data.DataStore#createSchema(org.geotools.feature.FeatureType)
1244: */
1245: public void createSchema(FeatureType featureType)
1246: throws IOException {
1247: String tableName = featureType.getTypeName();
1248:
1249: String lcTableName = tableName.toLowerCase();
1250:
1251: AttributeType[] attributeType = featureType.getAttributeTypes();
1252: String dbSchema = config.getDatabaseSchemaName();
1253:
1254: PostgisSQLBuilder sqlb = createSQLBuilder();
1255:
1256: //the featureType won't tell us who the primary key is, so we'll create
1257: //our own "fid_tablename". Later when we load the featureType, we will
1258: //pretend we didn't see fid_tablename when we return the attributes.
1259: String fidColumn = lcTableName + "_fid";
1260:
1261: //make sure the fid column doesn't already exist
1262: for (int i = 0; i < attributeType.length; i++) {
1263: if (attributeType[i].getName().equalsIgnoreCase(fidColumn)) {
1264: String message = "The featuretype cannot contain the column "
1265: + fidColumn
1266: + ", since this is used as the hidden FID column";
1267: throw new IOException(message);
1268: }
1269: }
1270:
1271: Connection con = this .getConnection(Transaction.AUTO_COMMIT);
1272: Statement st = null;
1273:
1274: boolean shouldExecute = !tablePresent(tableName, con);
1275:
1276: try {
1277: con.setAutoCommit(false);
1278: st = con.createStatement();
1279:
1280: StringBuffer sql = new StringBuffer("CREATE TABLE ");
1281: sql.append(sqlb.encodeTableName(tableName));
1282: sql.append(" (");
1283: sql.append(sqlb.encodeColumnName(fidColumn));
1284: sql.append(" serial PRIMARY KEY,");
1285: sql.append(makeSqlCreate(attributeType));
1286: sql.append(");");
1287:
1288: String sqlStr = sql.toString();
1289: LOGGER.info(sqlStr);
1290:
1291: if (shouldExecute) {
1292: st.execute(sqlStr);
1293: }
1294:
1295: //fix from pr: it may be that table existed and then was dropped
1296: //without removing its geometry info from GEOMETRY_COLUMNS.
1297: //To support this, try to delete before inserting.
1298: //Preserving case for table names gives problems,
1299: //so convert to lower case
1300:
1301: sql = new StringBuffer(
1302: "DELETE FROM GEOMETRY_COLUMNS WHERE f_table_catalog=''");
1303: sql.append(" AND f_table_schema = '");
1304: sql.append(dbSchema);
1305: sql.append("'");
1306: sql.append("AND f_table_name = '");
1307: sql.append(tableName);
1308: sql.append("';");
1309:
1310: //prints statement for later reuse
1311: sqlStr = sql.toString();
1312: LOGGER.info(sqlStr);
1313:
1314: if (shouldExecute) {
1315: st.execute(sqlStr);
1316: }
1317:
1318: //Ok, so Paolo Rizzi suggested that we get rid of our hand-adding
1319: //of geometry column information and use AddGeometryColumn instead
1320: //as it is better (this is in GEOT-379, he attached an extended
1321: //datastore that does postgis fixes). But I am pretty positive
1322: //the reason we are doing things this way is to preserve the order
1323: //of FeatureTypes. I know this is fairly silly, from most
1324: //information perspectives, but from another perspective it seems
1325: //to make sense - if you were transfering a featureType from one
1326: //data store to another then it should have the same order, right?
1327: //And order is important in WFS. There are a few caveats though
1328: //for one I don't even know if things work right. I imagine the
1329: //proper constraints that a AddGeometryColumn operation does are
1330: //not set in our hand version, for one. I would feel better about
1331: //ignoring the order and just doing things as we like if we had
1332: //views in place, if users could add the schema, and then be able
1333: //to get it back in exactly the order they wanted. So for now
1334: //let's leave things as is, and maybe talk about it in an irc. -ch
1335: for (int i = 0; i < attributeType.length; i++) {
1336: if (!(attributeType[i] instanceof GeometryAttributeType)) {
1337: continue;
1338: }
1339: GeometryAttributeType geomAttribute = (GeometryAttributeType) attributeType[i];
1340: String columnName = attributeType[i].getName();
1341:
1342: CoordinateReferenceSystem refSys = geomAttribute
1343: .getCoordinateSystem();
1344: int SRID;
1345:
1346: if (refSys != null) {
1347: try {
1348: Set ident = refSys.getIdentifiers();
1349: if ((ident == null || ident.isEmpty())
1350: && refSys == DefaultGeographicCRS.WGS84) {
1351: SRID = 4326;
1352: } else {
1353: String code = ((NamedIdentifier) ident
1354: .toArray()[0]).getCode();
1355: SRID = Integer.parseInt(code);
1356: }
1357: } catch (Exception e) {
1358: LOGGER.warning("SRID could not be determined");
1359: SRID = -1;
1360: }
1361: } else {
1362: SRID = -1;
1363: }
1364:
1365: // DatabaseMetaData metaData = con.getMetaData();
1366: // ResultSet rs = metaData.getCatalogs();
1367: // rs.next();
1368: //
1369: // //String dbName = rs.getString(1);
1370: // rs.close();
1371:
1372: String typeName = null;
1373:
1374: //this construct seems unnecessary, since we already would
1375: //pass over if this wasn't a geometry...
1376: Class type = geomAttribute.getType();
1377:
1378: if (geomAttribute instanceof GeometryAttributeType) {
1379: typeName = getGeometrySQLTypeName(type);
1380: } else {
1381: typeName = (String) CLASS_MAPPINGS.get(type);
1382: }
1383:
1384: if (typeName != null) {
1385: // statementSQL = new StringBuffer(
1386: // "SELECT AddGeometryColumn('" + dbSchema + "','"
1387: // + tableName + "','" + attributeType[i].getName()
1388: // + "','" + SRID + "','" + typeName + "',2);"
1389: // ); //assumes 2-D
1390:
1391: //add a row to the geometry_columns table
1392: sql = new StringBuffer(
1393: "INSERT INTO GEOMETRY_COLUMNS VALUES (");
1394: sql.append("'','");
1395: sql.append(dbSchema);
1396: sql.append("','");
1397: sql.append(tableName);
1398: sql.append("','");
1399: sql.append(columnName);
1400: sql.append("',2,");
1401: sql.append(SRID);
1402: sql.append(",'");
1403: sql.append(typeName);
1404: sql.append("');");
1405:
1406: sqlStr = sql.toString();
1407: LOGGER.info(sqlStr);
1408:
1409: if (shouldExecute) {
1410: st.execute(sqlStr);
1411: }
1412:
1413: //add geometry constaints to the table
1414: if (SRID > -1) {
1415: sql = new StringBuffer("ALTER TABLE ");
1416: sql.append(sqlb.encodeTableName(tableName));
1417: sql.append(" ADD CONSTRAINT enforce_srid_");
1418: sql.append(columnName);
1419: sql.append(" CHECK (SRID(");
1420: sql.append(sqlb.encodeColumnName(columnName));
1421: sql.append(") = ");
1422: sql.append(SRID);
1423: sql.append(");");
1424: sqlStr = sql.toString();
1425: LOGGER.info(sqlStr);
1426: if (shouldExecute) {
1427: st.execute(sqlStr);
1428: }
1429: }
1430:
1431: sql = new StringBuffer("ALTER TABLE ");
1432: sql.append(sqlb.encodeTableName(tableName));
1433: sql.append(" ADD CONSTRAINT enforce_dims_");
1434: sql.append(columnName);
1435: sql.append(" CHECK (ndims(");
1436: sql.append(sqlb.encodeColumnName(columnName));
1437: sql.append(") = 2);");
1438: sqlStr = sql.toString();
1439: LOGGER.info(sqlStr);
1440: if (shouldExecute) {
1441: st.execute(sqlStr);
1442: }
1443:
1444: if (!typeName.equals("GEOMETRY")) {
1445: sql = new StringBuffer("ALTER TABLE ");
1446: sql.append(sqlb.encodeTableName(tableName));
1447: sql.append(" ADD CONSTRAINT enforce_geotype_");
1448: sql.append(columnName);
1449: sql.append(" CHECK (geometrytype(");
1450: sql.append(sqlb.encodeColumnName(columnName));
1451: sql.append(") = '");
1452: sql.append(typeName);
1453: sql.append("'::text OR ");
1454: sql.append(sqlb.encodeColumnName(columnName));
1455: sql.append(" IS NULL);");
1456: sqlStr = sql.toString();
1457: LOGGER.info(sqlStr);
1458: if (shouldExecute) {
1459: st.execute(sqlStr);
1460: }
1461: }
1462:
1463: } else {
1464: LOGGER.warning("Error: " + geomAttribute.getName()
1465: + " unknown type!!!");
1466: }
1467:
1468: //also build a spatial index on each geometry column.
1469: sql = new StringBuffer("CREATE INDEX spatial_");
1470: sql.append(tableName);
1471: sql.append("_");
1472: sql.append(attributeType[i].getName().toLowerCase());
1473: sql.append(" ON ");
1474: sql.append(sqlb.encodeTableName(tableName));
1475: sql.append(" USING GIST (");
1476: sql.append(sqlb.encodeColumnName(attributeType[i]
1477: .getName()));
1478: sql.append(");");
1479:
1480: sqlStr = sql.toString();
1481: LOGGER.info(sqlStr);
1482:
1483: if (shouldExecute) {
1484: st.execute(sqlStr);
1485: }
1486: }
1487:
1488: con.commit();
1489:
1490: } catch (SQLException e) {
1491: try {
1492: if (con != null) {
1493: con.rollback();
1494: }
1495: } catch (SQLException sqle) {
1496: throw new IOException(sqle.getMessage());
1497: }
1498:
1499: throw (IOException) new IOException(e.getMessage())
1500: .initCause(e);
1501: } finally {
1502: try {
1503: if (st != null) {
1504: st.close();
1505: }
1506: } catch (SQLException e) {
1507: throw new IOException(e.getMessage());
1508: } finally {
1509: try {
1510: if (con != null) {
1511: con.setAutoCommit(true);
1512: con.close();
1513: }
1514: } catch (SQLException e) {
1515: throw new IOException(e.getMessage());
1516: }
1517: }
1518: }
1519:
1520: if (!shouldExecute) {
1521: throw new IOException("The table " + tableName
1522: + " already exists.");
1523: }
1524: }
1525:
1526: // /**
1527: // * Returns the sql type name given the SQL type code
1528: // *
1529: // * @param typeCode
1530: // *
1531: // *
1532: // * @throws RuntimeException DOCUMENT ME!
1533: // */
1534: // private String getSQLTypeName(int typeCode) {
1535: // Class typeClass = (Class) TYPE_MAPPINGS.get(new Integer(typeCode));
1536: //
1537: // if (typeClass == null) {
1538: // throw new RuntimeException("Unknown type " + typeCode
1539: // + " please update TYPE_MAPPINGS");
1540: // }
1541: //
1542: // String typeName = (String) CLASS_MAPPINGS.get(typeClass);
1543: //
1544: // if (typeName == null) {
1545: // throw new RuntimeException("Unknown type name for class "
1546: // + typeClass.getName() + " please update CLASS_MAPPINGS");
1547: // }
1548: //
1549: // return typeName;
1550: // }
1551:
1552: private StringBuffer makeSqlCreate(AttributeType[] attributeType)
1553: throws IOException {
1554: StringBuffer buf = new StringBuffer("");
1555:
1556: for (int i = 0; i < attributeType.length; i++) {
1557: String typeName = null;
1558: typeName = (String) CLASS_MAPPINGS.get(attributeType[i]
1559: .getType());
1560: if (typeName == null)
1561: typeName = (String) GEOM_CLASS_MAPPINGS
1562: .get(attributeType[i].getType());
1563:
1564: if (typeName != null) {
1565: if (attributeType[i] instanceof GeometryAttributeType) {
1566: typeName = "GEOMETRY";
1567: } else if (typeName.equals("VARCHAR")) {
1568: int length = -1;
1569: Filter f = attributeType[i].getRestriction();
1570: if (f != null
1571: && f != Filter.EXCLUDE
1572: && f != Filter.INCLUDE
1573: && (f instanceof PropertyIsLessThan || f instanceof PropertyIsLessThanOrEqualTo)) {
1574: try {
1575: CompareFilter cf = (CompareFilter) f;
1576: if (cf.getLeftValue() instanceof LengthFunction) {
1577: length = Integer
1578: .parseInt(((LiteralExpression) cf
1579: .getRightValue())
1580: .getLiteral()
1581: .toString());
1582: } else {
1583: if (cf.getRightValue() instanceof LengthFunction) {
1584: length = Integer
1585: .parseInt(((LiteralExpression) cf
1586: .getLeftValue())
1587: .getLiteral()
1588: .toString());
1589: }
1590: }
1591: } catch (NumberFormatException e) {
1592: length = 256;
1593: }
1594: } else {
1595: length = 256;
1596: }
1597:
1598: if (length < 1) {
1599: LOGGER
1600: .warning("FeatureType did not specify string length; defaulted to 256");
1601: length = 256;
1602: } else if (length > MAX_ALLOWED_VALUE) {
1603: length = MAX_ALLOWED_VALUE;
1604: }
1605: typeName = typeName + "(" + length + ")";
1606: }
1607:
1608: if (!attributeType[i].isNillable()) {
1609: typeName = typeName + " NOT NULL";
1610: }
1611:
1612: //TODO review!!! Is toString() always OK???
1613: Object defaultValue = attributeType[i]
1614: .createDefaultValue();
1615:
1616: if (defaultValue != null) {
1617: typeName = typeName + " DEFAULT '"
1618: + defaultValue.toString() + "'";
1619: }
1620:
1621: buf.append(" \"" + attributeType[i].getName() + "\" "
1622: + typeName + ",");
1623:
1624: } else {
1625: String msg;
1626: if (attributeType[i] == null) {
1627: msg = "AttributeType was null!";
1628: } else {
1629: msg = "Type '" + attributeType[i].getType()
1630: + "' not supported!";
1631: }
1632: throw (new IOException(msg));
1633: }
1634: }
1635:
1636: return buf.deleteCharAt(buf.length() - 1);
1637: }
1638:
1639: /**
1640: * DOCUMENT ME!
1641: *
1642: * @param table
1643: * @param con
1644: *
1645: *
1646: * @throws IOException DOCUMENT ME!
1647: * @throws DataSourceException DOCUMENT ME!
1648: */
1649: private boolean tablePresent(String table, Connection con)
1650: throws IOException {
1651: final int TABLE_NAME_COL = 3;
1652: Connection conn = null;
1653: //List list = new ArrayList();
1654:
1655: try {
1656: conn = getConnection(Transaction.AUTO_COMMIT);
1657:
1658: DatabaseMetaData meta = conn.getMetaData();
1659: String[] tableType = { "TABLE" };
1660: ResultSet tables = meta.getTables(null, config
1661: .getDatabaseSchemaName(), "%", tableType);
1662:
1663: while (tables.next()) {
1664: String tableName = tables.getString(TABLE_NAME_COL);
1665:
1666: if (allowTable(tableName) && (tableName != null)
1667: && (tableName.equalsIgnoreCase(table))) {
1668: return (true);
1669: }
1670: }
1671:
1672: return false;
1673: } catch (SQLException sqlException) {
1674: JDBCUtils
1675: .close(conn, Transaction.AUTO_COMMIT, sqlException);
1676: conn = null;
1677:
1678: String message = "Error querying database for list of tables:"
1679: + sqlException.getMessage();
1680: throw new DataSourceException(message, sqlException);
1681: } finally {
1682: JDBCUtils.close(conn, Transaction.AUTO_COMMIT, null);
1683: }
1684: }
1685:
1686: /**
1687: * DOCUMENT ME!
1688: *
1689: * @param query
1690: *
1691: *
1692: * @throws IOException DOCUMENT ME!
1693: */
1694: /**
1695: * Get propertyNames in a safe manner.
1696: *
1697: * <p>
1698: * Method wil figure out names from the schema for query.getTypeName(), if
1699: * query getPropertyNames() is <code>null</code>, or
1700: * query.retrieveAllProperties is <code>true</code>.
1701: * </p>
1702: *
1703: * @param query
1704: *
1705: *
1706: * @throws IOException
1707: */
1708: /*
1709: private String[] propertyNames(Query query) throws IOException {
1710: String[] names = query.getPropertyNames();
1711:
1712: if ((names == null) || query.retrieveAllProperties()) {
1713: String typeName = query.getTypeName();
1714: FeatureType schema = getSchema(typeName);
1715:
1716: names = new String[schema.getAttributeCount()];
1717:
1718: for (int i = 0; i < schema.getAttributeCount(); i++) {
1719: names[i] = schema.getAttributeType(i).getName();
1720: }
1721: }
1722:
1723: return names;
1724: }
1725: */
1726: /**
1727: * @see org.geotools.data.DataStore#updateSchema(java.lang.String,
1728: * org.geotools.feature.FeatureType)
1729: */
1730: public void updateSchema(String typeName, FeatureType featureType)
1731: throws IOException {
1732: throw new IOException(
1733: "PostgisDataStore.updateSchema not yet implemented");
1734: //TODO: implement updateSchema
1735: }
1736:
1737: /**
1738: * Default implementation based on getFeatureReader and getFeatureWriter.
1739: *
1740: * <p>
1741: * We should be able to optimize this to only get the RowSet once
1742: * </p>
1743: *
1744: * @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
1745: */
1746: public FeatureSource getFeatureSource(String typeName)
1747: throws IOException {
1748: if (!typeHandler.getFIDMapper(typeName).isVolatile()
1749: || allowWriteOnVolatileFIDs) {
1750: LOGGER.fine("get Feature source called on " + typeName);
1751:
1752: if (OPTIMIZE_MODE == OPTIMIZE_SQL) {
1753: LOGGER.fine("returning pg feature locking");
1754:
1755: return createFeatureLockingInternal(this ,
1756: getSchema(typeName));
1757: }
1758:
1759: // default
1760: if (getLockingManager() != null) {
1761: // Use default JDBCFeatureLocking that delegates all locking
1762: // the getLockingManager
1763: LOGGER.fine("returning jdbc feature locking");
1764:
1765: return new JDBCFeatureLocking(this , getSchema(typeName));
1766: } else {
1767: LOGGER
1768: .fine("returning jdbc feature store (lock manager is null)");
1769:
1770: // subclass should provide a FeatureLocking implementation
1771: // but for now we will simply forgo all locking
1772: return new JDBCFeatureStore(this , getSchema(typeName));
1773: }
1774: } else {
1775: return new JDBCFeatureSource(this , getSchema(typeName));
1776: }
1777: }
1778:
1779: public PostgisFeatureLocking createFeatureLockingInternal(
1780: PostgisDataStore ds, FeatureType type) throws IOException {
1781:
1782: return new PostgisFeatureLocking(ds, type);
1783: }
1784:
1785: /**
1786: * DOCUMENT ME!
1787: *
1788: * @param fReader
1789: * @param queryData
1790: *
1791: *
1792: * @throws IOException DOCUMENT ME!
1793: */
1794: protected JDBCFeatureWriter createFeatureWriter(
1795: FeatureReader fReader, QueryData queryData)
1796: throws IOException {
1797: PostgisSQLBuilder sqlBuilder = (PostgisSQLBuilder) getSqlBuilder(fReader
1798: .getFeatureType().getTypeName());
1799: PostgisFeatureWriter postgisFeatureWriter = new PostgisFeatureWriter(
1800: fReader, queryData, WKBEnabled, byteaWKB, sqlBuilder);
1801: return postgisFeatureWriter;
1802: }
1803:
1804: /**
1805: * Retrieve a FeatureWriter over entire dataset.
1806: *
1807: * <p>
1808: * Quick notes: This FeatureWriter is often used to add new content, or
1809: * perform summary calculations over the entire dataset.
1810: * </p>
1811: *
1812: * <p>
1813: * Subclass may wish to implement an optimized featureWriter for these
1814: * operations.
1815: * </p>
1816: *
1817: * <p>
1818: * It should provide Feature for next() even when hasNext() is
1819: * <code>false</code>.
1820: * </p>
1821: *
1822: * <p>
1823: * Subclasses are responsible for checking with the lockingManger unless
1824: * they are providing their own locking support.
1825: * </p>
1826: *
1827: * @param typeName
1828: * @param transaction
1829: *
1830: *
1831: * @throws IOException
1832: *
1833: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String,
1834: * boolean, org.geotools.data.Transaction)
1835: */
1836: public FeatureWriter getFeatureWriter(String typeName,
1837: Transaction transaction) throws IOException {
1838: return getFeatureWriter(typeName, Filter.INCLUDE, transaction);
1839: }
1840:
1841: /*
1842: * (non-Javadoc)
1843: *
1844: * @see org.geotools.data.DataStore#getFeatureWriterAppend(java.lang.String,
1845: * org.geotools.data.Transaction)
1846: */
1847:
1848: /**
1849: * Retrieve a FeatureWriter for creating new content.
1850: *
1851: * <p>
1852: * Subclass may wish to implement an optimized featureWriter for this
1853: * operation. One based on prepared statements is a possibility, as we do
1854: * not require a ResultSet.
1855: * </p>
1856: *
1857: * <p>
1858: * To allow new content the FeatureWriter should provide Feature for next()
1859: * even when hasNext() is <code>false</code>.
1860: * </p>
1861: *
1862: * <p>
1863: * Subclasses are responsible for checking with the lockingManger unless
1864: * they are providing their own locking support.
1865: * </p>
1866: *
1867: * @param typeName
1868: * @param transaction
1869: *
1870: *
1871: * @throws IOException
1872: *
1873: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String,
1874: * boolean, org.geotools.data.Transaction)
1875: */
1876: public FeatureWriter getFeatureWriterAppend(String typeName,
1877: Transaction transaction) throws IOException {
1878: FeatureWriter writer = getFeatureWriter(typeName,
1879: Filter.EXCLUDE, transaction);
1880:
1881: while (writer.hasNext()) {
1882: writer.next(); // this would be a use for skip then :-)
1883: }
1884:
1885: return writer;
1886: }
1887:
1888: int getSRID(String typeName, String geomColName) throws IOException {
1889: return typeHandler.getFeatureTypeInfo(typeName).getSRID(
1890: geomColName);
1891: }
1892:
1893: /**
1894: * @see org.geotools.data.jdbc.JDBCDataStore#getGeometryAttributeIO(org.geotools.feature.AttributeType)
1895: */
1896: protected AttributeIO getGeometryAttributeIO(AttributeType type,
1897: QueryData queryData) {
1898: // grab the crs if available
1899: GeometryAttributeType geometryType = (GeometryAttributeType) type;
1900: CoordinateReferenceSystem crs = null;
1901: if (geometryType != null)
1902: crs = geometryType.getCoordinateSystem();
1903:
1904: Hints hints = queryData != null ? queryData.getHints()
1905: : GeoTools.getDefaultHints();
1906: int D = (crs == null || Boolean.TRUE.equals(queryData
1907: .getHints().get(Hints.FEATURE_2D))) ? 2 : crs
1908: .getCoordinateSystem().getDimension();
1909: if (WKBEnabled) {
1910: return new PgWKBAttributeIO(isByteaEnabled(), hints);
1911: } else {
1912: if (D == 3) {
1913: return new EWKTAttributeIO();
1914: } else {
1915: return new WKTAttributeIO();
1916: }
1917: }
1918: }
1919:
1920: protected int getResultSetType(boolean forWrite) {
1921: return ResultSet.TYPE_FORWARD_ONLY;
1922: }
1923:
1924: protected int getConcurrency(boolean forWrite) {
1925: return ResultSet.CONCUR_READ_ONLY;
1926: }
1927:
1928: /**
1929: * Returns true if the WKB format is used to transfer geometries, false
1930: * otherwise
1931: *
1932: */
1933: public boolean isWKBEnabled() {
1934: return WKBEnabled;
1935: }
1936:
1937: /**
1938: * If turned on, WKB will be used to transfer geometry data instead of WKT
1939: *
1940: * @param enabled
1941: */
1942: public void setWKBEnabled(boolean enabled) {
1943: WKBEnabled = enabled;
1944: }
1945:
1946: /**
1947: * Sets this postgis instance to use a less strict but faster bounding box
1948: * query. Setting this to <tt>true</tt> will have PostGIS issue bounding
1949: * box queries against the envelope of the geometry, so some may be
1950: * <i>slighty</i> wrong, but will perform much faster. The intersects
1951: * function can still be used to obtain the exact query.
1952: *
1953: * @param isLooseBbox <tt>true</tt> if this should have a loose Bbox.
1954: */
1955: public void setLooseBbox(boolean isLooseBbox) {
1956: this .looseBbox = isLooseBbox;
1957: }
1958:
1959: /**
1960: * Whether the bounding boxes issued against this postgis datastore are on
1961: * the envelope of the geometry or the actual geometry.
1962: *
1963: * @return <tt>true</tt> if the bounding box is 'loose', against the
1964: * envelope instead of the actual geometry.
1965: */
1966: public boolean isLooseBbox() {
1967: return looseBbox;
1968: }
1969:
1970: /**
1971: * Returns true if the data store is using the bytea function to fasten WKB
1972: * data transfer, false otherwise
1973: *
1974: */
1975: public boolean isByteaEnabled() {
1976: return byteaEnabled;
1977: }
1978:
1979: public void setByteaWKB(boolean byteaWKB) {
1980: this .byteaWKB = byteaWKB;
1981: }
1982:
1983: public boolean isByteaWKB() {
1984: return byteaWKB;
1985: }
1986:
1987: /**
1988: * Enables the use of bytea function for WKB data transfer (will improve
1989: * performance). Note this function need not be set by the programmer, as
1990: * the datastore will use it to optimize performance whenever it can (when
1991: * postGIS is 0.7.2 or later)
1992: *
1993: * @param byteaEnabled
1994: */
1995: public void setByteaEnabled(boolean byteaEnabled) {
1996: this .byteaEnabled = byteaEnabled;
1997: }
1998:
1999: /**
2000: * Enables the use of the 'estimated_extent' function for bounds computation.
2001: * <p>
2002: * Beware that this function is an approximation and is dependent on the
2003: * degree to with the data in the actual bounds follows a uniform distribution.
2004: * </p>
2005: */
2006: public void setEstimatedExtent(boolean estimatedExtent) {
2007: this .estimatedExtent = estimatedExtent;
2008:
2009: //also make sure optimize mode is set properly
2010: if (estimatedExtent) {
2011: LOGGER.info("Setting OPTIMIZE_MODE to 'SQL'");
2012: setOptimizeMode(OPTIMIZE_SQL);
2013: }
2014: }
2015:
2016: /**
2017: * @see {@link #setEstimatedExtent(boolean)}.
2018: */
2019: public boolean isEstimatedExtent() {
2020: return estimatedExtent;
2021: }
2022:
2023: /**
2024: * Sets the optimization mode for the datastore.
2025: *
2026: * @param mode One of {@link #OPTIMIZE_SAFE},{@link #OPTIMIZE_SQL}.
2027: */
2028: public void setOptimizeMode(int mode) {
2029: OPTIMIZE_MODE = mode;
2030: }
2031:
2032: public int getOptimizeMode() {
2033: return OPTIMIZE_MODE;
2034: }
2035:
2036: public FeatureType getSchema(String arg0) throws IOException {
2037: return super .getSchema(arg0);
2038: }
2039:
2040: /**
2041: * Obtains the postgis datastore connection pool.
2042: *
2043: * @return ConnectionPool
2044: */
2045: public DataSource getDataSource() {
2046: return dataSource;
2047: }
2048:
2049: /**
2050: * Obtains database specific information, such as version, supported
2051: * functions, etc.
2052: */
2053: public PostgisDBInfo getDBInfo() {
2054: if (dbInfo == null) {
2055: Connection conn;
2056: try {
2057: conn = getConnection(Transaction.AUTO_COMMIT);
2058: dbInfo = new PostgisDBInfo(conn);
2059: } catch (IOException e1) {
2060: LOGGER.log(Level.SEVERE,
2061: "Could not obtain DBInfo object", e1);
2062: }
2063: }
2064: return dbInfo;
2065: }
2066:
2067: /**
2068: * The hints supported by this datastore depending on the configuration
2069: */
2070: private static final Set BASE_HINTS = Collections
2071: .unmodifiableSet(new HashSet(Arrays
2072: .asList(new Object[] { Hints.FEATURE_DETACHED })));
2073: private static final Set WKB_HINTS = Collections
2074: .unmodifiableSet(new HashSet(Arrays.asList(new Object[] {
2075: Hints.FEATURE_DETACHED,
2076: Hints.JTS_COORDINATE_SEQUENCE_FACTORY,
2077: Hints.JTS_GEOMETRY_FACTORY })));
2078:
2079: public Set getSupportedHints() {
2080: if (isWKBEnabled()) {
2081: return WKB_HINTS;
2082: } else {
2083: return BASE_HINTS;
2084: }
2085: }
2086: }
|