package net.sf.jailer.liquibase;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import javax.xml.transform.sax.TransformerHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import net.sf.jailer.ExecutionContext;
import net.sf.jailer.database.Session.AbstractResultSetReader;
import net.sf.jailer.database.Session.ResultSetReader;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.entitygraph.EntityGraph;
import net.sf.jailer.subsetting.TransformerFactory;
public class LiquibaseXMLTransformer extends AbstractResultSetReader {
private static final String VALUE_DATE = "valueDate";
private static final String VALUE_NUMERIC = "valueNumeric";
private static final String VALUE_CLOBFILE = "valueClobFile";
private static final String VALUE_NCLOBFILE = "valueNClobFile";
private static final String VALUE_BLOBFILE = "valueBlobFile";
private static final String VALUE = "value";
private final String rowElementName;
private final TransformerHandler transformerHandler;
private final File scriptFile;
private final EntityGraph entityGraph;
/**
* Pattern for dates.
*/
private final SimpleDateFormat datePattern;
/**
* Pattern for dates.
*/
private final SimpleDateFormat timePattern;
/**
* Pattern for time-stamps.
*/
private final SimpleDateFormat timestampPattern;
/**
* The execution context.
*/
private final ExecutionContext executionContext;
/**
* Factory.
*/
public static class Factory implements TransformerFactory {
private final TransformerHandler transformerHandler;
private final EntityGraph entityGraph;
private final String scriptFile;
private final DatabaseMetaData metaData;
private final String datePattern;
private final String timePattern;
private final String timestampPattern;
/**
* The execution context.
*/
private final ExecutionContext executionContext;
/**
* Constructor.
*
* @param table
* the table to read from
* @param transformerHandler
* to write the XML into
* @param metaData
* database meta data
*/
public Factory(TransformerHandler transformerHandler, DatabaseMetaData metaData,
EntityGraph entityGraph, String scriptFile, String datePattern, String timePattern, String timestampPattern, ExecutionContext executionContext) {
this.executionContext = executionContext;
this.transformerHandler = transformerHandler;
this.entityGraph = entityGraph;
this.scriptFile = scriptFile;
this.metaData = metaData;
this.datePattern = datePattern;
this.timePattern = timePattern;
this.timestampPattern = timestampPattern;
}
/**
* Creates transformer (as {@link ResultSetReader} which
* transforms rows of a given table into an external representation.
*
* @param table the table
* @return a transformer
*/
@Override
public ResultSetReader create(Table table) throws SQLException {
return new LiquibaseXMLTransformer(table, transformerHandler, metaData, entityGraph, scriptFile, datePattern, timePattern, timestampPattern, executionContext);
}
}
private LiquibaseXMLTransformer(Table table, TransformerHandler transformerHandler, DatabaseMetaData metaData,
EntityGraph entityGraph, String scriptFile, String datePattern, String timePattern, String timestampPattern, ExecutionContext executionContext) throws SQLException {
this.executionContext = executionContext;
this.transformerHandler = transformerHandler;
this.entityGraph = entityGraph;
this.rowElementName = qualifiedTableName(table);
this.scriptFile = new File(scriptFile);
this.datePattern = new SimpleDateFormat(datePattern);
this.timePattern = new SimpleDateFormat(timePattern);
this.timestampPattern = new SimpleDateFormat(timestampPattern);
}
private String qualifiedTableName(Table t) {
String schema = t.getOriginalSchema("");
String mappedSchema = executionContext.getSchemaMapping().get(schema);
if (mappedSchema != null) {
schema = mappedSchema;
}
if (schema.length() == 0) {
return unquote(t.getUnqualifiedName());
}
return unquote(schema) + "." + unquote(t.getUnqualifiedName());
}
private String unquote(String name) {
if (!name.isEmpty()) {
char fc = name.charAt(0);
if (!Character.isLetterOrDigit(fc) && fc != '_') {
String fcStr = Character.toString(fc);
if (name.startsWith(fcStr) && name.endsWith(fcStr)) {
name = name.substring(1, name.length() - 1);
}
}
}
return name;
}
public void readCurrentRow(ResultSet singleRow) throws SQLException {
int columnCount = getMetaData(singleRow).getColumnCount();
try {
AttributesImpl attrinsert = new AttributesImpl();
attrinsert.addAttribute("", "", "tableName", "", rowElementName);
synchronized (transformerHandler) {
transformerHandler.startElement("", "", "insert", attrinsert);
for (int i = 1; i <= columnCount; i++) {
AttributesImpl attrcolumn = getColumnAttributes(singleRow, i);
if(attrcolumn.getValue("name")!=null){
transformerHandler.startElement("", "", "column", attrcolumn);
transformerHandler.endElement("", "", "column");
}
}
transformerHandler.endElement("", "", "insert");
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
private AttributesImpl getColumnAttributes(ResultSet singleRow, int columncount) throws SQLException {
int count;
AttributesImpl attrcolumn = new AttributesImpl();
String columnname = getMetaData(singleRow).getColumnName(columncount);
Integer columnType = getMetaData(singleRow).getColumnType(columncount);
Integer precision = getMetaData(singleRow).getPrecision(columncount);
singleRow.getObject(columncount);
// special handling for sql server
if (columnType == Types.VARCHAR && precision.equals(Integer.MAX_VALUE)) {
columnType = Types.CLOB;
}
if (!singleRow.wasNull()){
switch(columnType){
case Types.CLOB:
count = entityGraph.incLobCount();
Clob clobValue = singleRow.getClob(columncount);
int lengthc = (int) clobValue.length();
// liquibase treats an empty clob file as though no file exists
if (lengthc > 0) {
String clobcontent=clobValue.getSubString(1, lengthc);
String clobname=lobName(count, ".txt");
attrcolumn=createAttribute(columnname,VALUE_CLOBFILE,clobname);
writeClob(clobcontent,clobname);
} else {
attrcolumn=createAttribute(columnname,VALUE_CLOBFILE,null);
}
break;
case Types.NCLOB:
count = entityGraph.incLobCount();
NClob nclobValue = singleRow.getNClob(columncount);
int lengthnc = (int) nclobValue.length();
// liquibase treats an empty clob file as though no file exists
if (lengthnc > 0) {
String nclobcontent=nclobValue.getSubString(1, lengthnc);
String nclobname=lobName(count, ".txt");
attrcolumn=createAttribute(columnname,VALUE_NCLOBFILE,nclobname);
writeClob(nclobcontent,nclobname);
} else {
attrcolumn=createAttribute(columnname,VALUE_NCLOBFILE,null);
}
break;
case Types.BLOB:
case Types.VARBINARY:
count = entityGraph.incLobCount();
Blob blob = singleRow.getBlob(columncount);
int lengthb = (int) blob.length();
// liquibase treats an empty blob file as though no file exists
if (lengthb > 0) {
byte[] blobcontent = blob.getBytes(1, lengthb);
String blobname=lobName(count, ".bin");
attrcolumn=createAttribute(columnname,VALUE_BLOBFILE,blobname);
writeBlob(blobcontent, blobname);
} else {
attrcolumn=createAttribute(columnname,VALUE_BLOBFILE,null);
}
break;
case Types.TIMESTAMP:
Timestamp ts = singleRow.getTimestamp(columncount);
String datetimestamp = timestampPattern.format(ts);
attrcolumn=createAttribute(columnname,VALUE_DATE,datetimestamp);
break;
case Types.TIME:
Timestamp t = singleRow.getTimestamp(columncount);
String datetime = timePattern.format(t);
attrcolumn=createAttribute(columnname,VALUE_DATE,datetime);
break;
case Types.DATE:
Timestamp d = singleRow.getTimestamp(columncount);
String datedate = datePattern.format(d);
attrcolumn=createAttribute(columnname,VALUE_DATE,datedate);
break;
case Types.NUMERIC:
case Types.DECIMAL:
BigDecimal bigdecimalvalue= singleRow.getBigDecimal(columncount);
attrcolumn=createAttribute(columnname,VALUE_NUMERIC,bigdecimalvalue.toString());
break;
case Types.INTEGER:
case Types.SMALLINT:
Integer integervalue= singleRow.getInt(columncount);
attrcolumn=createAttribute(columnname,VALUE_NUMERIC,integervalue.toString());
break;
case Types.FLOAT:
Float floatvalue= singleRow.getFloat(columncount);
attrcolumn=createAttribute(columnname,VALUE_NUMERIC,floatvalue.toString());
break;
case Types.DOUBLE:
Double doublevalue= singleRow.getDouble(columncount);
attrcolumn=createAttribute(columnname,VALUE_NUMERIC,doublevalue.toString());
break;
case Types.ROWID:
RowId rowidvalue= singleRow.getRowId(columncount);
attrcolumn=createAttribute(columnname,VALUE,rowidvalue.toString());
break;
case Types.VARCHAR:
case Types.CHAR:
case Types.LONGVARCHAR:
case Types.NVARCHAR:
case Types.NCHAR:
case Types.LONGNVARCHAR:
attrcolumn=createAttribute(columnname,VALUE,singleRow.getString(columncount));
break;
case Types.BOOLEAN:
boolean booleanvalue= singleRow.getBoolean(columncount);
attrcolumn=createAttribute(columnname,VALUE,Boolean.toString(booleanvalue));
break;
case Types.ARRAY:
Array arrayvalue= singleRow.getArray(columncount);
attrcolumn=createAttribute(columnname,VALUE,arrayvalue.toString());
break;
case Types.REF:
Ref refvalue= singleRow.getRef(columncount);
attrcolumn=createAttribute(columnname,VALUE,refvalue.toString());
break;
default:
attrcolumn=createAttribute(columnname,VALUE_NUMERIC,singleRow.getString(columncount));
break;
// throw new RuntimeException("Falscher Datentyp: "+singleRow.getMetaData().getColumnTypeName(columncount));
}
}else{
attrcolumn=createAttribute(columnname,null,null);
}
return attrcolumn;
}
private String lobName(int count, String suffix) {
String path = "";
final int MAX_FILES_PER_FOLDER = 100;
long i = 1;
for (;;) {
i *= MAX_FILES_PER_FOLDER;
long folderNumber = (count / i) * i;
if (folderNumber > 0) {
path = File.separator + folderNumber + path;
} else {
break;
}
}
return scriptFile.getName() + ".lob" + File.separator
+ rowElementName.toLowerCase() + path + File.separator
+ count + suffix;
}
private AttributesImpl createAttribute(String columnname, String valuetype,String value) {
AttributesImpl attrcolumn = new AttributesImpl();
attrcolumn.addAttribute("", "", "name", "", columnname);
if((valuetype!=null) && (value!=null)){
attrcolumn.addAttribute("", "", valuetype, "", value);
}
return attrcolumn;
}
private void writeClob(String clobcontent, String clobname) {
File lobFile = getLobFile(clobname);
try {
FileOutputStream fos = new FileOutputStream(lobFile);
try {
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fos, "UTF8"));
try {
out.write(clobcontent);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
private void writeBlob(byte[] blobcontent, String blobname) {
File lobFile = getLobFile(blobname);
try {
FileOutputStream fos = new FileOutputStream(lobFile);
try {
fos.write(blobcontent);
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
private File getLobFile(String lobname) {
File lobFile = new File(scriptFile.getParent(), lobname);
File parent = lobFile.getParentFile();
if (parent != null) {
parent.mkdirs(); //ensure that the child table name dir exists
}
return lobFile;
}
public void close() {}
}