/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, 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.coverage.postgresql;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.nio.IOUtilities;
import org.geotoolkit.storage.coverage.AbstractCoverageStoreFactory;
import org.geotoolkit.coverage.postgresql.exception.SchemaExistsException;
import org.geotoolkit.jdbc.DBCPDataSource;
import org.apache.sis.metadata.iso.DefaultIdentifier;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.identification.DefaultServiceIdentification;
import org.apache.sis.parameter.ParameterBuilder;
import org.apache.sis.referencing.factory.sql.EPSGFactory;
import org.geotoolkit.storage.DataType;
import org.geotoolkit.storage.DefaultFactoryMetadata;
import org.geotoolkit.storage.FactoryMetadata;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.identification.Identification;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.FactoryException;
import org.geotoolkit.internal.sql.AuthenticatedDataSource;
import org.geotoolkit.internal.sql.DefaultDataSource;
/**
* GeotoolKit Coverage Store using PostgreSQL Raster model factory.
*
* @author Johann Sorel (Geomatys)
*/
public class PGCoverageStoreFactory extends AbstractCoverageStoreFactory{
/** factory identification **/
public static final String NAME = "pgraster";
public static final DefaultServiceIdentification IDENTIFICATION;
static {
IDENTIFICATION = new DefaultServiceIdentification();
final Identifier id = new DefaultIdentifier(NAME);
final DefaultCitation citation = new DefaultCitation(NAME);
citation.setIdentifiers(Collections.singleton(id));
IDENTIFICATION.setCitation(citation);
}
public static final ParameterDescriptor<String> IDENTIFIER = createFixedIdentifier(NAME);
/** 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);
/** 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);
public static final ParameterDescriptorGroup PARAMETERS_DESCRIPTOR =
new ParameterBuilder().addName("PGRasterParameters").createGroup(
IDENTIFIER,HOST,PORT,DATABASE,SCHEMA,USER,PASSWORD,NAMESPACE,
DATASOURCE,MAXCONN,MINCONN,VALIDATECONN,FETCHSIZE,MAXWAIT);
@Override
public Identification getIdentification() {
return IDENTIFICATION;
}
/**
* {@inheritDoc }
*/
@Override
public ParameterDescriptorGroup getParametersDescriptor() {
return PARAMETERS_DESCRIPTOR;
}
@Override
public CharSequence getDescription() {
return Bundle.formatInternational(Bundle.Keys.description);
}
@Override
public CharSequence getDisplayName() {
return Bundle.formatInternational(Bundle.Keys.title);
}
@Override
public PGCoverageStore open(ParameterValueGroup params) throws DataStoreException {
// datasource
// check if the DATASOURCE parameter was supplied, it takes precendence
DataSource ds = (DataSource) params.parameter(DATASOURCE.getName().toString()).getValue();
if(ds == null){
try {
ds = createDataSource(params);
} catch (IOException ex) {
throw new DataStoreException(ex.getMessage(),ex);
}
}
final PGCoverageStore store = new PGCoverageStore(params, ds);
// fetch size
Integer fetchSize = (Integer) params.parameter(FETCHSIZE.getName().toString()).getValue();
if (fetchSize != null && fetchSize > 0) {
store.setFetchSize(fetchSize);
}
//database schema
final String schema = (String) params.parameter(SCHEMA.getName().toString()).getValue();
if (schema != null) {
store.setDatabaseSchema(schema);
}
return store;
}
@Override
public PGCoverageStore create(ParameterValueGroup params) throws DataStoreException {
final String jdbcurl = getJDBCUrl(params);
//create epsg model
final String dbURL = jdbcurl;
final String user = (String) params.parameter(USER.getName().getCode()).getValue();
final String password = (String) params.parameter(PASSWORD.getName().getCode()).getValue();
final DataSource ds = new AuthenticatedDataSource(new DefaultDataSource(dbURL), user, password, null);
final Map<String,Object> properties = new HashMap<>();
properties.put("dataSource", ds);
PGCoverageStore store = null;
Connection cnx = null;
Statement stmt = null;
ResultSet rs = null;
try (final EPSGFactory installer = new EPSGFactory(properties)) {
store = open(params);
cnx = store.getDataSource().getConnection();
rs = cnx.getMetaData().getSchemas();
boolean epsgExists = false;
final String schema = store.getDatabaseSchema();
while (rs.next()) {
final String currentSchema = rs.getString(1);
if (currentSchema.contains("epsg")) {
epsgExists = true;
} else if (currentSchema.contains(schema)) {
throw new SchemaExistsException(schema);
}
}
// Only creates this schema if not present.
if (!epsgExists) {
try (Connection c = ds.getConnection()) {
installer.install(c);
}
}
String sql = IOUtilities.toString(PGCoverageStoreFactory.class
.getResourceAsStream("/org/geotoolkit/coverage/postgresql/pgcoverage.sql"));
stmt = cnx.createStatement();
if(schema != null && !schema.isEmpty()){
sql = sql.replaceAll("CREATE TABLE ", "CREATE TABLE \""+schema+"\".");
sql = sql.replaceAll("REFERENCES ", "REFERENCES \""+schema+"\".");
//create schema
stmt.executeUpdate("CREATE SCHEMA \""+schema+"\";");
}
final String[] parts = sql.split(";");
for(String part : parts){
stmt.executeUpdate(part.trim());
}
return store;
} catch (SQLException ex) {
if(store != null){
store.close();
}
throw new DataStoreException(ex);
} catch (IOException ex) {
if(store != null){
store.close();
}
throw new DataStoreException(ex);
} catch (FactoryException ex) {
if(store != null){
store.close();
}
throw new DataStoreException(ex);
}finally{
if(store != null){
store.closeSafe(cnx, stmt, rs);
}
}
}
private String getDriverClassName() {
return "org.postgresql.Driver";
}
private String getValidationQuery() {
return "select now()";
}
private String getJDBCUrl(final ParameterValueGroup params) {
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();
return "jdbc:postgresql://" + host + ":" + port + "/" + db+"";
}
/**
* Creates the datasource for the coverage store.
*/
private DataSource createDataSource(final ParameterValueGroup params) throws IOException {
//create a datasource
final BasicDataSource dataSource = new BasicDataSource();
// 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());
}
// might need this
dataSource.setAccessToUnderlyingConnectionAllowed(true);
return new DBCPDataSource(dataSource);
}
@Override
public FactoryMetadata getMetadata() {
return new DefaultFactoryMetadata(DataType.PYRAMID, true, false, true);
}
}