org.geotoolkit.db.AbstractJDBCFeatureStoreFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.geotoolkit.db.AbstractJDBCFeatureStoreFactory.java

Source

/*
 *    Geotoolkit - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2012-2013, Geomatys
 *
 *    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.geotoolkit.db;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.geotoolkit.data.AbstractFeatureStoreFactory;
import org.geotoolkit.db.dialect.SQLDialect;
import org.geotoolkit.jdbc.DBCPDataSource;
import org.apache.sis.metadata.iso.quality.DefaultConformanceResult;
import org.apache.sis.parameter.ParameterBuilder;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.storage.DataStore;
import org.opengis.metadata.quality.ConformanceResult;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.ParameterValueGroup;

/**
 * Abstract FeatureStoreFactory for databases.
 * 
 * @author Johann Sorel (Geomatys)
 * @module
 */
public abstract class AbstractJDBCFeatureStoreFactory extends AbstractFeatureStoreFactory {

    /** parameter for database host */
    public static final ParameterDescriptor<String> HOST = new ParameterBuilder().addName("host")
            .addName(Bundle.formatInternational(Bundle.Keys.host))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.host_remarks)).setRequired(true)
            .create(String.class, "localhost");

    /** parameter for database port */
    public static final ParameterDescriptor<Integer> PORT = new ParameterBuilder().addName("port")
            .addName(Bundle.formatInternational(Bundle.Keys.port))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.port_remarks)).setRequired(true)
            .create(Integer.class, null);

    /** parameter for database instance */
    public static final ParameterDescriptor<String> DATABASE = new ParameterBuilder().addName("database")
            .addName(Bundle.formatInternational(Bundle.Keys.database))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.database_remarks)).setRequired(false)
            .create(String.class, null);

    /** parameter for database schema */
    public static final ParameterDescriptor<String> SCHEMA = new ParameterBuilder().addName("schema")
            .addName(Bundle.formatInternational(Bundle.Keys.schema))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.schema_remarks)).setRequired(false)
            .create(String.class, null);

    /** parameter for database user */
    public static final ParameterDescriptor<String> USER = new ParameterBuilder().addName("user")
            .addName(Bundle.formatInternational(Bundle.Keys.user))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.user_remarks)).setRequired(true)
            .create(String.class, null);

    /** parameter for database password */
    public static final ParameterDescriptor<String> PASSWORD = new ParameterBuilder().addName("password")
            .addName(Bundle.formatInternational(Bundle.Keys.password))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.password_remarks)).setRequired(true)
            .create(String.class, null);

    /** parameter for data source */
    public static final ParameterDescriptor<DataSource> DATASOURCE = new ParameterBuilder().addName("Data Source")
            .addName(Bundle.formatInternational(Bundle.Keys.datasource))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.datasource_remarks)).setRequired(false)
            .create(DataSource.class, null);

    /** Set to true to have only simple feature types. 
     * relations won't be rebuilded as complexe features.
     * Default is true.
     */
    public static final ParameterDescriptor<Boolean> SIMPLETYPE = new ParameterBuilder().addName("simple types")
            .addName(Bundle.formatInternational(Bundle.Keys.simpletype))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.simpletype_remarks)).setRequired(true)
            .create(Boolean.class, Boolean.TRUE);

    /** Maximum number of connections in the connection pool */
    public static final ParameterDescriptor<Integer> MAXCONN = new ParameterBuilder().addName("max connections")
            .addName(Bundle.formatInternational(Bundle.Keys.max_connections))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.max_connections_remarks)).setRequired(false)
            .create(Integer.class, 10);

    /** Minimum number of connections in the connection pool */
    public static final ParameterDescriptor<Integer> MINCONN = new ParameterBuilder().addName("min connections")
            .addName(Bundle.formatInternational(Bundle.Keys.min_connections))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.min_connections_remarks)).setRequired(false)
            .create(Integer.class, 1);

    /** If connections should be validated before using them */
    public static final ParameterDescriptor<Boolean> VALIDATECONN = new ParameterBuilder()
            .addName("validate connections").addName(Bundle.formatInternational(Bundle.Keys.validate_connections))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.validate_connections_remarks)).setRequired(false)
            .create(Boolean.class, Boolean.FALSE);

    /** If connections should be validated before using them */
    public static final ParameterDescriptor<Integer> FETCHSIZE = new ParameterBuilder().addName("fetch size")
            .addName(Bundle.formatInternational(Bundle.Keys.fetch_size))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.fetch_size_remarks)).setRequired(false)
            .create(Integer.class, 1000);

    /** Maximum amount of time the pool will wait when trying to grab a new connection **/
    public static final ParameterDescriptor<Integer> MAXWAIT = new ParameterBuilder().addName("Connection timeout")
            .addName(Bundle.formatInternational(Bundle.Keys.timeout))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.timeout_remarks)).setRequired(false)
            .create(Integer.class, 20);

    /** parameter for table to load **/
    public static final ParameterDescriptor<String> TABLE = new ParameterBuilder().addName("Table Name")
            .addName(Bundle.formatInternational(Bundle.Keys.table))
            .setRemarks(Bundle.formatInternational(Bundle.Keys.table_remarks)).setRequired(false)
            .create(String.class, null);

    /**
     * Create the database port descriptor, and set default parameter value.
     * @return a databse port descriptor.
     */
    public static ParameterDescriptor<Integer> createFixedPort(Integer value) {
        return new ParameterBuilder().addName(PORT.getName().getCode()).addName(PORT.getAlias().iterator().next())
                .setRemarks(PORT.getRemarks()).setRequired(true).create(Integer.class, value);
    }

    @Override
    public boolean canProcess(final ParameterValueGroup params) {
        final boolean valid = super.canProcess(params);

        if (!valid) {
            //check if the datasource is set
            try {
                final DataSource ds = (DataSource) params.parameter(DATASOURCE.getName().toString()).getValue();
                if (ds == null) {
                    return false;
                }
            } catch (ParameterNotFoundException ex) {
                //parameter does not exist
                return false;
            }
        }

        return valid;
    }

    @Override
    public JDBCFeatureStore open(final ParameterValueGroup params) throws DataStoreException {
        ensureCanProcess(params);

        final DefaultJDBCFeatureStore featureStore = toFeatureStore(params,
                getIdentification().getCitation().getIdentifiers().iterator().next().getCode());
        prepareStore(featureStore, params);
        return featureStore;
    }

    /**
     * Configure feature store datasource and dialect.
     * 
     * @param featureStore
     * @param params
     * @throws DataStoreException 
     */
    public void prepareStore(DefaultJDBCFeatureStore featureStore, final ParameterValueGroup params)
            throws DataStoreException {

        // datasource
        final DataSource ds = (DataSource) params.parameter(DATASOURCE.getName().getCode()).getValue();
        try {
            featureStore.setDataSource((ds != null) ? ds : createDataSource(params));
        } catch (IOException ex) {
            throw new DataStoreException(ex);
        }

        // dialect
        final SQLDialect dialect = createSQLDialect(featureStore);
        featureStore.setDialect(dialect);
    }

    protected DefaultJDBCFeatureStore toFeatureStore(final ParameterValueGroup params, String factoryId) {
        return new DefaultJDBCFeatureStore(params, factoryId);
    }

    /**
     * Create a JDBC database : this will not create a database but just
     * create the schema if possible.
     * 
     * @param params
     * @return
     * @throws DataStoreException 
     */
    @Override
    public DataStore create(final ParameterValueGroup params) throws DataStoreException {

        JDBCFeatureStore store = null;
        Connection cnx = null;
        Statement stmt = null;
        ResultSet rs = null;

        try {
            store = open(params);
            cnx = store.getDataSource().getConnection();
            rs = cnx.getMetaData().getSchemas();
            final String schema = store.getDatabaseSchema();
            while (rs.next()) {
                final String currentSchema = rs.getString(1);
                if (currentSchema.equals(schema)) {
                    //shema already exist, might be normal, like 'public' on postgres and H2.
                    return store;
                    //throw new DataStoreException("Schema " + schema+ " already exist. Unable to create DataStore.");
                }
            }

            stmt = cnx.createStatement();

            if (schema != null && !schema.isEmpty()) {
                //create schema
                stmt.executeUpdate("CREATE SCHEMA \"" + schema + "\";");
            }

            return store;
        } catch (SQLException ex) {
            if (store != null) {
                store.close();
            }
            throw new DataStoreException(ex);
        } finally {
            if (store != null) {
                JDBCFeatureStoreUtilities.closeSafe(store.getLogger(), cnx, stmt, rs);
            }
        }
    }

    /**
     * {@inheritDoc }
     */
    @Override
    public ConformanceResult availability() {
        final DefaultConformanceResult result = (DefaultConformanceResult) super.availability();
        if (Boolean.FALSE.equals(result.pass()))
            return result;

        try {
            //check jdbc driver
            Class.forName(getDriverClassName());
        } catch (ClassNotFoundException e) {
            result.setPass(false);
        }
        return result;
    }

    /**
     * @return String , name used in the construction of the JDBC url.
     */
    protected abstract String getJDBCURLDatabaseName();

    /**
     * @return String , complete JDBC driver class name.
     */
    protected abstract String getDriverClassName();

    /**
     * Creates the dialect that the featurestore uses for communication with the
     * underlying database.
     *
     * @param featureStore The featurestore.
     */
    protected abstract SQLDialect createSQLDialect(final JDBCFeatureStore featureStore);

    /**
     * Create a datasource using given parameters.
     */
    protected DataSource createDataSource(final ParameterValueGroup params) throws IOException {
        //create a datasource
        final BasicDataSource dataSource = new BasicDataSource();

        // some default data source behaviour
        dataSource.setPoolPreparedStatements(false);

        // driver
        dataSource.setDriverClassName(getDriverClassName());

        // url
        dataSource.setUrl(getJDBCUrl(params));

        // username
        final String user = (String) params.parameter(USER.getName().toString()).getValue();
        dataSource.setUsername(user);

        // password
        final String passwd = (String) params.parameter(PASSWORD.getName().toString()).getValue();
        if (passwd != null) {
            dataSource.setPassword(passwd);
        }

        // max wait
        final Integer maxWait = (Integer) params.parameter(MAXWAIT.getName().toString()).getValue();
        if (maxWait != null && maxWait != -1) {
            dataSource.setMaxWait(maxWait * 1000);
        }

        // connection pooling options
        final Integer minConn = (Integer) params.parameter(MINCONN.getName().toString()).getValue();
        if (minConn != null) {
            dataSource.setMinIdle(minConn);
        }

        final Integer maxConn = (Integer) params.parameter(MAXCONN.getName().toString()).getValue();
        if (maxConn != null) {
            dataSource.setMaxActive(maxConn);
        }

        final Boolean validate = (Boolean) params.parameter(VALIDATECONN.getName().toString()).getValue();
        if (validate != null && validate && getValidationQuery() != null) {
            dataSource.setTestOnBorrow(true);
            dataSource.setValidationQuery(getValidationQuery());
        }

        // allow manipulating connections for possible tuning.
        dataSource.setAccessToUnderlyingConnectionAllowed(true);

        return new DBCPDataSource(dataSource);
    }

    /**
     * @return String : a fast query which can be send to the server to ensure
     * the connextion is still valid.
     */
    protected abstract String getValidationQuery();

    /**
     * Build JDBC url string = jdbc:<database>://<host>:<port>/<dbname>
     */
    protected String getJDBCUrl(final ParameterValueGroup params) throws IOException {
        final String host = (String) params.parameter(HOST.getName().toString()).getValue();
        final Integer port = (Integer) params.parameter(PORT.getName().toString()).getValue();
        final String db = (String) params.parameter(DATABASE.getName().toString()).getValue();

        final StringBuilder sb = new StringBuilder("jdbc:");
        sb.append(getJDBCURLDatabaseName());
        sb.append("://");
        sb.append(host);
        if (port != null) {
            sb.append(':').append(port);
        }
        if (db != null) {
            sb.append('/').append(db);
        }
        return sb.toString();
    }
}