/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2015, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.data.postgis; import static org.geotools.data.postgis.PostgisNGDataStoreFactory.SIMPLIFY; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import javax.sql.DataSource; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.JDBCDataStoreFactory; import org.geotools.jdbc.SQLDialect; import org.geotools.util.KVP; /** * * * @source $URL$ */ public class PostgisNGDataStoreFactory extends JDBCDataStoreFactory { /** parameter for database type */ public static final Param DBTYPE = new Param("dbtype", String.class, "Type", true, "postgis"); /** enables using && in bbox queries */ public static final Param LOOSEBBOX = new Param("Loose bbox", Boolean.class, "Perform only primary filter on bbox", false, Boolean.TRUE); /** parameter that enables estimated extends instead of exact ones */ public static final Param ESTIMATED_EXTENTS = new Param("Estimated extends", Boolean.class, "Use the spatial index information to quickly get an estimate of the data bounds", false, Boolean.TRUE); /** parameter for database port */ public static final Param PORT = new Param("port", Integer.class, "Port", true, 5432); /** parameter for database schema */ public static final Param SCHEMA = new Param("schema", String.class, "Schema", false, "public"); /** attempt to create the database if missing */ public static final Param CREATE_DB_IF_MISSING = new Param("create database", Boolean.class, "Creates the database if it does not exist yet", false, false, Param.LEVEL, "advanced"); /** attempt to create the database if missing */ public static final Param CREATE_PARAMS = new Param("create database params", String.class, "Extra specifications appeneded to the CREATE DATABASE command", false, "", Param.LEVEL, "advanced"); /** * Wheter a prepared statements based dialect should be used, or not */ public static final Param PREPARED_STATEMENTS = new Param("preparedStatements", Boolean.class, "Use prepared statements", false, Boolean.FALSE); /** * Enables direct encoding of selected filter functions in sql */ public static final Param ENCODE_FUNCTIONS = new Param( "encode functions", Boolean.class, "set to true to have a set of filter functions be translated directly in SQL. " + "Due to differences in the type systems the result might not be the same as evaluating " + "them in memory, including the SQL failing with errors while the in memory version works fine. " + "However this allows to push more of the filter into the database, increasing performance." + "the postgis table.", false, new Boolean(false), new KVP( Param.LEVEL, "advanced")); /** * Enables usage of ST_Simplify when the queries contain geometry simplification hints */ public static final Param SIMPLIFY = new Param("Support on the fly geometry simplification", Boolean.class, "When enabled, operations such as map rendering will pass a hint that will enable the usage of ST_Simplify", false, Boolean.TRUE); @Override protected SQLDialect createSQLDialect(JDBCDataStore dataStore) { return new PostGISDialect(dataStore); } @Override protected String getDatabaseID() { return (String) DBTYPE.sample; } @Override public String getDisplayName() { return "PostGIS"; } public String getDescription() { return "PostGIS Database"; } @Override protected String getDriverClassName() { return "org.postgresql.Driver"; } @Override protected boolean checkDBType(Map params) { if (super.checkDBType(params)) { //check for old factory try { Class.forName("org.geotools.data.postgis.PostgisDataStoreFactory"); //old factory is around, let it handle the connection return false; } catch(ClassNotFoundException e) { //old factory is not around, handle this connection return true; } } else { //check for postgisng as well return checkDBType(params, "postgisng"); } } protected JDBCDataStore createDataStoreInternal(JDBCDataStore dataStore, Map params) throws IOException { // setup loose bbox PostGISDialect dialect = (PostGISDialect) dataStore.getSQLDialect(); Boolean loose = (Boolean) LOOSEBBOX.lookUp(params); dialect.setLooseBBOXEnabled(loose == null || Boolean.TRUE.equals(loose)); // check the estimated extents Boolean estimated = (Boolean) ESTIMATED_EXTENTS.lookUp(params); dialect.setEstimatedExtentsEnabled(estimated == null || Boolean.TRUE.equals(estimated)); // check if we can encode functions in sql Boolean encodeFunctions = (Boolean) ENCODE_FUNCTIONS.lookUp(params); dialect.setFunctionEncodingEnabled(encodeFunctions != null && encodeFunctions); // setup the ps dialect if need be Boolean usePs = (Boolean) PREPARED_STATEMENTS.lookUp(params); if(Boolean.TRUE.equals(usePs)) { dataStore.setSQLDialect(new PostGISPSDialect(dataStore, dialect)); } // check geometry simplification (on by default) Boolean simplify = (Boolean) SIMPLIFY.lookUp(params); dialect.setSimplifyEnabled(simplify == null || simplify); // encode BBOX filter with wrapping ST_Envelope (GEOT-5167) Boolean encodeBBOXAsEnvelope = false; String largeGeometriesOptimized = System.getProperty("org.geotools.data.postgis.largeGeometriesOptimize"); if(largeGeometriesOptimized != null) { encodeBBOXAsEnvelope = largeGeometriesOptimized.toLowerCase().equals("true"); } dialect.setEncodeBBOXFilterAsEnvelope(encodeBBOXAsEnvelope != null && Boolean.TRUE.equals(encodeBBOXAsEnvelope)); return dataStore; } @Override protected void setupParameters(Map parameters) { // NOTE: when adding parameters here remember to add them to PostgisNGJNDIDataStoreFactory super.setupParameters(parameters); parameters.put(DBTYPE.key, DBTYPE); parameters.put(SCHEMA.key, SCHEMA); parameters.put(LOOSEBBOX.key, LOOSEBBOX); parameters.put(ESTIMATED_EXTENTS.key, ESTIMATED_EXTENTS); parameters.put(PORT.key, PORT); parameters.put(PREPARED_STATEMENTS.key, PREPARED_STATEMENTS); parameters.put(MAX_OPEN_PREPARED_STATEMENTS.key, MAX_OPEN_PREPARED_STATEMENTS); parameters.put(ENCODE_FUNCTIONS.key, ENCODE_FUNCTIONS); parameters.put(SIMPLIFY.key, SIMPLIFY); parameters.put(CREATE_DB_IF_MISSING.key, CREATE_DB_IF_MISSING); parameters.put(CREATE_PARAMS.key, CREATE_PARAMS); } @Override protected String getValidationQuery() { return "select now()"; } @Override protected String getJDBCUrl(Map params) throws IOException { String host = (String) HOST.lookUp(params); String db = (String) DATABASE.lookUp(params); int port = (Integer) PORT.lookUp(params); return "jdbc:postgresql" + "://" + host + ":" + port + "/" + db; } protected DataSource createDataSource(Map params, SQLDialect dialect) throws IOException { DataSource ds = super.createDataSource(params, dialect); JDBCDataStore closer = new JDBCDataStore(); if (Boolean.TRUE.equals(CREATE_DB_IF_MISSING.lookUp(params))) { // verify we can connect Connection cx = null; boolean canConnect = true; try { cx = ds.getConnection(); } catch (SQLException e) { canConnect = false; } finally { closer.closeSafe(cx); } if (!canConnect) { // get the connection params String host = (String) HOST.lookUp(params); int port = (Integer) PORT.lookUp(params); String db = (String) DATABASE.lookUp(params); String user = (String) USER.lookUp(params); String password = (String) PASSWD.lookUp(params); Statement st = null; try { // connect to template1 instead String url = "jdbc:postgresql" + "://" + host + ":" + port + "/template1"; cx = getConnection(user, password, url); // create the database String createParams = (String) CREATE_PARAMS.lookUp(params); String sql = "CREATE DATABASE \"" + db + "\" " + (createParams == null ? "" : createParams); st = cx.createStatement(); st.execute(sql); } catch (SQLException e) { throw new IOException("Failed to create the target database", e); } finally { closer.closeSafe(st); closer.closeSafe(cx); } // if we got here the database has been created, now verify it has the postgis // extensions // and eventually try to create them ResultSet rs = null; try { String url = "jdbc:postgresql" + "://" + host + ":" + port + "/" + db; cx = DriverManager.getConnection(url, user, password); // check we have postgis st = cx.createStatement(); try { rs = st.executeQuery("select PostGIS_version()"); rs.close(); } catch (SQLException e) { // not available eh? create it st.execute("create extension postgis"); } } catch (SQLException e) { throw new IOException("Failed to create the target database", e); } finally { closer.closeSafe(st); closer.closeSafe(cx); } // and finally re-create the connection pool ds = super.createDataSource(params, dialect); } } return ds; } private Connection getConnection(String user, String password, String url) throws SQLException { Connection cx; if(user != null) { cx = DriverManager.getConnection(url, user, password); } else { cx = DriverManager.getConnection(url); } return cx; } /** * Drops the database specified in the connection params. The database must not be in use, and * the user must have the necessary privileges * @param params * @throws IOException */ public void dropDatabase(Map<String, Object> params) throws IOException { JDBCDataStore closer = new JDBCDataStore(); // get the connection params String host = (String) HOST.lookUp(params); int port = (Integer) PORT.lookUp(params); String db = (String) DATABASE.lookUp(params); String user = (String) USER.lookUp(params); String password = (String) PASSWD.lookUp(params); Connection cx = null; Statement st = null; try { // connect to template1 instead String url = "jdbc:postgresql" + "://" + host + ":" + port + "/template1"; cx = getConnection(user, password, url); // drop the database String sql = "DROP DATABASE \"" + db + "\""; st = cx.createStatement(); st.execute(sql); } catch (SQLException e) { throw new IOException("Failed to drop the target database", e); } finally { closer.closeSafe(st); closer.closeSafe(cx); } } }