/*
* 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.coverage.postgresql;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.storage.coverage.AbstractCoverageStore;
import org.geotoolkit.storage.coverage.CoverageReference;
import org.geotoolkit.storage.coverage.CoverageStoreFactory;
import org.geotoolkit.storage.coverage.CoverageType;
import org.geotoolkit.util.NamesExt;
import org.geotoolkit.jdbc.ManageableDataSource;
import org.apache.sis.util.ArgumentChecks;
import org.geotoolkit.storage.DataNode;
import org.geotoolkit.storage.DataStores;
import org.geotoolkit.storage.DefaultDataNode;
import org.geotoolkit.version.Version;
import org.geotoolkit.version.VersionControl;
import org.geotoolkit.version.VersioningException;
import org.opengis.util.GenericName;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.FactoryException;
import org.apache.sis.referencing.factory.sql.EPSGFactory;
/**
* GeotoolKit Coverage Store using PostgreSQL Raster model.
*
* @author Johann Sorel (Geomatys)
*/
public class PGCoverageStore extends AbstractCoverageStore{
private EPSGFactory epsgfactory;
private DataSource source;
private int fetchSize;
private String schema;
public PGCoverageStore(final ParameterValueGroup params, final DataSource source){
super(params);
ArgumentChecks.ensureNonNull("source", source);
this.source = source;
}
public int getFetchSize() {
return fetchSize;
}
public void setFetchSize(int fetchSize) {
this.fetchSize = fetchSize;
}
public void setDatabaseSchema(String schema) {
this.schema = schema;
}
public String getDatabaseSchema() {
return schema;
}
public DataSource getDataSource() {
return source;
}
public synchronized EPSGFactory getEPSGFactory() throws FactoryException {
if (epsgfactory == null) {
epsgfactory = new EPSGFactory(Collections.singletonMap("dataSource", source));
}
return epsgfactory;
}
/**
* {@inheritDoc}
*/
@Override
public CoverageStoreFactory getFactory() {
return (CoverageStoreFactory) DataStores.getFactoryById(PGCoverageStoreFactory.NAME);
}
@Override
public DataNode getRootNode() throws DataStoreException {
final DataNode root = new DefaultDataNode();
final String ns = getDefaultNamespace();
final StringBuilder query = new StringBuilder();
query.append("SELECT name FROM ");
query.append(encodeTableName("Layer"));
Connection cnx = null;
Statement stmt = null;
ResultSet rs = null;
try {
cnx = source.getConnection();
stmt = cnx.createStatement();
rs = stmt.executeQuery(query.toString());
while (rs.next()){
final GenericName n = NamesExt.create(ns, rs.getString(1));
final CoverageReference ref = createCoverageReference(n, null);
root.getChildren().add(ref);
}
} catch (SQLException ex) {
throw new DataStoreException(ex);
} finally {
closeSafe(cnx,stmt,rs);
}
return root;
}
@Override
public CoverageReference create(GenericName name) throws DataStoreException {
final StringBuilder query = new StringBuilder();
query.append("INSERT INTO ");
query.append(encodeTableName("Layer"));
query.append("(name) VALUES ('");
query.append(name.tip().toString());
query.append("')");
Connection cnx = null;
Statement stmt = null;
ResultSet rs = null;
try {
cnx = source.getConnection();
cnx.setReadOnly(false);
stmt = cnx.createStatement();
stmt.executeUpdate(query.toString());
} catch (SQLException ex) {
throw new DataStoreException(ex);
} finally {
closeSafe(cnx,stmt,rs);
}
fireCoverageAdded(name);
return getCoverageReference(NamesExt.create(getDefaultNamespace(), name.tip().toString()));
}
@Override
public void delete(GenericName name) throws DataStoreException {
final StringBuilder query = new StringBuilder();
query.append("DELETE FROM ");
query.append(encodeTableName("Layer"));
query.append(" WHERE name='");
query.append(name.tip().toString());
query.append("'");
Connection cnx = null;
Statement stmt = null;
ResultSet rs = null;
try {
cnx = source.getConnection();
cnx.setReadOnly(false);
stmt = cnx.createStatement();
stmt.execute(query.toString());
} catch (SQLException ex) {
throw new DataStoreException(ex);
} finally {
closeSafe(cnx,stmt,rs);
}
fireCoverageDeleted(name);
}
public void dropPostgresSchema(final String name) throws DataStoreException {
Statement stmt = null;
Connection cnx = null;
String sql = null;
try {
cnx = getDataSource().getConnection();
sql = "DROP SCHEMA \""+ name +"\" CASCADE;";
stmt = cnx.createStatement();
stmt.execute(sql);
} catch (SQLException ex) {
throw new DataStoreException("Failed to delete features : " + ex.getMessage() + "\nSQL Query :" + sql, ex);
} finally {
closeSafe(cnx, stmt, null);
}
}
@Override
public Logger getLogger() {
return super.getLogger();
}
int getLayerId(Connection cnx, String name) throws SQLException {
final StringBuilder query = new StringBuilder();
query.append("SELECT id FROM ");
query.append(encodeTableName("Layer"));
query.append(" WHERE name='");
query.append(name);
query.append("'");
Statement stmt = null;
ResultSet rs = null;
try {
stmt = cnx.createStatement();
rs = stmt.executeQuery(query.toString());
if(rs.next()){
return rs.getInt(1);
}else{
throw new SQLException("No layer for name : "+name);
}
} finally {
closeSafe(null,stmt,rs);
}
}
////////////////////////////////////////////////////////////////////////////
// Versioning support //////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public boolean handleVersioning() {
return true;
}
@Override
public VersionControl getVersioning(GenericName typeName) throws VersioningException {
try {
typeCheck(typeName);
return new PGVersionControl(this, typeName);
} catch (DataStoreException ex) {
throw new VersioningException(ex.getMessage(), ex);
}
}
@Override
public CoverageReference getCoverageReference(GenericName name, Version version) throws DataStoreException {
typeCheck(name);
return createCoverageReference(name, version);
}
private CoverageReference createCoverageReference(final GenericName name, Version version) throws DataStoreException {
if(version == null){
try {
//grab the latest
VersionControl vc = new PGVersionControl(this, name);
final List<Version> versions = vc.list();
if(versions.isEmpty()){
final Calendar cal = Calendar.getInstance(PGVersionControl.GMT0);
cal.setTimeInMillis(0);
version = vc.createVersion(cal.getTime());
}else{
version = versions.get(versions.size()-1);
}
} catch (VersioningException ex) {
throw new DataStoreException(ex.getMessage(), ex);
}
}
return new PGCoverageReference(this, name, version);
}
////////////////////////////////////////////////////////////////////////////
// Connection utils ////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
String encodeTableName(String name){
final String schema = getDatabaseSchema();
if(schema == null){
return "\""+name+"\"";
}else{
return "\""+schema+"\".\""+name+"\"";
}
}
public void closeSafe(final Connection cx, final Statement st, final ResultSet rs){
closeSafe(cx);
closeSafe(st);
closeSafe(rs);
}
public void closeSafe(final ResultSet rs) {
if (rs == null) {
return;
}
try {
rs.close();
} catch (SQLException e) {
final String msg = "Error occurred closing result set";
getLogger().warning(msg);
if (getLogger().isLoggable(Level.FINER)) {
getLogger().log(Level.FINER, msg, e);
}
}
}
public void closeSafe(final Statement st) {
if (st == null) {
return;
}
try {
st.close();
} catch (SQLException e) {
final String msg = "Error occurred closing statement";
getLogger().warning(msg);
if (getLogger().isLoggable(Level.FINER)) {
getLogger().log(Level.FINER, msg, e);
}
}
}
public void closeSafe(final Connection cx) {
if (cx == null) {
return;
}
try {
cx.close();
getLogger().fine("CLOSE CONNECTION");
} catch (SQLException e) {
final String msg = "Error occurred closing connection";
getLogger().warning(msg);
if (getLogger().isLoggable(Level.FINER)) {
getLogger().log(Level.FINER, msg, e);
}
}
}
@Override
public void close() {
if (source instanceof ManageableDataSource) {
try {
final ManageableDataSource mds = (ManageableDataSource) source;
source = null;
mds.close();
} catch (SQLException e) {
// it's ok, we did our best..
getLogger().log(Level.FINE, "Could not close dataSource", e);
}
}
}
@Override
protected void finalize() throws Throwable {
if (source != null) {
getLogger().severe("There's code using JDBC based coverage store and " +
"not disposing them. This may lead to temporary loss of database connections. " +
"Please make sure all data access code calls CoverageStore.dispose() " +
"before freeing all references to it");
close();
}
super.finalize();
}
@Override
public CoverageType getType() {
return CoverageType.PYRAMID;
}
}