package org.gcube.application.cms.sdi.engine; import java.sql.PreparedStatement; import java.sql.SQLException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gcube.application.cms.sdi.faults.DataParsingException; import org.gcube.application.cms.sdi.model.MappingObject; import org.gcube.application.geoportal.common.model.legacy.BBOX; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; @Slf4j @RequiredArgsConstructor @Getter @ToString public class PostgisTable { @Getter @AllArgsConstructor public static enum GeometryType { MULTIPOINT("4326", "geometry (MULTIPOINT,4326)", "", ""), POINT("4326", "geometry (POINT,4326)", "", ""), LINE("4326", "geometry (MULTILINESTRING,4326)", "", ""), POLYGON("4326", "geometry (MULTIPOLYGON,4326)", "", ""); private final String SRID; private final String definition; private final String InsertWKT; private final String insertWKB; } @RequiredArgsConstructor @Getter @Setter @ToString public static class Field { @NonNull private String name; @NonNull private FieldType type; private Boolean isIndexed; private Object constantValue; public static final Field fromMapping(MappingObject m) { return new PostgisTable.Field(m.getName(), PostgisTable.FieldType.valueOf(m.getType())); } public static final List fromMappings(Collection coll) { ArrayList toReturn = new ArrayList<>(); if (coll != null) coll.forEach(m -> toReturn.add(fromMapping(m))); return toReturn; } } @Getter @AllArgsConstructor public enum FieldType { INT("int", java.sql.Types.INTEGER), BOOLEAN("boolean", java.sql.Types.BOOLEAN), TEXT("text", java.sql.Types.LONGVARCHAR), FLOAT("float", java.sql.Types.FLOAT), GEOMETRY("", 0), AUTOINCREMENT("BIGSERIAL PRIMARY KEY", java.sql.Types.BIGINT); private String definition; private int sqlType; } @RequiredArgsConstructor @Getter @ToString public static class POINT { private static Pattern pattern = Pattern.compile("(?!=\\d\\.\\d\\.)([\\d.]+)"); public static POINT parsePOINT(String point) throws DataParsingException { // POINT(8.30230113965909 44.8011688237011) // x,y try { log.debug("Parsing POINT " + point); Matcher m = pattern.matcher(point); if (!m.find()) throw new DataParsingException("Unable to get x "); Double x = Double.parseDouble(m.group(1)); if (!m.find()) throw new DataParsingException("Unable to get y "); Double y = Double.parseDouble(m.group(1)); return new POINT(x, y); } catch (Throwable t) { throw new DataParsingException("Invalid POINT " + point, t); } } @NonNull private Double x; @NonNull private Double y; } private static final NumberFormat DECIMAL_FORMAT = NumberFormat.getInstance(Locale.US); static { ((DecimalFormat) DECIMAL_FORMAT).setGroupingUsed(false); } public String getGeometryColumn() { for (Field f : fields) if (f.getType().equals(FieldType.GEOMETRY)) return f.getName(); return null; } @NonNull private String tablename; @NonNull private List fields; @NonNull private GeometryType geometryColumnType; @Setter private BBOX boundingBox = null; @Setter private POINT centroid = null; public void setTablename(String tablename) { this.tablename = sanitizeFieldName(tablename); } public String getCreateStatement() { StringBuilder stmt = new StringBuilder(); stmt.append("CREATE TABLE IF NOT EXISTS " + tablename + "( "); for (Field field : fields) { String fieldDefinition = field.getType().getDefinition(); if (field.getType().equals(FieldType.GEOMETRY)) fieldDefinition = this.getGeometryColumnType().definition; stmt.append(field.getName() + " " + fieldDefinition + ","); } stmt.deleteCharAt(stmt.lastIndexOf(",")); stmt.append(")"); return stmt.toString(); } public String getDeleteByFieldStatement(Field field) { return "DELETE FROM " + tablename + " WHERE " + field.getName() + " = ? "; } public String getInsertionStatement(boolean geometryText) { StringBuilder fieldList = new StringBuilder(); StringBuilder fieldInsertion = new StringBuilder(); for (Field field : fields) { switch (field.getType()) { case AUTOINCREMENT: break; case GEOMETRY: { fieldList.append(field.getName() + ","); if (geometryText) fieldInsertion.append("ST_GeomFromText(?, 4326),"); else fieldInsertion.append("ST_GeomFromWKB(?, 4326),"); break; } default: { fieldList.append(field.getName() + ","); fieldInsertion.append("?,"); } } } fieldList.deleteCharAt(fieldList.lastIndexOf(",")); fieldInsertion.deleteCharAt(fieldInsertion.lastIndexOf(",")); return "Insert into " + tablename + " (" + fieldList + ") VALUES (" + fieldInsertion + ")"; } public void fillObjectsPreparedStatement(Map row, PreparedStatement toFill) throws SQLException { int psFieldIndex = 0; HashMap rowValues = new HashMap(); for (Map.Entry entry : row.entrySet()) rowValues.put(sanitizeFieldName(entry.getKey()), entry.getValue()); for (Field field : fields) { if (!field.getType().equals(FieldType.AUTOINCREMENT)) { psFieldIndex++; Object value = rowValues.get(field.getName()); setObjectInPreparedStatement(field, value, toFill, psFieldIndex); } } } public void setObjectInPreparedStatement(Field field, Object value, PreparedStatement toFill, int psFieldIndex) throws SQLException { if (value == null) { try { toFill.setNull(psFieldIndex, field.getType().sqlType); } catch (SQLException e) { log.error("Unable to set null for field " + field); throw e; } } else { switch (field.getType()) { case FLOAT: { toFill.setFloat(psFieldIndex, (Float) value); break; } case INT: { toFill.setInt(psFieldIndex, (Integer) value); break; } case TEXT: { toFill.setString(psFieldIndex, value.toString()); break; } case GEOMETRY: { if (value instanceof String) toFill.setString(psFieldIndex, ((String) value)); else toFill.setBytes(psFieldIndex, (byte[]) value); break; } case BOOLEAN: { if (value instanceof String) toFill.setBoolean(psFieldIndex, Boolean.parseBoolean(value.toString())); if (value instanceof Boolean) toFill.setBoolean(psFieldIndex, (Boolean) value); break; } // Added by Francesco default: { if (value instanceof String) { toFill.setString(psFieldIndex, ((String) value)); } else { try { String toStringValue = value.toString(); toFill.setString(psFieldIndex, toStringValue); } catch (Exception e) { // silence } } } } } } public void fillCSVPreparedStatament(Map row, PreparedStatement toFill, boolean explicitGeometry) throws SQLException { int psFieldIndex = 0; HashMap rowValues = new HashMap(); for (Map.Entry entry : row.entrySet()) rowValues.put(sanitizeFieldName(entry.getKey()), entry.getValue()); for (Field field : fields) { if (!field.getType().equals(FieldType.AUTOINCREMENT)) { psFieldIndex++; String value = rowValues.get(field.getName()); // if(value==null||value.equalsIgnoreCase("null")) toFill.setNull(psFieldIndex, field.getType().sqlType); // else switch (field.getType()) { case FLOAT: { try { toFill.setFloat(psFieldIndex, Float.parseFloat(value)); } catch (NumberFormatException e) { throw new SQLException(field + " cannot be null. CSV Row is " + rowValues, e); } break; } case INT: { try { toFill.setInt(psFieldIndex, Integer.parseInt(value)); } catch (NumberFormatException e) { log.warn("Skipping value for " + field + " row was " + rowValues, e); toFill.setNull(psFieldIndex, java.sql.Types.INTEGER); } break; } case TEXT: { toFill.setString(psFieldIndex, value.toString()); break; } case GEOMETRY: { if (explicitGeometry) { toFill.setString(psFieldIndex, value); } else { switch (geometryColumnType) { case POINT: { String xRepresentation = DECIMAL_FORMAT .format(Double.parseDouble(rowValues.get(DBConstants.Defaults.XCOORD_FIELD))); String yRepresentation = DECIMAL_FORMAT .format(Double.parseDouble(rowValues.get(DBConstants.Defaults.YCOORD_FIELD))); toFill.setString(psFieldIndex, "POINT(" + xRepresentation + " " + yRepresentation + ")"); break; } default: { toFill.setString(psFieldIndex, rowValues.get("wkt")); break; } } } break; } } } } } public static String sanitizeFieldName(String fieldName) { // return fieldName.toLowerCase().replaceAll(" ", "_").replaceAll("\\.", "").replaceAll("-", "_").replaceAll("////","_"); return fieldName.toLowerCase().replaceAll("[^a-z0-9_\\\\]", "_"); } }