package org.exist.mongodb.shared;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSFile;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import javax.xml.transform.OutputKeys;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.exist.memtree.MemTreeBuilder;
import org.exist.memtree.NodeImpl;
import org.exist.storage.serializers.Serializer;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.BinaryValue;
import org.exist.xquery.value.DateTimeValue;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Type;
import org.xml.sax.SAXException;
/**
*
* @author wessels
*/
public class ContentSerializer {
private final static Logger LOG = Logger.getLogger(ContentSerializer.class);
/**
* Stream content of item to output stream
*
* @param item The Item
* @param context The XQUery context
* @param os The output stream
* @throws XPathException Thrown when an something unexpected happened
* @throws IOException An error occurred during serialization
*/
public static void serialize(Item item, XQueryContext context, OutputStream os) throws XPathException, IOException {
switch (item.getType()) {
case Type.JAVA_OBJECT:
streamJavaObject(item, os);
break;
case Type.ANY_URI:
streamAnyURI(item, os);
break;
case Type.ELEMENT:
case Type.DOCUMENT:
streamElement(context, item, os);
break;
case Type.BASE64_BINARY:
case Type.HEX_BINARY:
streamBase64Binary(item, os);
break;
case Type.TEXT:
case Type.STRING:
streamText(item, os);
break;
default:
LOG.error("Wrong item type " + Type.getTypeName(item.getType()));
throw new XPathException("wrong item type " + Type.getTypeName(item.getType()));
}
}
private static void streamText(Item item, OutputStream os) throws IOException, XPathException {
LOG.debug("Streaming text");
IOUtils.write(item.getStringValue(), os, "UTF-8");
}
private static void streamBase64Binary(Item item, OutputStream os) throws IOException {
LOG.debug("Streaming base64 binary");
final BinaryValue binary = (BinaryValue) item;
binary.streamBinaryTo(os);
}
private static void streamElement(XQueryContext context, Item item, OutputStream os) throws IOException {
LOG.debug("Streaming element or document node");
final Serializer serializer = context.getBroker().newSerializer();
final NodeValue node = (NodeValue) item;
// Setup serialization options
// TODO: get global serialization options
final Properties outputProperties = new Properties();
outputProperties.setProperty(OutputKeys.INDENT, "yes");
outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
SerializerPool serializerPool = SerializerPool.getInstance();
LOG.debug("Serializing started.");
final SAXSerializer sax = (SAXSerializer) serializerPool.borrowObject(SAXSerializer.class);
try {
final String encoding = "UTF-8";
try (Writer writer = new OutputStreamWriter(os, encoding)) {
sax.setOutput(writer, outputProperties);
serializer.reset();
serializer.setProperties(outputProperties);
serializer.setSAXHandlers(sax, sax);
sax.startDocument();
serializer.toSAX(node);
sax.endDocument();
}
} catch (final IOException | SAXException e) {
final String txt = "A problem occurred while serializing the node set: " + e.getMessage();
LOG.debug(txt, e);
throw new IOException(txt, e);
} finally {
LOG.debug("Serializing done.");
serializerPool.returnObject(sax);
}
}
private static void streamAnyURI(Item item, OutputStream os) throws IOException, XPathException {
LOG.debug("Streaming xs:anyURI");
// anyURI provided
String url = item.getStringValue();
// Fix URL
if (url.startsWith("/")) {
url = "xmldb:exist://" + url;
}
final InputStream is = new BufferedInputStream(new URL(url).openStream());
IOUtils.copyLarge(is, os);
IOUtils.closeQuietly(is);
}
private static void streamJavaObject(Item item, OutputStream os) throws XPathException, IOException {
LOG.debug("Streaming Java object");
final Object obj = ((JavaObjectValue) item).getObject();
if (!(obj instanceof File)) {
throw new XPathException("Passed java object should be a File object");
}
final File inputFile = (File) obj;
final InputStream is = new BufferedInputStream(new FileInputStream(inputFile));
IOUtils.copyLarge(is, os);
IOUtils.closeQuietly(is);
}
/**
* Get simple report for the Gridfs file
*
* @param gfsFile The GridFS fle
* @return in-memory structure describing the file
*/
public static NodeImpl getReport(GridFSFile gfsFile) {
MemTreeBuilder builder = new MemTreeBuilder();
builder.startDocument();
int nodeNr = addGridFSFileEntry(builder, gfsFile);
// return result
return builder.getDocument().getNode(nodeNr);
}
/**
* Get list of documents in gridFS
*
* @param gfs GridFS object
* @return in-memory structure describing all documents
*/
public static NodeImpl getDocuments(GridFS gfs) {
MemTreeBuilder builder = new MemTreeBuilder();
// Start document
builder.startDocument();
int nodeNr = builder.startElement("", "GridFSFiles", "GridFSFiles", null);
// Iterate over all files, write report
DBCursor cursor = gfs.getFileList();
while (cursor.hasNext()) {
DBObject next = cursor.next();
if (next instanceof GridFSFile) {
addGridFSFileEntry(builder, (GridFSFile) next);
}
}
// Finish document
builder.endElement();
builder.endDocument();
// Return result
return builder.getDocument().getNode(nodeNr);
}
/**
* Add element describing GridFSFile to in-memory structure
*/
static int addGridFSFileEntry(final MemTreeBuilder builder, final GridFSFile gfsFile) {
// start root element
int nodeNr = builder.startElement("", "GridFSFile", "GridFSFile", null);
// Some identities
addElementValue(builder, "id", "" + gfsFile.getId());
addElementValue(builder, "filename", gfsFile.getFilename());
List<String> aliases = gfsFile.getAliases();
if (aliases != null) {
for (String alias : gfsFile.getAliases()) {
addElementValue(builder, "alias", alias);
}
}
// mimetype
addElementValue(builder, "contentType", gfsFile.getContentType());
// sizes
addElementValue(builder, "length", "" + gfsFile.getLength());
addElementValue(builder, "chunkSize", "" + gfsFile.getChunkSize());
addElementValue(builder, "numberOfChunks", "" + gfsFile.numChunks());
// more meta data
try {
addElementValue(builder, "uploadDate", "" + (new DateTimeValue(gfsFile.getUploadDate()).getStringValue()));
} catch (XPathException ex) {
LOG.error("Error adding upload date. " + ex.getMessage());
}
addElementValue(builder, "md5", gfsFile.getMD5());
DBObject metaData = gfsFile.getMetaData();
if (metaData != null && !metaData.keySet().isEmpty()) {
builder.startElement("", "metaData", "metaData", null);
for (String key : metaData.keySet()) {
String value = metaData.get(key).toString();
addElementValue(builder, key, value);
}
builder.endElement();
}
// finish root element
builder.endElement();
return nodeNr;
}
/**
* Add simple element with value to XML structure.
*/
static void addElementValue(final MemTreeBuilder builder, final String elementName, final String value) {
if (StringUtils.isNotBlank(value) && StringUtils.isNotBlank(elementName)) {
builder.startElement("", elementName, elementName, null);
builder.characters(value);
builder.endElement();
} else {
LOG.debug(String.format("Skipping element '%s' with value '%s'.", elementName, value));
}
}
}