package nl.ipo.cds.etl.db;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import nl.ipo.cds.etl.db.annotation.CodeSpaceColumn;
import nl.ipo.cds.etl.db.annotation.Column;
import nl.ipo.cds.etl.db.annotation.Table;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.deegree.commons.tom.ows.CodeType;
import org.deegree.geometry.Geometry;
import org.deegree.geometry.io.WKTWriter;
import org.postgis.PGgeometry;
import org.postgis.binary.BinaryWriter;
public class DBWriterFactory<T> {
private static final Log logger = LogFactory.getLog(DBWriterFactory.class);
private static interface DatabaseColumn {
public String toString(Object o);
public String getName();
}
private static class ConstDatabaseColumn implements DatabaseColumn {
private final String name, constValue;
ConstDatabaseColumn(String name, String constValue) {
this.name = name;
this.constValue = constValue;
}
@Override
public String toString(Object o) {
return constValue;
}
@Override
public String getName() {
return name;
}
}
private static class FieldDatabaseColumn implements DatabaseColumn {
FieldDatabaseColumn(Field field, Column column) {
this.field = field;
this.column = column;
}
Field field;
Column column;
@Override
public String toString(Object o) {
try {
Object fieldObj = field.get(o);
return fieldObj == null ? null : fieldObj.toString();
} catch(Exception e) {
throw new RuntimeException("Couldn't convert object to string", e);
}
}
@Override
public String getName() {
String name = column.name();
if(name.equals("##default")) {
name = field.getName();
}
return name;
}
}
private static class StringArrayFieldDatabaseColumn extends FieldDatabaseColumn {
StringArrayFieldDatabaseColumn (final Field field, final Column column) {
super (field, column);
}
@Override
public String toString (final Object o) {
try {
final Object fieldObj = field.get (o);
if (fieldObj == null) {
return null;
}
final StringBuffer buffer = new StringBuffer ();
final String[] list = (String[])fieldObj;
for (int i = 0; i < list.length; ++ i) {
if (i > 0) {
buffer.append ("|");
}
buffer.append (list[i]);
}
return buffer.toString ();
} catch (Exception e) {
throw new RuntimeException ("Couldn't convert object to string", e);
}
}
}
private static class GeometryFieldDatabaseColumn extends FieldDatabaseColumn {
GeometryFieldDatabaseColumn(Field field, Column column) {
super(field, column);
}
@Override
public String toString(Object o) {
try {
Object fieldObj = field.get(o);
if(fieldObj == null) {
return null;
} else {
Geometry geometry = (Geometry)fieldObj;
StringWriter stringWriter = new StringWriter();
WKTWriter wktWriter = new WKTWriter(null, null);
wktWriter.writeGeometry(geometry, stringWriter);
String wkt = stringWriter.toString();
logger.trace("wkt: " + wkt);
BinaryWriter writer = new BinaryWriter();
org.postgis.Geometry geom = PGgeometry.geomFromString(wkt);
// TODO: prevent reloading deegree crs database
//ICRS cs = geometry.getCoordinateSystem();
//CRSCodeType crsCode = cs.getCode();
//String code = crsCode.getCode();
//geom.setSrid(Integer.parseInt(code));
geom.setSrid(28992);
return writer.writeHexed(geom);
}
} catch(Exception e) {
throw new RuntimeException("Couldn't convert geometry to string", e);
}
}
}
private static class CodeSpaceDatabaseColumn implements DatabaseColumn {
CodeSpaceDatabaseColumn(Field field, CodeSpaceColumn column) {
this.field = field;
this.column = column;
}
Field field;
CodeSpaceColumn column;
@Override
public String toString(Object o) {
try {
Object fieldObj = field.get(o);
if (fieldObj == null || !(fieldObj instanceof CodeType)) {
return null;
}
CodeType code = (CodeType) fieldObj;
return code.getCodeSpace();
} catch(Exception e) {
throw new RuntimeException("Couldn't convert object to string", e);
}
}
@Override
public String getName() {
return column.name();
}
}
private final DatabaseColumn[] columns;
private final String tableName;
public DBWriterFactory(Class<? extends T> clazz, String... constColumns) {
logger.debug("DBWriter constructed for class: " + clazz.getCanonicalName());
if(constColumns.length % 2 != 0) {
throw new IllegalArgumentException("unevent constColumns parameters");
}
ArrayList<DatabaseColumn> columns = new ArrayList<DatabaseColumn>();
for(int i = 0; i < constColumns.length;) {
String name = constColumns[i++], constValue = constColumns[i++];
logger.debug("Const column added, name: " + name + " constValue: " + constValue);
columns.add(new ConstDatabaseColumn(name, constValue));
}
tableName = findAnnotationsAndAddColumns(columns, clazz);
if(tableName == null) {
throw new IllegalArgumentException("Class doesn't have a table annotation");
}
this.columns = columns.toArray(new DatabaseColumn[columns.size()]);
}
private FieldDatabaseColumn createFieldDatabaseColumn(Field field, Column column) {
Class<?> fieldType = field.getType();
if(org.deegree.geometry.Geometry.class.isAssignableFrom(fieldType)) {
return new GeometryFieldDatabaseColumn(field, column);
} else if (String[].class.isAssignableFrom (fieldType)) {
return new StringArrayFieldDatabaseColumn (field, column);
}
return new FieldDatabaseColumn(field, column);
}
private String findAnnotationsAndAddColumns(ArrayList<DatabaseColumn> columns, Class<?> c) {
String tableName = null;
Table table = c.getAnnotation(Table.class);
if(table != null) {
logger.debug("Table annotation found");
tableName = table.name();
if(tableName.equals("##default")) {
tableName = c.getSimpleName();
}
String schema = table.schema();
if(!schema.equals("##default")) {
tableName = schema + "." + tableName;
}
}
for(Field field : c.getDeclaredFields()) {
logger.debug("Field: " + field.getName());
Column columnAnnotation = field.getAnnotation(Column.class);
if(columnAnnotation != null) {
logger.debug("Column annotation found");
field.setAccessible(true);
columns.add(createFieldDatabaseColumn(field, columnAnnotation));
}
CodeSpaceColumn codeSpaceColumnAnnotation = field.getAnnotation(CodeSpaceColumn.class);
if(codeSpaceColumnAnnotation != null) {
logger.debug("CodeSpace column annotation found");
field.setAccessible(true);
columns.add(new CodeSpaceDatabaseColumn (field, codeSpaceColumnAnnotation));
}
}
Class<?> superclass = c.getSuperclass();
if(superclass != null) {
logger.debug("Superclass: " + superclass.getCanonicalName());
String superTableName = findAnnotationsAndAddColumns(columns, superclass);
if(tableName == null) {
tableName = superTableName;
}
}
return tableName;
}
public String getTableName() {
return tableName;
}
public String getQuery() {
StringBuilder stringBuilder = new StringBuilder("copy ");
stringBuilder.append(tableName);
stringBuilder.append("(");
for(int i = 0; i < columns.length; i++) {
if(i != 0) {
stringBuilder.append(", ");
}
DatabaseColumn columnField = columns[i];
stringBuilder.append(columnField.getName());
}
stringBuilder.append(") from stdin csv");
return stringBuilder.toString();
}
public DBWriter<T> getDBWriter(OutputStream outputStream, String charsetName) throws UnsupportedEncodingException {
return getDBWriter(new OutputStreamWriter(outputStream, charsetName));
}
public DBWriter<T> getDBWriter(Writer writer) {
return getDBWriter(new PrintWriter(writer));
}
public DBWriter<T> getDBWriter(final PrintWriter printWriter) {
return new DBWriter<T>() {
@Override
public void writeObject(T t) {
try {
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < columns.length; i++) {
if(i != 0) {
stringBuilder.append(",");
}
DatabaseColumn column = columns[i];
String columnValue = column.toString(t);
if(columnValue != null) {
stringBuilder.append('"');
columnValue = columnValue.replace("\"", "\"\"");
stringBuilder.append(columnValue);
stringBuilder.append('"');
}
}
String outputLine = stringBuilder.toString();
// logger.debug(outputLine);
printWriter.println(outputLine);
} catch(Exception e) {
logger.debug("Couldn't write object", e);
throw new RuntimeException("Couldn't write object", e);
}
}
@Override
public void close() {
printWriter.close();
}
};
}
}