/*
* Copyright 2007 - 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.jailer.xml;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import net.sf.jailer.configuration.DBMS;
import net.sf.jailer.database.Session;
import net.sf.jailer.datamodel.AggregationSchema;
import net.sf.jailer.datamodel.Association;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.util.Base64;
import net.sf.jailer.util.CellContentConverter;
import net.sf.jailer.util.SqlUtil;
/**
* Writes rows into XML file.
*
* @author Ralf Wisser
*/
public class XmlRowWriter {
/**
* SAX transformer.
*/
private final TransformerHandler transformerHandler;
/**
* Root tag name.
*/
private final String rootTag;
/**
* Pattern for dates.
*/
final SimpleDateFormat datePattern;
/**
* Pattern for time-stamps.
*/
final SimpleDateFormat timestampPattern;
/**
* Current level of if-blocks to skip.
*/
private int ifLevel = 0;
/**
* Type caches per table (String key).
*/
private final Map<Table, Map<String, Integer>> typeCachesForStringKey = new HashMap<Table, Map<String,Integer>>();
/**
* Constructor.
*
* @param out output stream to write the xml into
* @param commentHeader comment at top of document
* @param rootTag root tag name
* @param datePattern pattern for dates
* @param timestampPattern pattern for time-stamps
*/
public XmlRowWriter(OutputStream out, String commentHeader, String rootTag, String datePattern, String timestampPattern, Charset charset) throws SAXException, TransformerConfigurationException {
this.rootTag = rootTag;
this.datePattern = new SimpleDateFormat(datePattern);
this.timestampPattern = new SimpleDateFormat(timestampPattern);
StreamResult streamResult = new StreamResult(new OutputStreamWriter(out, charset));
transformerHandler = XmlUtil.createTransformerHandler(commentHeader, rootTag, streamResult, charset);
}
/**
* Closes the writer.
*/
public void close() throws SAXException {
if (rootTag.length() > 0) {
transformerHandler.endElement("", "", rootTag);
}
transformerHandler.endDocument();
}
/**
* Writes start element for a list of rows.
*
* @param association association describing the list
*/
public void startList(Association association) throws SAXException {
if (association != null && association.getAggregationSchema() == AggregationSchema.EXPLICIT_LIST) {
if (ifLevel == 0) {
transformerHandler.startElement(null, null, association.getAggregationTagName(), null);
}
}
}
/**
* Writes end element for a list of rows.
*
* @param association association describing the list
*/
public void endList(Association association) throws SAXException {
if (association != null && association.getAggregationSchema() == AggregationSchema.EXPLICIT_LIST) {
if (ifLevel == 0) {
transformerHandler.endElement(null, null, association.getAggregationTagName());
}
}
}
/**
* Visits nodes of mapping templates and writes data as XML.
*/
public abstract class XmlWritingNodeVisitor implements NodeVisitor {
/**
* To read rows from.
*/
private final ResultSet resultSet;
/**
* Meta data.
*/
private final ResultSetMetaData resultSetMetaData;
/**
* The table from which the data comes.
*/
private final Table table;
/**
* The association which is currently resolved.
*/
private final Association association;
/**
* The DB session.
*/
private final Session session;
/**
* Next number of column to write out.
*/
private int nr = 0;
/**
* {@link CellContentConverter}.
*/
protected final CellContentConverter cellContentConverter;
/**
* Constructor.
*
* @param resultSet to read rows from
*/
public XmlWritingNodeVisitor(ResultSet resultSet, ResultSetMetaData resultSetMetaData, Table table, Association association, Session session) {
this.resultSet = resultSet;
this.resultSetMetaData = resultSetMetaData;
this.table = table;
this.association = association;
this.session = session;
this.cellContentConverter = new CellContentConverter(resultSetMetaData, session, session.dbms);
}
/**
* Gets text to write out. If it starts with "SQL:", write out next column value.
*
* @param text the text
* @param returnNull if <code>true</code>, return null instead of empty string if sql-result is null
* @return the xml to write out
*/
private String toXml(String text, boolean returnNull) {
if (text != null && text.startsWith(XmlUtil.SQL_PREFIX)) {
String columnName = "C" + nr++;
int type;
try {
Map<String, Integer> typeCache = typeCachesForStringKey.get(table);
if (typeCache == null) {
typeCache = new HashMap<String, Integer>();
typeCachesForStringKey.put(table, typeCache);
}
type = SqlUtil.getColumnType(resultSet, resultSetMetaData, columnName, typeCache);
if ((type == Types.BLOB || type == Types.CLOB|| type == Types.NCLOB) && !DBMS.SQLITE.equals(session.dbms)) {
Object object = resultSet.getObject(columnName);
if (returnNull && (object == null || resultSet.wasNull())) {
return null;
}
if (object instanceof Blob) {
Blob blob = (Blob) object;
byte[] blobValue = blob.getBytes(1, (int) blob.length());
return Base64.encodeBytes(blobValue);
}
if (object instanceof Clob) {
Clob clobValue = (Clob) object;
int length = (int) clobValue.length();
if (length > 0) {
return clobValue.getSubString(1, length);
}
return "";
}
} else {
Object o = cellContentConverter.getObject(resultSet, columnName);
if (returnNull && (o == null || resultSet.wasNull())) {
return null;
}
if (o != null) {
String value;
if (o instanceof Timestamp) {
value = timestampPattern.format((Timestamp) o);
} else if (o instanceof Date) {
value = datePattern.format((Date) o);
} else {
value = o.toString();
}
return value;
}
}
return "";
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return text;
}
public void visitComment(String comment) {
try {
if (ifLevel == 0) {
transformerHandler.comment(comment.toCharArray(), 0, comment.length());
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
public void visitElementEnd(String elementName, boolean isRoot) {
if (ifLevel > 0) {
--ifLevel;
} else {
try {
if (!isRoot || association == null || association.getAggregationSchema() != AggregationSchema.FLAT) {
String tagName = isRoot && association != null && association.getAggregationSchema() != AggregationSchema.EXPLICIT_LIST? association.getAggregationTagName() : elementName;
transformerHandler.endElement("", "", tagName);
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
}
String jailerNamespaceDeclaration = "xmlns:" + XmlUtil.NS_PREFIX;
public void visitElementStart(String elementName, boolean isRoot, String[] aNames, String[] aValues) {
if (ifLevel > 0) {
++ifLevel;
}
try {
AttributesImpl attr = null;
boolean cond = true;
if (aNames.length > 0) {
attr = new AttributesImpl();
for (int i = 0; i < aNames.length; ++i) {
if (aNames[i].equals(XmlUtil.NS_PREFIX + ":if-not-null")) {
if (toXml(aValues[i], true) == null) {
if (ifLevel == 0) {
cond = false;
}
}
} else if (aNames[i].equals(XmlUtil.NS_PREFIX + ":if-null")) {
if (toXml(aValues[i], true) != null) {
if (ifLevel == 0) {
cond = false;
}
}
} else if (!aNames[i].equals(jailerNamespaceDeclaration)) {
attr.addAttribute("", "", aNames[i], "CDATA", toXml(aValues[i], false));
}
}
}
if (ifLevel == 0) {
if (!cond) {
++ifLevel;
} else {
if (!isRoot || association == null || association.getAggregationSchema() != AggregationSchema.FLAT) {
String tagName = isRoot && association != null && association.getAggregationSchema() != AggregationSchema.EXPLICIT_LIST? association.getAggregationTagName() : elementName;
transformerHandler.startElement("", "", tagName, attr);
}
}
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
public void visitText(String text) {
text = toXml(text, false);
try {
if (ifLevel == 0) {
transformerHandler.characters(text.toCharArray(), 0, text.length());
}
} catch (SAXException e) {
throw new RuntimeException(e);
}
}
};
}