package org.xbib.elasticsearch.common.xcontent.xml;
import com.ctc.wstx.stax.WstxInputFactory;
import com.ctc.wstx.stax.WstxOutputFactory;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.FastStringReader;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentType;
import org.xbib.elasticsearch.common.xcontent.XmlXContentBuilder;
import org.elasticsearch.common.xcontent.XContentGenerator;
import org.elasticsearch.common.xcontent.XContentParser;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
/**
* A XML based content implementation using Jackson XML dataformat
*
*/
public class XmlXContent implements XContent {
/*
* This is a bit tricky. We want Woodstox. Woodstox is stax2-api, and can indent XML. We package it in the plugin.
* But how can we force to load Woodstox instead of internal com.sun JDK streaming XML parser?
*
* 1) Elasticsearch runs the JVM in a "parent" classpath and a plugin in a separate "child" classpath.
* 2) the Java XML API under XMLInputFactory.newInstance() / XMLOutputFactory.newInstance()
* uses a mixture of META-INF/services and system property configuration.
* Both work only on Elasticsearch parent classloader. They can not use resources inside a plugin.
* So, XML factory lookup does only work on the ES "lib" folder.
* 3) com.fasterxml.jackson.dataformat.xml.XmlFactory creates internal XMLStreamWriter instances to create an
* indenting XML stream in the generator. We need XMLStreamWriter2 for indentation.
* But XmlFactory uses the ES parent class loader because it's JDK code in javax.xml.stream that tries to load it.
* Therefore, XML indentation crashes with:
* java.lang.UnsupportedOperationException: Not implemented
* at org.codehaus.stax2.ri.Stax2WriterAdapter.writeRaw(Stax2WriterAdapter.java:380)
* because it only sees the Stax API implementation of the JDK, not Woodstox of the plugin.
* 4) We can work around it by:
* a) setting system properties in this static initializer (as early as possible)
* b) use direct initialization of WstxInputFactory / WstxOutputFactory, no javax.xml.stream class loading
*/
static {
System.setProperty("javax.xml.stream.XMLInputFactory", WstxInputFactory.class.getName());
System.setProperty("javax.xml.stream.XMLOutputFactory", WstxOutputFactory.class.getName());
XMLInputFactory inputFactory = new WstxInputFactory(); // do not use XMLInputFactory.newInstance()
inputFactory.setProperty("javax.xml.stream.isNamespaceAware", Boolean.TRUE);
inputFactory.setProperty("javax.xml.stream.isValidating", Boolean.FALSE);
inputFactory.setProperty("javax.xml.stream.isCoalescing", Boolean.TRUE);
inputFactory.setProperty("javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE);
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", Boolean.FALSE);
XMLOutputFactory outputFactory = new WstxOutputFactory(); // do not use XMLOutputFactory.newInstance()
outputFactory.setProperty("javax.xml.stream.isRepairingNamespaces", Boolean.TRUE);
xmlFactory = new XmlFactory(inputFactory, outputFactory);
xmlXContent = new XmlXContent();
}
public static XmlXContentBuilder contentBuilder() throws IOException {
return XmlXContentBuilder.builder(xmlXContent);
}
public static XmlXContentBuilder contentBuilder(XmlXParams params) throws IOException {
XmlXContentBuilder builder = XmlXContentBuilder.builder(xmlXContent);
((XmlXContentGenerator) builder.generator()).setParams(params);
return builder;
}
private final static XmlFactory xmlFactory;
private final static XmlXContent xmlXContent;
private XmlXContent() {
}
@Override
public XContentType type() {
//return XmlXContentType.XML;
return null;
}
public static XmlXContent xmlXContent() {
return xmlXContent;
}
protected static XmlFactory xmlFactory() {
return xmlFactory;
}
@Override
public byte streamSeparator() {
throw new UnsupportedOperationException("xml does not support stream parsing...");
}
@Override
public XContentGenerator createGenerator(OutputStream os) throws IOException {
return new XmlXContentGenerator(xmlFactory.createGenerator(os, JsonEncoding.UTF8));
}
@Override
public XContentGenerator createGenerator(OutputStream os, String[] filters) throws IOException {
// ignore filters (for now)
return new XmlXContentGenerator(xmlFactory.createGenerator(os, JsonEncoding.UTF8));
}
@Override
public XContentParser createParser(String content) throws IOException {
return new XmlXContentParser(xmlFactory.createParser(new FastStringReader(content)));
}
@Override
public XContentParser createParser(InputStream is) throws IOException {
return new XmlXContentParser(xmlFactory.createParser(is));
}
@Override
public XContentParser createParser(byte[] data) throws IOException {
return new XmlXContentParser(xmlFactory.createParser(data));
}
@Override
public XContentParser createParser(byte[] data, int offset, int length) throws IOException {
return new XmlXContentParser(xmlFactory.createParser(data, offset, length));
}
@Override
public XContentParser createParser(BytesReference bytes) throws IOException {
if (bytes.hasArray()) {
return createParser(bytes.array(), bytes.arrayOffset(), bytes.length());
}
return createParser(bytes.streamInput());
}
@Override
public XContentParser createParser(Reader reader) throws IOException {
return new XmlXContentParser(xmlFactory.createParser(reader));
}
}