/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011-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.postgres;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import net.iharder.Base64;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.feature.SingleAttributeTypeBuilder;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.Version;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.wkb.WKBRasterReader;
import org.geotoolkit.coverage.wkb.WKBRasterWriter;
import org.geotoolkit.db.DefaultJDBCFeatureStore;
import org.geotoolkit.db.FilterToSQL;
import org.geotoolkit.db.JDBCFeatureStore;
import org.geotoolkit.db.JDBCFeatureStoreUtilities;
import static org.geotoolkit.db.JDBCFeatureStoreUtilities.*;
import org.geotoolkit.db.dialect.AbstractSQLDialect;
import org.geotoolkit.db.reverse.ColumnMetaModel;
import org.geotoolkit.db.reverse.MetaDataConstants;
import org.geotoolkit.db.reverse.PrimaryKey;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.filter.capability.DefaultArithmeticOperators;
import org.geotoolkit.filter.capability.DefaultComparisonOperators;
import org.geotoolkit.filter.capability.DefaultFilterCapabilities;
import org.geotoolkit.filter.capability.DefaultFunctions;
import org.geotoolkit.filter.capability.DefaultIdCapabilities;
import org.geotoolkit.filter.capability.DefaultOperator;
import org.geotoolkit.filter.capability.DefaultScalarCapabilities;
import org.geotoolkit.filter.capability.DefaultSpatialCapabilities;
import org.geotoolkit.filter.capability.DefaultSpatialOperator;
import org.geotoolkit.filter.capability.DefaultSpatialOperators;
import org.geotoolkit.filter.capability.DefaultTemporalCapabilities;
import org.geotoolkit.filter.capability.DefaultTemporalOperators;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.util.ObjectConverters;
import org.opengis.coverage.Coverage;
import org.opengis.filter.Filter;
import org.opengis.filter.PropertyIsBetween;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.PropertyIsGreaterThan;
import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
import org.opengis.filter.PropertyIsLessThan;
import org.opengis.filter.PropertyIsLessThanOrEqualTo;
import org.opengis.filter.PropertyIsLike;
import org.opengis.filter.PropertyIsNotEqualTo;
import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.capability.ArithmeticOperators;
import org.opengis.filter.capability.ComparisonOperators;
import org.opengis.filter.capability.FilterCapabilities;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.capability.Functions;
import org.opengis.filter.capability.GeometryOperand;
import org.opengis.filter.capability.Operator;
import org.opengis.filter.capability.ScalarCapabilities;
import org.opengis.filter.capability.SpatialCapabilities;
import org.opengis.filter.capability.SpatialOperator;
import org.opengis.filter.capability.SpatialOperators;
import org.opengis.filter.capability.TemporalCapabilities;
import org.opengis.filter.capability.TemporalOperand;
import org.opengis.filter.capability.TemporalOperator;
import org.opengis.filter.capability.TemporalOperators;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.Beyond;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Crosses;
import org.opengis.filter.spatial.DWithin;
import org.opengis.filter.spatial.Disjoint;
import org.opengis.filter.spatial.Equals;
import org.opengis.filter.spatial.Intersects;
import org.opengis.filter.spatial.Overlaps;
import org.opengis.filter.spatial.Touches;
import org.opengis.filter.spatial.Within;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.FactoryException;
import org.apache.sis.referencing.crs.AbstractCRS;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
import org.apache.sis.util.Classes;
import org.opengis.feature.AttributeType;
import org.opengis.feature.FeatureType;
import org.opengis.feature.Operation;
import org.opengis.feature.PropertyType;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.IdentifiedObject;
import org.postgresql.jdbc4.Jdbc4ResultSetMetaData;
/**
* Postgres/Postgis dialect.
*
* @author Johann Sorel (Geomatys)
*/
final class PostgresDialect extends AbstractSQLDialect{
private static final String GEOM_ENCODING = "Encoding";
private static enum GeometryEncoding{
HEXEWKB,
WKB,
WKT,
UNKNOWNED
}
protected final Map<Integer, CoordinateReferenceSystem> CRS_CACHE = new HashMap<>();
private static final Map<Integer,Class> TYPE_TO_CLASS = new HashMap<>();
private static final Map<String,Class> TYPENAME_TO_CLASS = new HashMap<>();
private static final Map<Class,String> CLASS_TO_TYPENAME = new HashMap<>();
private static final Map<String, String> TYPE_TO_ST_TYPE_MAP = new HashMap<>();
private static final Set<String> IGNORE_TABLES = new HashSet<>();
private static final FilterCapabilities FILTER_CAPABILITIES;
static {
//fill base types
TYPE_TO_CLASS.put(Types.VARCHAR, String.class);
TYPE_TO_CLASS.put(Types.CHAR, String.class);
TYPE_TO_CLASS.put(Types.LONGVARCHAR, String.class);
TYPE_TO_CLASS.put(Types.NVARCHAR, String.class);
TYPE_TO_CLASS.put(Types.NCHAR, String.class);
TYPE_TO_CLASS.put(Types.BIT, Boolean.class);
TYPE_TO_CLASS.put(Types.BOOLEAN, Boolean.class);
TYPE_TO_CLASS.put(Types.TINYINT, Short.class);
TYPE_TO_CLASS.put(Types.SMALLINT, Short.class);
TYPE_TO_CLASS.put(Types.INTEGER, Integer.class);
TYPE_TO_CLASS.put(Types.BIGINT, Long.class);
TYPE_TO_CLASS.put(Types.REAL, Float.class);
TYPE_TO_CLASS.put(Types.FLOAT, Double.class);
TYPE_TO_CLASS.put(Types.DOUBLE, Double.class);
TYPE_TO_CLASS.put(Types.DECIMAL, BigDecimal.class);
TYPE_TO_CLASS.put(Types.NUMERIC, BigDecimal.class);
TYPE_TO_CLASS.put(Types.DATE, Date.class);
TYPE_TO_CLASS.put(Types.TIME, Time.class);
TYPE_TO_CLASS.put(Types.TIMESTAMP, Timestamp.class);
TYPE_TO_CLASS.put(Types.BLOB, byte[].class);
TYPE_TO_CLASS.put(Types.BINARY, byte[].class);
TYPE_TO_CLASS.put(Types.CLOB, String.class);
TYPE_TO_CLASS.put(Types.VARBINARY, byte[].class);
TYPE_TO_CLASS.put(Types.ARRAY, Array.class);
//NAME IN CREATE QUERY SQL TYPE SQL TPE NAME
/*serial 4 serial */ TYPENAME_TO_CLASS.put("serial", Integer.class);
/*bigserial -5 bigserial */ TYPENAME_TO_CLASS.put("bigserial", Long.class);
/*abstime 1111 abstime */ TYPENAME_TO_CLASS.put("abstime", Object.class);
/*aclitem 1111 aclitem */ TYPENAME_TO_CLASS.put("aclitem", Object.class);
/*bigint -5 int8 */ TYPENAME_TO_CLASS.put("int8", Long.class);
/*bit(1) -7 bit */ TYPENAME_TO_CLASS.put("bit", Boolean.class);
/*bit varying 1111 varbit */ TYPENAME_TO_CLASS.put("varbit", Object.class);
/*boolean -7 bool */ TYPENAME_TO_CLASS.put("bool", Boolean.class);
/*box 1111 box */ TYPENAME_TO_CLASS.put("box", Object.class);
/*byte -2 bytea */ TYPENAME_TO_CLASS.put("bytea", Object.class);
/*char 1 char */ TYPENAME_TO_CLASS.put("char", String.class);
/*character(1) 1 bpchar */ TYPENAME_TO_CLASS.put("bpchar", String.class);
/*character varying 12 varchar */ TYPENAME_TO_CLASS.put("varchar", String.class);
/*cid 1111 cid */ TYPENAME_TO_CLASS.put("cid", Object.class);
/*cidr 1111 cidr */ TYPENAME_TO_CLASS.put("cidr", Object.class);
/*circle 1111 circle */ TYPENAME_TO_CLASS.put("circle", Object.class);
/*date 91 date */ TYPENAME_TO_CLASS.put("date", Date.class);
/*double precision 8 float8 */ TYPENAME_TO_CLASS.put("float8", Double.class);
/*gtsvector 1111 gtsvector */ TYPENAME_TO_CLASS.put("gtsvector", Object.class);
/*inet 1111 inet */ TYPENAME_TO_CLASS.put("inet", Object.class);
/*int2vector 1111 int2vector */ TYPENAME_TO_CLASS.put("int2vector", Object.class);
/*integer 4 int4 */ TYPENAME_TO_CLASS.put("int4", Integer.class);
/*interval 1111 interval */ TYPENAME_TO_CLASS.put("interval", Object.class);
/*line 1111 line */ TYPENAME_TO_CLASS.put("line", Object.class);
/*lseg 1111 lseg */ TYPENAME_TO_CLASS.put("lseg", Object.class);
/*macaddr 1111 macaddr */ TYPENAME_TO_CLASS.put("macaddr", Object.class);
/*money 8 money */ TYPENAME_TO_CLASS.put("money", Double.class);
/*name 12 name */ TYPENAME_TO_CLASS.put("name", String.class);
/*numeric 2 numeric */ TYPENAME_TO_CLASS.put("numeric", BigDecimal.class);
/*oid -5 oid */ TYPENAME_TO_CLASS.put("oid", Long.class);
/*oidvector 1111 oidvector */ TYPENAME_TO_CLASS.put("oidvector", Object.class);
/*path 1111 path */ TYPENAME_TO_CLASS.put("path", Object.class);
/*pg_node_tree 1111 pg_node_tree*/ TYPENAME_TO_CLASS.put("pg_node_tree", Object.class);
/*point 1111 point */ TYPENAME_TO_CLASS.put("point", Object.class);
/*polygon 1111 polygon */ TYPENAME_TO_CLASS.put("polygon", Object.class);
/*real 7 float4 */ TYPENAME_TO_CLASS.put("float4", Float.class);
/*refcursor 1111 refcursor */ TYPENAME_TO_CLASS.put("refcursor", Object.class);
/*regclass 1111 regclass */ TYPENAME_TO_CLASS.put("regclass", Object.class);
/*regconfig 1111 regconfig */ TYPENAME_TO_CLASS.put("regconfig", Object.class);
/*regdictionary 1111 regdictionary*/ TYPENAME_TO_CLASS.put("regdictionary", Object.class);
/*regoper 1111 regoper */ TYPENAME_TO_CLASS.put("regoper", Object.class);
/*regoperator 1111 regoperator */ TYPENAME_TO_CLASS.put("regoperator", Object.class);
/*regproc 1111 regproc */ TYPENAME_TO_CLASS.put("regproc", Object.class);
/*regprocedure 1111 regprocedure*/ TYPENAME_TO_CLASS.put("regprocedure", Object.class);
/*regtype 1111 regtype */ TYPENAME_TO_CLASS.put("regtype", Object.class);
/*reltime 1111 reltime */ TYPENAME_TO_CLASS.put("reltime", Object.class);
/*smallint 8 int2 */ TYPENAME_TO_CLASS.put("int2", Short.class);
/*smgr 1111 smgr */ TYPENAME_TO_CLASS.put("smgr", Object.class);
/*text 12 text */ TYPENAME_TO_CLASS.put("text", String.class);
/*tid 1111 tid */ TYPENAME_TO_CLASS.put("tid", Object.class);
/*timestamp without time zone 93 timestamp */ TYPENAME_TO_CLASS.put("timestamp", Timestamp.class);
/*timestamp with time zone 93 timestamptz */ TYPENAME_TO_CLASS.put("timestamptz", Timestamp.class);
/*time without time zone 92 time */ TYPENAME_TO_CLASS.put("time", Time.class);
/*time with time zone 92 timetz */ TYPENAME_TO_CLASS.put("timetz", Time.class);
/*tinterval 1111 tinterval */ TYPENAME_TO_CLASS.put("tinterval", Object.class);
/*tsquery 1111 tsquery */ TYPENAME_TO_CLASS.put("tsquery", Object.class);
/*tsvector 1111 tsvector */ TYPENAME_TO_CLASS.put("tsvector", Object.class);
/*txid_snapshot 1111 txid_snapshot*/ TYPENAME_TO_CLASS.put("txid_snapshot", Object.class);
/*uuid 1111 uuid */ TYPENAME_TO_CLASS.put("uuid", Object.class);
/*xid 1111 xid */ TYPENAME_TO_CLASS.put("xid", Object.class);
/*xml 2009 xml */ TYPENAME_TO_CLASS.put("xml", String.class);
/*box2d 1111 box2d */ TYPENAME_TO_CLASS.put("box2d", Object.class);
/*box3d 1111 box3d */ TYPENAME_TO_CLASS.put("box3d", Object.class);
/*box3d_extent 1111 box3d_extent*/ TYPENAME_TO_CLASS.put("box3d_extent", Object.class);
/*chip 1111 chip */ TYPENAME_TO_CLASS.put("chip", Object.class);
/*geography 1111 geography */ TYPENAME_TO_CLASS.put("geography", Object.class);
/*geometry_dump 2002 geometry_dump*/ TYPENAME_TO_CLASS.put("geometry_dump", Object.class);
/*gidx 1111 gidx */ TYPENAME_TO_CLASS.put("gidx", Object.class);
/*pgis_abs 1111 pgis_abs */ TYPENAME_TO_CLASS.put("pgis_abs", Object.class);
/*spheroid 1111 spheroid */ TYPENAME_TO_CLASS.put("spheroid", Object.class);
CLASS_TO_TYPENAME.put(String.class, "varchar");
CLASS_TO_TYPENAME.put(Boolean.class, "bool");
CLASS_TO_TYPENAME.put(boolean.class, "bool");
CLASS_TO_TYPENAME.put(Byte.class, "smallint");
CLASS_TO_TYPENAME.put(byte.class, "smallint");
CLASS_TO_TYPENAME.put(Short.class, "int2");
CLASS_TO_TYPENAME.put(short.class, "int2");
CLASS_TO_TYPENAME.put(Integer.class, "int4");
CLASS_TO_TYPENAME.put(int.class, "int4");
CLASS_TO_TYPENAME.put(Long.class, "int8");
CLASS_TO_TYPENAME.put(long.class, "int8");
CLASS_TO_TYPENAME.put(Float.class, "float4");
CLASS_TO_TYPENAME.put(float.class, "float4");
CLASS_TO_TYPENAME.put(Double.class, "float8");
CLASS_TO_TYPENAME.put(double.class, "float8");
CLASS_TO_TYPENAME.put(BigDecimal.class, "");
CLASS_TO_TYPENAME.put(Date.class, "date");
CLASS_TO_TYPENAME.put(Time.class, "time");
CLASS_TO_TYPENAME.put(java.util.Date.class, "timestamp");
CLASS_TO_TYPENAME.put(Timestamp.class, "timestamp");
CLASS_TO_TYPENAME.put(byte[].class, "blob");
CLASS_TO_TYPENAME.put(Coverage.class, "raster");
//POSTGIS extension
TYPENAME_TO_CLASS.put("GEOMETRY", Geometry.class);
TYPENAME_TO_CLASS.put("GEOGRAPHY", Geometry.class);
TYPENAME_TO_CLASS.put("POINT", Point.class);
TYPENAME_TO_CLASS.put("POINTM", Point.class);
TYPENAME_TO_CLASS.put("LINESTRING", LineString.class);
TYPENAME_TO_CLASS.put("LINESTRINGM", LineString.class);
TYPENAME_TO_CLASS.put("POLYGON", Polygon.class);
TYPENAME_TO_CLASS.put("POLYGONM", Polygon.class);
TYPENAME_TO_CLASS.put("MULTIPOINT", MultiPoint.class);
TYPENAME_TO_CLASS.put("MULTIPOINTM", MultiPoint.class);
TYPENAME_TO_CLASS.put("MULTILINESTRING", MultiLineString.class);
TYPENAME_TO_CLASS.put("MULTILINESTRINGM", MultiLineString.class);
TYPENAME_TO_CLASS.put("MULTIPOLYGON", MultiPolygon.class);
TYPENAME_TO_CLASS.put("MULTIPOLYGONM", MultiPolygon.class);
TYPENAME_TO_CLASS.put("GEOMETRYCOLLECTION", GeometryCollection.class);
TYPENAME_TO_CLASS.put("GEOMETRYCOLLECTIONM", GeometryCollection.class);
TYPENAME_TO_CLASS.put("RASTER", Coverage.class);
CLASS_TO_TYPENAME.put(Geometry.class, "GEOMETRY");
CLASS_TO_TYPENAME.put(Point.class, "POINT");
CLASS_TO_TYPENAME.put(LineString.class, "LINESTRING");
CLASS_TO_TYPENAME.put(Polygon.class, "POLYGON");
CLASS_TO_TYPENAME.put(MultiPoint.class, "MULTIPOINT");
CLASS_TO_TYPENAME.put(MultiLineString.class, "MULTILINESTRING");
CLASS_TO_TYPENAME.put(MultiPolygon.class, "MULTIPOLYGON");
CLASS_TO_TYPENAME.put(GeometryCollection.class, "GEOMETRYCOLLECTION");
TYPE_TO_ST_TYPE_MAP.put("GEOMETRY","ST_Geometry");
TYPE_TO_ST_TYPE_MAP.put("POINT","ST_Point");
TYPE_TO_ST_TYPE_MAP.put("LINESTRING","ST_LineString");
TYPE_TO_ST_TYPE_MAP.put("POLYGON","ST_Polygon");
TYPE_TO_ST_TYPE_MAP.put("MULTIPOINT","ST_MultiPoint");
TYPE_TO_ST_TYPE_MAP.put("MULTILINESTRING","ST_MultiLineString");
TYPE_TO_ST_TYPE_MAP.put("MULTIPOLYGON","ST_MultiPolygon");
TYPE_TO_ST_TYPE_MAP.put("GEOMETRYCOLLECTION","ST_GeometryCollection");
//postgis 1+ geometry and referencing
IGNORE_TABLES.add("spatial_ref_sys");
IGNORE_TABLES.add("geometry_columns");
IGNORE_TABLES.add("geography_columns");
//postgis 2 raster
IGNORE_TABLES.add("raster_columns");
IGNORE_TABLES.add("raster_overviews");
//filter capabilities
final String version = null;
//ID capabilities, support : EID, FID
final DefaultIdCapabilities idCapa = new DefaultIdCapabilities(true, true);
//Spatial capabilities
final GeometryOperand[] geometryOperands = new GeometryOperand[]{
GeometryOperand.Point,
GeometryOperand.LineString,
GeometryOperand.Polygon,
GeometryOperand.Envelope
};
final SpatialOperator[] spatialOperatrs = new SpatialOperator[]{
new DefaultSpatialOperator(BBOX.NAME , geometryOperands),
new DefaultSpatialOperator(Beyond.NAME , geometryOperands),
new DefaultSpatialOperator(Contains.NAME , geometryOperands),
new DefaultSpatialOperator(Crosses.NAME , geometryOperands),
new DefaultSpatialOperator(Disjoint.NAME , geometryOperands),
new DefaultSpatialOperator(DWithin.NAME , geometryOperands),
new DefaultSpatialOperator(Equals.NAME , geometryOperands),
new DefaultSpatialOperator(Intersects.NAME, geometryOperands),
new DefaultSpatialOperator(Overlaps.NAME , geometryOperands),
new DefaultSpatialOperator(Touches.NAME , geometryOperands),
new DefaultSpatialOperator(Within.NAME , geometryOperands)
};
final SpatialOperators spatialOperators = new DefaultSpatialOperators(spatialOperatrs);
final SpatialCapabilities spatialCapa = new DefaultSpatialCapabilities(geometryOperands, spatialOperators);
//scalar capabilities
//support : AND, OR, NOT
final boolean logical = true;
//support : =, <>, <, <=, >, >=, LIKE, BEETWEN, NULL
final Operator[] comparaisonOps = new Operator[]{
new DefaultOperator(PropertyIsEqualTo.NAME),
new DefaultOperator(PropertyIsNotEqualTo.NAME),
new DefaultOperator(PropertyIsLessThan.NAME),
new DefaultOperator(PropertyIsLessThanOrEqualTo.NAME),
new DefaultOperator(PropertyIsGreaterThan.NAME),
new DefaultOperator(PropertyIsGreaterThanOrEqualTo.NAME),
new DefaultOperator(PropertyIsLike.NAME),
new DefaultOperator(PropertyIsBetween.NAME),
new DefaultOperator(PropertyIsNull.NAME)
};
final ComparisonOperators comparisonOperators = new DefaultComparisonOperators(comparaisonOps);
//support : +, -, *, /
final boolean arithmeticSimple = true;
//support various functions
final FunctionName[] functionNames = new FunctionName[0];
final Functions functions = new DefaultFunctions(functionNames);
final ArithmeticOperators arithmeticOperators = new DefaultArithmeticOperators(arithmeticSimple, functions);
final ScalarCapabilities scalarCapa = new DefaultScalarCapabilities(logical, comparisonOperators, arithmeticOperators);
//temporal capabilities
final TemporalOperand[] temporalOperands = new TemporalOperand[0];
final TemporalOperator[] temporalOperatrs = new TemporalOperator[0];
final TemporalOperators temporalOperators = new DefaultTemporalOperators(temporalOperatrs);
final TemporalCapabilities temporalCapa = new DefaultTemporalCapabilities(temporalOperands, temporalOperators);
FILTER_CAPABILITIES = new DefaultFilterCapabilities(version, idCapa, spatialCapa, scalarCapa, temporalCapa);
}
private final DefaultJDBCFeatureStore featurestore;
//readers
private final ThreadLocal<WKBReader> wkbReader = new ThreadLocal<WKBReader>();
private final PostgisHexEWKB ewkbReader;
//cache
private Version version = null;
PostgresDialect(DefaultJDBCFeatureStore datastore) {
this.featurestore = datastore;
ewkbReader = new PostgisHexEWKB(featurestore.getGeometryFactory(),this);
}
DefaultJDBCFeatureStore getFeaturestore() {
return featurestore;
}
@Override
public boolean supportGlobalMetadata() {
return true;
}
@Override
public FilterCapabilities getFilterCapabilities() {
return FILTER_CAPABILITIES;
}
@Override
public FilterToSQL getFilterToSQL(FeatureType featureType) {
try{
PrimaryKey pk = null;
if(featureType!=null){
pk = featurestore.getDatabaseModel().getPrimaryKey(featureType.getName().toString());
}
return new PostgresFilterToSQL(
featureType, pk,
getVersion(featurestore.getDatabaseSchema()));
}catch(DataStoreException ex){
throw new RuntimeException(ex.getMessage(),ex);
}
}
@Override
public String getTableEscape() {
return "\"";
}
@Override
public Class getJavaType(int sqlType, String sqlTypeName) {
Class c = null;
sqlTypeName = sqlTypeName.toLowerCase();
if(sqlType == Types.ARRAY){
//special case for array types
if(sqlTypeName.startsWith("_")){
sqlTypeName = sqlTypeName.substring(1);
}
c = TYPENAME_TO_CLASS.get(sqlTypeName);
if(c==null) c = TYPENAME_TO_CLASS.get(sqlTypeName.toUpperCase());
if(c == null){
c = Object.class;
}
c = Array.newInstance(c, 0).getClass();
}else{
c = TYPENAME_TO_CLASS.get(sqlTypeName);
if(c==null) c = TYPENAME_TO_CLASS.get(sqlTypeName.toUpperCase());
if(c == null){
//try relying on base type.
c = TYPE_TO_CLASS.get(sqlType);
}
}
if(c == null){
featurestore.getLogger().log(Level.INFO, "No definied mapping for type : {0} {1}", new Object[]{sqlType, sqlTypeName});
c = Object.class;
}
return c;
}
@Override
public String getSQLType(Class javaType) throws SQLException{
String sqlName = CLASS_TO_TYPENAME.get(javaType);
if(javaType.isArray()){
sqlName = getSQLType(javaType.getComponentType());
if(sqlName == null) throw new SQLException("No database mapping for type "+ javaType);
sqlName = sqlName+"[]";
}
if(sqlName == null) throw new SQLException("No database mapping for type "+ javaType);
return sqlName;
}
@Override
public String getColumnSequence(Connection cx, String schemaName, String tableName, String columnName) throws SQLException {
final Statement st = cx.createStatement();
try {
final StringBuilder sb = new StringBuilder();
sb.append("SELECT pg_get_serial_sequence('");
encodeSchemaAndTableName(sb, schemaName, tableName);
sb.append("', '").append(columnName).append("')");
final String sql = sb.toString();
final ResultSet rs = st.executeQuery(sql);
try {
if(rs.next()){
return rs.getString(1);
}
} finally {
closeSafe(featurestore.getLogger(),rs);
}
} finally {
closeSafe(featurestore.getLogger(),st);
}
return null;
}
@Override
public boolean ignoreTable(String name) {
name = name.toLowerCase();
//ignore the versioning tables
if(name.startsWith("hs_tbl_")){
return true;
}
return IGNORE_TABLES.contains(name.toLowerCase());
}
@Override
public Version getVersion(String schema){
if(version != null){
return version;
}
Connection cx = null;
Statement statement = null;
ResultSet result = null;
try {
cx = featurestore.getDataSource().getConnection();
statement = cx.createStatement();
result = statement.executeQuery("SELECT postgis_lib_version();");
if (result.next()) {
version = new Version(result.getString(1));
}else{
version = new Version("0.0.0");
}
} catch(SQLException ex){
featurestore.getLogger().log(Level.WARNING, ex.getMessage(),ex);
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(),cx,statement,result);
}
return version;
}
////////////////////////////////////////////////////////////////////////////
// METHODS TO CREATE SQL QUERIES ///////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public String encodeFilter(Filter filter, FeatureType type) {
final FilterToSQL fts = getFilterToSQL(type);
final StringBuilder sb = (StringBuilder)filter.accept(fts, new StringBuilder());
return sb.toString();
}
@Override
public void encodeColumnType(StringBuilder sql, String sqlTypeName, Integer length) {
if(TYPE_TO_ST_TYPE_MAP.containsKey(sqlTypeName)){
//geometry type, will be added as a constraint in the postcreate method
sqlTypeName = "GEOMETRY";
}
if(length == null){
sql.append(sqlTypeName);
}else{
final int arrayindex = sqlTypeName.indexOf("[]");
if(arrayindex>0){
sql.append(sqlTypeName.substring(0, arrayindex));
sql.append('(').append(length).append(')');
sql.append(sqlTypeName.substring(arrayindex));
}else{
sql.append(sqlTypeName);
sql.append('(').append(length).append(')');
}
}
}
@Override
public void encodeGeometryColumn(StringBuilder sql, AttributeType gatt, int srid, Hints hints) {
double res = 0;
if(hints != null){
double[] ress = (double[]) hints.get(JDBCFeatureStore.RESAMPLING);
if(ress != null){
res = Math.min(ress[0], ress[1]);
}
}
if(Double.isInfinite(res)){
res = Double.MAX_VALUE;
}
//postgis raster type
final Class binding = gatt.getValueClass();
if(Coverage.class.isAssignableFrom(binding)){
sql.append("encode(st_asbinary(");
encodeColumnName(sql, gatt.getName().tip().toString());
sql.append("),'base64')");
return;
}
final CoordinateReferenceSystem crs = FeatureExt.getCRS(gatt);
final int dimensions = (crs == null) ? 2 : crs.getCoordinateSystem().getDimension();
sql.append("encode(");
if(res > 0){
if (dimensions > 2) {
if (((Comparable)getVersion(null).getMajor()).compareTo((Comparable)Integer.valueOf(2)) >= 0) {
sql.append("ST_AsEWKB(st_simplifyPreserveTopology(");
encodeColumnName(sql, gatt.getName().tip().toString());
sql.append(",").append(res).append(")");
} else {
sql.append("ST_AsEWKB(st_simplify(");
encodeColumnName(sql, gatt.getName().tip().toString());
sql.append(",").append(res).append(")");
}
} else {
if (((Comparable)getVersion(null).getMajor()).compareTo((Comparable)Integer.valueOf(2)) >= 0) {
sql.append("ST_AsBinary(st_simplifyPreserveTopology(");
encodeColumnName(sql, gatt.getName().tip().toString());
sql.append(",").append(res).append(")");
} else {
sql.append("ST_AsBinary(st_simplify(");
encodeColumnName(sql, gatt.getName().tip().toString());
sql.append(",").append(res).append(")");
}
}
sql.append(") ");
}else{
if (dimensions > 2) {
sql.append("ST_AsEWKB(");
encodeColumnName(sql, gatt.getName().tip().toString());
} else {
sql.append("ST_AsBinary(");
encodeColumnName(sql, gatt.getName().tip().toString());
}
sql.append(") ");
}
sql.append(",'base64')");
}
@Override
public void encodeLimitOffset(StringBuilder sql, Integer limit, int offset) {
if (limit != null && limit > 0 && limit < Integer.MAX_VALUE) {
sql.append(" LIMIT ").append(limit);
}
if (offset > 0) {
sql.append(" OFFSET ").append(offset);
}
}
@Override
public void encodeValue(StringBuilder sql, Object value, Class type) {
//turn the value into a literal and use FilterToSQL to encode it
final Literal literal = featurestore.getFilterFactory().literal(value);
literal.accept(getFilterToSQL(null), sql);
}
@Override
public void encodeGeometryValue(StringBuilder sql, Geometry value, int srid) throws DataStoreException {
if (value == null) {
sql.append("NULL");
} else {
if (value instanceof LinearRing) {
//postgis does not handle linear rings, convert to just a line string
value = value.getFactory().createLineString(((LinearRing) value).getCoordinateSequence());
}
if(value.isEmpty() && ((Comparable)getVersion(null).getMajor()).compareTo((Comparable)Integer.valueOf(2)) < 0){
//empty geometries are interpreted as Geometrycollection in postgis < 2
//this breaks the column geometry type constraint so we replace those by null
sql.append("NULL");
}else{
sql.append("st_geomfromtext('").append(value.toText()).append("', ").append(srid).append(")");
}
}
}
@Override
public void encodeCoverageValue(StringBuilder sql, Coverage value) throws DataStoreException {
try{
final WKBRasterWriter writer = new WKBRasterWriter();
final byte[] wkbimg = writer.write((GridCoverage2D)value);
final String base64 = Base64.encodeBytes(wkbimg);
sql.append("(encode(").append("decode('").append(base64).append("','base64')").append(",'hex')").append(")::raster");
}catch(IOException | FactoryException ex){
throw new DataStoreException(ex);
}
}
@Override
public void encodePrimaryKey(StringBuilder sql, Class binding, String sqlType) {
if(Integer.class.isAssignableFrom(binding) || Short.class.isAssignableFrom(binding)){
sql.append(" SERIAL ");
}else if(Long.class.isAssignableFrom(binding)){
sql.append(" BIGSERIAL ");
}else{
sql.append(' ').append(sqlType).append(' ');
}
sql.append("PRIMARY KEY");
}
@Override
public void postCreateTable(String schemaName, final FeatureType featureType,
final Connection cx) throws SQLException{
if (schemaName == null) {
schemaName = "public";
}
final String tableName = featureType.getName().tip().toString();
Statement st = null;
try {
st = cx.createStatement();
// register all geometry columns in the database
for (PropertyType att : featureType.getProperties(true)) {
if (AttributeConvention.contains(att.getName())) continue;
if (AttributeConvention.isGeometryAttribute(att)) {
PropertyType gd = (PropertyType) att;
// lookup or reverse engineer the srid
int srid = -1;
if (FeatureExt.getCharacteristicValue(gd, JDBCFeatureStore.JDBC_PROPERTY_SRID.getName().toString(), null) != null) {
srid = (Integer) FeatureExt.getCharacteristicValue(gd, JDBCFeatureStore.JDBC_PROPERTY_SRID.getName().toString(), null);
} else if (FeatureExt.getCRS(gd) != null) {
try {
final IdentifiedObjectFinder finder = IdentifiedObjects.newFinder("EPSG");
finder.setIgnoringAxes(true);
IdentifiedObject replacement = finder.findSingleton(FeatureExt.getCRS(gd));
final Identifier result = IdentifiedObjects.getIdentifier(replacement, Citations.EPSG);
if (result != null) {
srid = Integer.parseInt(result.getCode());
}
} catch (FactoryException e) {
featurestore.getLogger().log(Level.FINE, "Error looking up the "
+ "epsg code for metadata "
+ "insertion, assuming -1", e);
}
}
while (gd instanceof Operation) {
gd = (PropertyType) ((Operation)gd).getResult();
}
Class binding = ((AttributeType)gd).getValueClass();
if(Coverage.class.isAssignableFrom(binding)){
//postgis raster type
final StringBuilder sb = new StringBuilder();
sb.append("select addrasterconstraints('");
sb.append(schemaName);
sb.append("', '");
sb.append(featureType.getName().tip().toString());
sb.append("', '");
sb.append(att.getName().tip().toString());
sb.append("', ");
sb.append("true");
sb.append(", false, false, false, false, false, false, false, false, false, false, false);");
final String sql = sb.toString();
featurestore.getLogger().fine( sql );
st.execute( sql );
//add the srid in the comments
//the view crs is not set until a first raster in added, so we store it in the comments
st.execute("COMMENT ON COLUMN \""+schemaName+"\".\""+featureType.getName().tip().toString()+"\".\""+att.getName().tip().toString()+"\" IS '"+srid+"';");
continue;
}
// assume 2 dimensions, but ease future customisation
final int dimensions = 2;
// grab the geometry type
String geomType = getSQLType(binding);
if (geomType == null)
geomType = "GEOMETRY";
// register the geometry type, first remove and eventual
// leftover, then write out the real one
StringBuilder sb = new StringBuilder("DELETE FROM GEOMETRY_COLUMNS");
sb.append(" WHERE f_table_catalog=''");
sb.append(" AND f_table_schema = '").append(schemaName).append('\'');
sb.append(" AND f_table_name = '").append(tableName).append('\'');
sb.append(" AND f_geometry_column = '").append(gd.getName().tip().toString()).append('\'');
String sql = sb.toString();
featurestore.getLogger().fine( sql );
st.execute( sql );
sb = new StringBuilder("INSERT INTO GEOMETRY_COLUMNS VALUES ('',");
sb.append('\'').append(schemaName).append("',");
sb.append('\'').append(tableName).append("',");
sb.append('\'').append(gd.getName().tip().toString()).append("',");
sb.append(dimensions).append(',');
sb.append(srid).append(',');
sb.append('\'').append(geomType).append("')");
sql = sb.toString();
featurestore.getLogger().fine( sql );
st.execute( sql );
// add srid checks
if (srid > -1) {
sb = new StringBuilder("ALTER TABLE ");
encodeSchemaAndTableName(sb, schemaName, tableName);
sb.append(" ADD CONSTRAINT \"enforce_srid_");
sb.append(gd.getName().tip().toString()).append('"');
sb.append(" CHECK (st_srid(");
sb.append('"').append(gd.getName().tip().toString()).append('"');
sb.append(") = ").append(srid).append(')');
sql = sb.toString();
featurestore.getLogger().fine( sql );
st.execute(sql);
}
// add dimension checks
sb = new StringBuilder("ALTER TABLE ");
encodeSchemaAndTableName(sb, schemaName, tableName);
sb.append(" ADD CONSTRAINT \"enforce_dims_");
sb.append(gd.getName().tip().toString()).append('"');
sb.append(" CHECK (st_ndims(\"").append(gd.getName().tip().toString()).append("\") = 2)");
sql = sb.toString();
featurestore.getLogger().fine(sql);
st.execute(sql);
// add geometry type checks
if(!"geometry".equalsIgnoreCase(geomType)){
sb = new StringBuilder("ALTER TABLE ");
encodeSchemaAndTableName(sb, schemaName, tableName);
sb.append(" ADD CONSTRAINT \"enforce_geotype_");
sb.append(gd.getName().tip().toString()).append('"');
sb.append(" CHECK (st_geometrytype(");
sb.append('"').append(gd.getName().tip().toString()).append('"');
sb.append(") = '").append(TYPE_TO_ST_TYPE_MAP.get(geomType)).append("'::text OR \"");
sb.append(gd.getName().tip().toString()).append('"').append(" IS NULL)");
sql = sb.toString();
featurestore.getLogger().fine(sql);
st.execute(sql);
}
// add the spatial index
sb = new StringBuilder("CREATE INDEX \"spatial_").append(tableName);
sb.append('_').append(gd.getName().tip().toString().toLowerCase()).append('"');
sb.append(" ON ");
encodeSchemaAndTableName(sb, schemaName, tableName);
sb.append(" USING GIST (");
sb.append('"').append(gd.getName().tip().toString()).append('"').append(')');
sql = sb.toString();
featurestore.getLogger().fine(sql);
st.execute(sql);
}
}
if(!cx.getAutoCommit()){
cx.commit();
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(),st);
}
}
////////////////////////////////////////////////////////////////////////////
// PRIMARY KEY CALCULATION METHOS //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public Object nextValue(final ColumnMetaModel column, final Connection cx) throws SQLException, DataStoreException {
if(column.getType() == ColumnMetaModel.Type.SEQUENCED){
final Statement st = cx.createStatement();
ResultSet rs = null;
try {
final String sql = "SELECT nextval('" + column.getSequenceName() + "')";
rs = st.executeQuery(sql);
if (rs.next()) {
return rs.getLong(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(), null,st,rs);
}
return null;
}
return null;
}
////////////////////////////////////////////////////////////////////////////
// METHODS TO READ FROM RESULTSET //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
@Override
public void decodeColumnType(final SingleAttributeTypeBuilder atb, final Connection cx,
String typeName, final int datatype, final String schemaName,
final String tableName, final String columnName) throws SQLException {
super.decodeColumnType(atb, cx, typeName, datatype,
schemaName, tableName,columnName);
typeName = typeName.toUpperCase();
//postgis raster type
if("RASTER".equals(typeName)){
atb.setValueClass(Coverage.class);
return;
}
if (atb.getValueClass().isArray()) {
//get dimension size, JDBC do not provide the exact size
final String query = "select att.attndims " +
"from pg_attribute att " +
"join pg_class tbl on tbl.oid = att.attrelid " +
"join pg_namespace ns on tbl.relnamespace = ns.oid " +
"where tbl.relname = '" +tableName+"' " +
"and ns.nspname = '" +schemaName+"' " +
"and att.attname = '" +columnName+"' ";
Statement statement = null;
ResultSet result = null;
int nbDim = 1;
try {
statement = cx.createStatement();
result = statement.executeQuery(query);
if (result.next()) {
nbDim = result.getInt(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(),null,statement,result);
}
if (nbDim!=1) {
Class base = atb.getValueClass().getComponentType();
atb.setValueClass(Classes.changeArrayDimension(base, nbDim));
}
}
if (!TYPE_TO_ST_TYPE_MAP.containsKey(typeName)) {
return;
}
String gType = null;
if(tableName == null || tableName.isEmpty()){
//this column informations seems to come from a custom sql query
//the result will be the natural encoding of postgis which is hexadecimal EWKB
atb.addCharacteristic(GEOM_ENCODING, GeometryEncoding.class, 1, 1, PostgresDialect.GeometryEncoding.HEXEWKB);
}else{
//this column informations comes from a real table
atb.addCharacteristic(GEOM_ENCODING, GeometryEncoding.class, 1, 1, PostgresDialect.GeometryEncoding.WKB);
//first attempt, try with the geometry metadata
Statement statement = null;
ResultSet result = null;
try {
final StringBuilder sb = new StringBuilder("SELECT TYPE FROM GEOMETRY_COLUMNS WHERE ");
sb.append("F_TABLE_SCHEMA = '").append(schemaName).append("' ");
sb.append("AND F_TABLE_NAME = '").append(tableName).append("' ");
sb.append("AND F_GEOMETRY_COLUMN = '").append(columnName).append('\'');
final String sqlStatement = sb.toString();
featurestore.getLogger().log(Level.FINE, "Geometry type check; {0} ", sqlStatement);
statement = cx.createStatement();
result = statement.executeQuery(sqlStatement);
if (result.next()) {
gType = result.getString(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(),null,statement,result);
}
}
// decode the type
Class geometryClass = null;
if (gType != null) {
geometryClass = (Class) TYPENAME_TO_CLASS.get(gType.replaceFirst("ST_", "").toUpperCase());
}
if (geometryClass == null) {
geometryClass = Geometry.class;
}
atb.setValueClass(geometryClass);
}
@Override
public void decodeGeometryColumnType(final SingleAttributeTypeBuilder atb, final Connection cx,
final ResultSet rs, final int columnIndex, boolean customQuery) throws SQLException {
final Jdbc4ResultSetMetaData metadata = (Jdbc4ResultSetMetaData)rs.getMetaData();
String typeName = metadata.getColumnTypeName(columnIndex);
final String columnName = metadata.getColumnName(columnIndex);
final String columnBaseName = metadata.getBaseColumnName(columnIndex);
final String schemaName = metadata.getBaseSchemaName(columnIndex);
final String tableName = metadata.getTableName(columnIndex);
final String tableBaseName = metadata.getBaseTableName(columnIndex);
typeName = typeName.toUpperCase();
if (!TYPE_TO_ST_TYPE_MAP.containsKey(typeName)) {
return;
}
String gType = null;
if(tableName == null || tableName.isEmpty() || customQuery){
//this column informations seems to come from a custom sql query
//the result will be the natural encoding of postgis which is hexadecimal EWKB
atb.addCharacteristic(GEOM_ENCODING, GeometryEncoding.class, 1, 1, PostgresDialect.GeometryEncoding.HEXEWKB);
}else{
//this column informations comes from a real table
atb.addCharacteristic(GEOM_ENCODING, GeometryEncoding.class, 1, 1, PostgresDialect.GeometryEncoding.WKB);
}
//first attempt, try with the geometry metadata
Statement statement = null;
ResultSet result = null;
try {
final StringBuilder sb = new StringBuilder("SELECT TYPE FROM GEOMETRY_COLUMNS WHERE ");
sb.append("F_TABLE_SCHEMA = '").append(schemaName).append("' ");
sb.append("AND F_TABLE_NAME = '").append(tableBaseName).append("' ");
sb.append("AND F_GEOMETRY_COLUMN = '").append(columnBaseName).append('\'');
final String sqlStatement = sb.toString();
featurestore.getLogger().log(Level.FINE, "Geometry type check; {0} ", sqlStatement);
statement = cx.createStatement();
result = statement.executeQuery(sqlStatement);
if (result.next()) {
gType = result.getString(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(),null,statement,result);
}
// decode the type
Class geometryClass = null;
if (gType != null) {
geometryClass = (Class) TYPENAME_TO_CLASS.get(gType.replaceFirst("ST_", "").toUpperCase());
}
if (geometryClass == null) {
geometryClass = Geometry.class;
}
atb.setName(columnName);
atb.setValueClass(geometryClass);
}
@Override
public Integer getGeometrySRID(String schemaName, final String tableName, final String columnName,
Map metas, final Connection cx) throws SQLException{
// first attempt, try with the geometry metadata
Statement statement = null;
ResultSet result = null;
Integer srid = null;
//search in the geometry columns
try {
final StringBuilder sb = new StringBuilder("SELECT SRID FROM GEOMETRY_COLUMNS WHERE ");
if (schemaName != null && !schemaName.isEmpty()) {
sb.append("F_TABLE_SCHEMA = '").append(schemaName).append("' ");
sb.append(" AND ");
}
sb.append("F_TABLE_NAME = '").append(tableName).append("' ");
sb.append("AND F_GEOMETRY_COLUMN = '").append(columnName).append('\'');
final String sqlStatement = sb.toString();
featurestore.getLogger().log(Level.FINE, "Geometry type check; {0} ", sqlStatement);
statement = cx.createStatement();
result = statement.executeQuery(sqlStatement);
if (result.next()) {
srid = result.getInt(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(), null,statement,result);
}
if(srid==null || srid==0){
//search the raster columns view
try {
final StringBuilder sb = new StringBuilder("SELECT SRID FROM RASTER_COLUMNS WHERE ");
if (schemaName != null && !schemaName.isEmpty()) {
sb.append("R_TABLE_SCHEMA = '").append(schemaName).append("' ");
sb.append(" AND ");
}
sb.append("R_TABLE_NAME = '").append(tableName).append("' ");
sb.append("AND R_RASTER_COLUMN = '").append(columnName).append('\'');
final String sqlStatement = sb.toString();
featurestore.getLogger().log(Level.FINE, "Raster type check; {0} ", sqlStatement);
statement = cx.createStatement();
result = statement.executeQuery(sqlStatement);
if (result.next()) {
srid = result.getInt(1);
}
} finally {
JDBCFeatureStoreUtilities.closeSafe(featurestore.getLogger(), null,statement,result);
}
}
if(srid==null || srid==0){
//still nothing ? search in the comment, if it is a raster column the srid
//can not be set until there is a real data. so we stored the srid in the comment
final String comments = (String) metas.get(MetaDataConstants.Column.REMARKS);
if(comments != null){
try{
srid = Integer.valueOf(comments);
}catch(NumberFormatException ex){
//we tryed
}
}
}
return srid;
}
@Override
public CoordinateReferenceSystem createCRS(int srid, Connection cx) throws SQLException {
CoordinateReferenceSystem crs = CRS_CACHE.get(srid);
if (crs == null) {
try {
crs = AbstractCRS.castOrCopy(CRS.forCode("EPSG:" + srid)).forConvention(AxesConvention.RIGHT_HANDED);
CRS_CACHE.put(srid, crs);
} catch(Exception e) {
if(featurestore.getLogger().isLoggable(Level.FINE)) {
featurestore.getLogger().log(Level.FINE, "Could not decode " + srid + " using the built-in EPSG database", e);
}
return null;
}
}
return crs;
}
@Override
public Object decodeAttributeValue(AttributeType descriptor, ResultSet rs,
int i) throws SQLException{
final Class binding = descriptor.getValueClass();
if(binding.isArray()){
if (rs.getArray(i) != null) {
Object baseArray = rs.getArray(i).getArray();
Class c = binding.getComponentType();
while(c.isArray()) c = c.getComponentType();
if(!baseArray.getClass().getComponentType().equals(c)){
//postgres handle multi depth array, yet do not declare them as Nd array in metadatas
//find the number of dimensions
int nbdim=1;
Class base = baseArray.getClass().getComponentType();
while(base.isArray()){
base = base.getComponentType();
nbdim++;
}
baseArray = rebuildArray(baseArray, c, nbdim);
// if(nbdim==1){
// //not exact match retype it
// int size = Array.getLength(baseArray);
// final Object rarray = Array.newInstance(c, size);
// for(int k=0; k<size; k++){
// Array.set(rarray, k, Converters.convert(Array.get(baseArray, k), c));
// }
// baseArray = rarray;
// }else{
// final Object rarray = Array.newInstance(c, new int[nbdim]);
//
// }
}
return baseArray;
} else {
return null;
}
}else{
Object v = rs.getObject(i);
if (!binding.isInstance(v)) {
v = ObjectConverters.convert(v, binding);
}
return v;
}
}
private Object rebuildArray(Object candidate, Class componentType, int depth){
if(candidate==null) return null;
if(candidate.getClass().isArray()){
final int size = Array.getLength(candidate);
final int[] dims = new int[depth];
dims[0] = size;
final Object rarray = Array.newInstance(componentType, dims);
depth--;
for(int k=0; k<size; k++){
Array.set(rarray, k, rebuildArray(Array.get(candidate, k), componentType, depth));
}
return rarray;
}else{
return ObjectConverters.convert(candidate, componentType);
}
}
@Override
public Geometry decodeGeometryValue(AttributeType descriptor, ResultSet rs,
String column) throws IOException, SQLException {
switch((GeometryEncoding)FeatureExt.getCharacteristicValue(descriptor, GEOM_ENCODING, null)){
case HEXEWKB:
return ewkbReader.read(rs.getString(column));
case WKB:
WKBReader reader = wkbReader.get();
if (reader == null) {
reader = new WKBReader(featurestore.getGeometryFactory());
wkbReader.set(reader);
}
try {
return (Geometry) reader.read(Base64.decode(rs.getBytes(column)));
} catch (ParseException ex) {
throw new IOException(ex.getMessage(),ex);
}
default:
throw new IllegalStateException("Can not decode geometry not knowing it's encoding.");
}
}
@Override
public Geometry decodeGeometryValue(AttributeType descriptor, ResultSet rs,
int column) throws IOException, SQLException {
GeometryEncoding ge = FeatureExt.getCharacteristicValue(descriptor, GEOM_ENCODING, null);
if(ge==null){
//try to guess type
final Object obj = rs.getObject(column);
if(obj instanceof String){
ge = GeometryEncoding.WKT;
}else{
ge = GeometryEncoding.HEXEWKB;
}
}
switch(ge){
case HEXEWKB:
return ewkbReader.read(rs.getString(column));
case WKB:
WKBReader reader = wkbReader.get();
if (reader == null) {
reader = new WKBReader(featurestore.getGeometryFactory());
wkbReader.set(reader);
}
final byte[] encodedValue = rs.getBytes(column);
if (encodedValue != null) {
try {
return (Geometry) reader.read(Base64.decode(encodedValue));
} catch (ParseException ex) {
throw new IOException(ex.getMessage(),ex);
}
}
return null;
default:
throw new IllegalStateException("Can not decode geometry not knowing it's encoding.");
}
}
@Override
public Coverage decodeCoverageValue(AttributeType descriptor, ResultSet rs, String column) throws IOException, SQLException {
byte[] data = rs.getBytes(column);
try {
data = Base64.decode(data);
final WKBRasterReader reader = new WKBRasterReader();
return reader.readCoverage(data, null);
} catch (IOException | FactoryException ex) {
throw new SQLException("Failed to uncompressed base64 : "+ex.getMessage(),ex);
}
}
@Override
public Coverage decodeCoverageValue(AttributeType descriptor, ResultSet rs, int column) throws IOException, SQLException {
byte[] data = rs.getBytes(column);
try {
data = Base64.decode(data);
final WKBRasterReader reader = new WKBRasterReader();
return reader.readCoverage(data, null);
} catch (IOException | FactoryException ex) {
throw new SQLException("Failed to uncompressed base64 : "+ex.getMessage(),ex);
}
}
public CoordinateReferenceSystem decodeCRS(final int srid, final Connection cx) throws SQLException{
CoordinateReferenceSystem crs = CRS_CACHE.get(srid);
if (crs == null) {
try {
crs = AbstractCRS.castOrCopy(CRS.forCode("EPSG:" + srid)).forConvention(AxesConvention.RIGHT_HANDED);
CRS_CACHE.put(srid, crs);
} catch(Exception e) {
if(featurestore.getLogger().isLoggable(Level.FINE)) {
featurestore.getLogger().log(Level.FINE, "Could not decode " + srid + " using the built-in EPSG database", e);
}
return null;
}
}
return crs;
}
}