XmlFactory.java
package tools.jackson.dataformat.xml;
import java.io.*;
import javax.xml.stream.*;
import org.codehaus.stax2.XMLInputFactory2;
import org.codehaus.stax2.io.Stax2ByteArraySource;
import org.codehaus.stax2.io.Stax2CharArraySource;
import tools.jackson.core.*;
import tools.jackson.core.base.TextualTSFactory;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.exc.StreamWriteException;
import tools.jackson.core.io.IOContext;
import tools.jackson.dataformat.xml.deser.FromXmlParser;
import tools.jackson.dataformat.xml.ser.ToXmlGenerator;
import tools.jackson.dataformat.xml.util.StaxUtil;
/**
* Factory used for constructing {@link FromXmlParser} and {@link ToXmlGenerator}
* instances.
*<p>
* Implements {@link TokenStreamFactory} since interface for constructing XML backed
* parsers and generators is quite similar to dealing with JSON.
*
* @author Tatu Saloranta (tatu.saloranta@iki.fi)
*/
public class XmlFactory
extends TextualTSFactory
implements java.io.Serializable
{
private static final long serialVersionUID = 2; // 3.0
/**
* Name used to identify XML format
* (and returned by {@link #getFormatName()}
*/
public final static String FORMAT_NAME_XML = "XML";
/**
* Bitfield (set of flags) of all parser features that are enabled
* by default.
*/
protected final static int DEFAULT_XML_READ_FEATURE_FLAGS = XmlReadFeature.collectDefaults();
/**
* Bitfield (set of flags) of all generator features that are enabled
* by default.
*/
protected final static int DEFAULT_XML_WRITE_FEATURE_FLAGS = XmlWriteFeature.collectDefaults();
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
protected final String _cfgNameForTextElement;
protected final XmlNameProcessor _nameProcessor;
// // Transient just because JDK serializability requires some trickery
protected transient final XMLInputFactory _xmlInputFactory;
protected transient final XMLOutputFactory _xmlOutputFactory;
/*
/**********************************************************************
/* Factory construction, configuration
/**********************************************************************
*/
/**
* Default constructor used to create factory instances.
* Creation of a factory instance is a light-weight operation,
* but it is still a good idea to reuse limited number of
* factory instances (and quite often just a single instance):
* factories are used as context for storing some reused
* processing objects (such as symbol tables parsers use)
* and this reuse only works within context of a single
* factory instance.
*/
public XmlFactory() { this(null, (XMLOutputFactory) null); }
public XmlFactory(XMLInputFactory xmlIn) {
this(xmlIn, null);
}
public XmlFactory(XMLInputFactory xmlIn, XMLOutputFactory xmlOut) {
this(DEFAULT_XML_READ_FEATURE_FLAGS, DEFAULT_XML_WRITE_FEATURE_FLAGS,
xmlIn, xmlOut, XmlNameProcessors.newPassthroughProcessor(),
FromXmlParser.DEFAULT_UNNAMED_TEXT_PROPERTY);
}
protected XmlFactory(int xpFeatures, int xgFeatures,
XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
XmlNameProcessor nameProcessor,
String nameForTextElem)
{
super(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(),
ErrorReportConfiguration.defaults(),
xpFeatures, xgFeatures);
_nameProcessor = nameProcessor;
_cfgNameForTextElement = nameForTextElem;
if (xmlIn == null) {
xmlIn = XmlFactoryBuilder.defaultXmlInputFactory(getClass().getClassLoader());
}
if (xmlOut == null) {
xmlOut = XmlFactoryBuilder.defaultXmlOutputFactory(getClass().getClassLoader());
}
_initFactories(xmlIn, xmlOut);
_xmlInputFactory = xmlIn;
_xmlOutputFactory = xmlOut;
}
/**
* Constructor used by {@link XmlFactoryBuilder} for instantiation.
*/
protected XmlFactory(XmlFactoryBuilder b)
{
super(b);
_cfgNameForTextElement = b.nameForTextElement();
_xmlInputFactory = b.xmlInputFactory();
_xmlOutputFactory = b.xmlOutputFactory();
_nameProcessor = b.xmlNameProcessor();
_initFactories(_xmlInputFactory, _xmlOutputFactory);
}
/**
* Constructor used by {@link XmlFactory#copy()}
*
* @param src Factory to make copy of
*/
protected XmlFactory(XmlFactory src)
{
this(src, src._cfgNameForTextElement);
}
protected XmlFactory(XmlFactory src, String nameForTextElement)
{
super(src);
_cfgNameForTextElement = nameForTextElement;
_xmlInputFactory = src._xmlInputFactory;
_xmlOutputFactory = src._xmlOutputFactory;
_nameProcessor = src._nameProcessor;
}
protected void _initFactories(XMLInputFactory xmlIn, XMLOutputFactory xmlOut)
{
// [dataformat-xml#326]: Better ensure namespaces get built properly, so:
xmlOut.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
// and for parser, force coalescing as well (much simpler to use)
xmlIn.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
}
@Override
public XmlFactoryBuilder rebuild() {
return new XmlFactoryBuilder(this);
}
/**
* Main factory method to use for constructing {@link XmlFactory} instances with
* different configuration.
*/
public static XmlFactoryBuilder builder() {
return new XmlFactoryBuilder();
}
/**
* The builder returned uses default settings more closely
* matching the default configs used in Jackson 2.x versions.
* <p>
* This method is still a work in progress and may not yet fully replicate the
* default settings of Jackson 2.x.
* </p>
*/
public static XmlFactoryBuilder builderWithJackson2Defaults() {
return builder().configureForJackson2();
}
/**
* Note: compared to base implementation by {@link TokenStreamFactory},
* here the copy will actually share underlying XML input and
* output factories, as there is no way to make copies of those.
*/
@Override
public XmlFactory copy() {
return new XmlFactory(this);
}
/**
* Instances are immutable so just return `this`
*/
@Override
public TokenStreamFactory snapshot() {
return this;
}
/**
* "Mutant factory" method used to allow {@code XmlMapper.Builder} to configure
* name used for text elements, instead of requiring defining it via
* {@code XmlFactoryBuilder}.
*/
public XmlFactory withNameForTextElement(String name) {
if (name == null) {
name = "";
}
if (name.equals(_cfgNameForTextElement)) {
return this;
}
return new XmlFactory(this, name);
}
/*
/**********************************************************************
/* Serializable overrides
/**********************************************************************
*/
// Hiding place for JDK-serialization unthawed factories...
protected transient String _jdkXmlInFactory;
// Hiding place for JDK-serialization unthawed factories...
protected transient String _jdkXmlOutFactory;
/**
* Method that we need to override to actually make restoration go
* through constructors etc.
*/
protected Object readResolve() {
if (_jdkXmlInFactory == null) {
throw new IllegalStateException("No XMLInputFactory class name read during JDK deserialization");
}
if (_jdkXmlOutFactory == null) {
throw new IllegalStateException("No XMLOutputFactory class name read during JDK deserialization");
}
final XMLInputFactory inf;
final XMLOutputFactory outf;
try {
inf = (XMLInputFactory) Class.forName(_jdkXmlInFactory).getDeclaredConstructor().newInstance();
outf = (XMLOutputFactory) Class.forName(_jdkXmlOutFactory).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
return new XmlFactory(_formatReadFeatures, _formatWriteFeatures,
inf, outf, _nameProcessor, _cfgNameForTextElement);
}
/**
* In addition to default serialization, which mostly works, need
* to handle case of XML factories, hence override.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
_jdkXmlInFactory = in.readUTF();
_jdkXmlOutFactory = in.readUTF();
}
/**
* In addition to default serialization, which mostly works, need
* to handle case of XML factories, hence override.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeUTF(_xmlInputFactory.getClass().getName());
out.writeUTF(_xmlOutputFactory.getClass().getName());
}
/*
/**********************************************************************
/* Introspection: version, capabilities
/**********************************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
@Override
public boolean canParseAsync() {
return false;
}
/**
* Since 2.4, we do have actual capability for passing char arrays
* efficiently, but unfortunately
* have no working mechanism for recycling buffers. So we have to
* admit that can not make efficient use.
*/
@Override
public boolean canUseCharArrays() { return false; }
/*
/**********************************************************************
/* Format support
/**********************************************************************
*/
/**
* Method that returns short textual id identifying format
* this factory supports.
*<p>
* Note: sub-classes should override this method; default
* implementation will return null for all sub-classes
*/
@Override
public String getFormatName() {
return FORMAT_NAME_XML;
}
@Override
public boolean canUseSchema(FormatSchema schema) {
return false; // no FormatSchema for xml
}
@Override
public Class<XmlReadFeature> getFormatReadFeatureType() {
return XmlReadFeature.class;
}
@Override
public Class<XmlWriteFeature> getFormatWriteFeatureType() {
return XmlWriteFeature.class;
}
@Override
public int getFormatReadFeatures() { return _formatReadFeatures; }
@Override
public int getFormatWriteFeatures() { return _formatWriteFeatures; }
/*
/**********************************************************************
/* Configuration, XML-specific
/**********************************************************************
*/
public String getXMLTextElementName() {
return _cfgNameForTextElement;
}
public XMLInputFactory getXMLInputFactory() {
return _xmlInputFactory;
}
public XMLOutputFactory getXMLOutputFactory() {
return _xmlOutputFactory;
}
/*
/**********************************************************************
/* Overrides of public methods: parsing
/**********************************************************************
*/
/**
* Overridden just to prevent trying to optimize access via char array;
* while nice idea, problem is that we don't have proper hooks to ensure
* that temporary buffer gets recycled; so let's just use StringReader.
*/
@Override
public JsonParser createParser(ObjectReadContext readCtxt, String content) {
IOContext ioCtxt = _createContext(_createContentReference(content), true);
return _createParser(readCtxt, ioCtxt, _decorate(ioCtxt, new StringReader(content)));
}
@Override
protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, DataInput input)
{
return _unsupported();
}
/*
/**********************************************************************
/* Overrides of public methods: generation
/**********************************************************************
*/
@Override
protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt,
IOContext ioCtxt, Writer out)
{
// Only care about features and pretty-printer, for now;
// may add CharacterEscapes in future?
return _toXmlGenerator(writeCtxt, ioCtxt, _createXmlWriter(out));
}
@Override
protected JsonGenerator _createUTF8Generator(ObjectWriteContext writeCtxt,
IOContext ioCtxt, OutputStream out)
{
return _toXmlGenerator(writeCtxt, ioCtxt, _createXmlWriter(out));
}
private final XmlPrettyPrinter _xmlPrettyPrinter(ObjectWriteContext writeCtxt)
{
PrettyPrinter pp = writeCtxt.getPrettyPrinter();
if (pp == null) {
return null;
}
// Ideally should catch earlier, but just in case....
if (!(pp instanceof XmlPrettyPrinter)) {
throw new IllegalStateException("Configured PrettyPrinter not of type `XmlPrettyPrinter` but `"
+pp.getClass().getName()+"`");
}
return (XmlPrettyPrinter) pp;
}
/**
* Overridable method to allow using custom FromXmlParser sub-classes.
*/
protected ToXmlGenerator _toXmlGenerator(ObjectWriteContext writeCtxt, IOContext ioCtxt,
XMLStreamWriter sw) {
return new ToXmlGenerator(writeCtxt, ioCtxt,
writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
writeCtxt.getFormatWriteFeatures(_formatWriteFeatures),
sw,
_xmlPrettyPrinter(writeCtxt),
_nameProcessor);
}
/*
/**********************************************************************
/* Extended public API, mostly for XmlMapper
/**********************************************************************
*/
/**
* Factory method that wraps given {@link XMLStreamReader}, usually to allow
* partial data-binding.
*/
public FromXmlParser createParser(ObjectReadContext readCtxt,
XMLStreamReader sr) throws IOException
{
// note: should NOT move parser if already pointing to START_ELEMENT
if (sr.getEventType() != XMLStreamConstants.START_ELEMENT) {
sr = _initializeXmlReader(sr);
}
// false -> not managed
return _fromXmlParser(readCtxt,
_createContext(_createContentReference(sr), false),
sr);
}
/**
* Factory method that wraps given {@link XMLStreamWriter}, usually to allow
* incremental serialization to compose large output by serializing a sequence
* of individual objects.
*/
public ToXmlGenerator createGenerator(ObjectWriteContext writeCtxt,
XMLStreamWriter sw) throws IOException
{
sw = _initializeXmlWriter(sw);
IOContext ioCtxt = _createContext(_createContentReference(sw), false);
return _toXmlGenerator(writeCtxt, ioCtxt, sw);
}
/*
/**********************************************************************
/* Internal factory method overrides
/**********************************************************************
*/
@Override
protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
InputStream in)
{
XMLStreamReader sr;
try {
sr = _xmlInputFactory.createXMLStreamReader(in);
} catch (XMLStreamException e) {
return StaxUtil.throwAsReadException(e, null);
}
return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr));
}
@Override
protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
Reader r)
{
XMLStreamReader sr;
try {
sr = _xmlInputFactory.createXMLStreamReader(r);
} catch (XMLStreamException e) {
return StaxUtil.throwAsReadException(e, null);
}
return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr));
}
@Override
protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
char[] data, int offset, int len,
boolean recycleBuffer)
{
// !!! TODO: add proper handling of 'recycleBuffer'; currently its handling
// is always same as if 'false' was passed
XMLStreamReader sr;
try {
// 03-Jul-2021, tatu: [dataformat-xml#482] non-Stax2 impls unlikely to
// support so avoid:
if (_xmlInputFactory instanceof XMLInputFactory2) {
sr = _xmlInputFactory.createXMLStreamReader(new Stax2CharArraySource(data, offset, len));
} else {
sr = _xmlInputFactory.createXMLStreamReader(new CharArrayReader(data, offset, len));
}
} catch (XMLStreamException e) {
return StaxUtil.throwAsReadException(e, null);
}
return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr));
}
@Override
protected FromXmlParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
byte[] data, int offset, int len)
{
XMLStreamReader sr;
try {
// 03-Jul-2021, tatu: [dataformat-xml#482] non-Stax2 impls unlikely to
// support so avoid:
if (_xmlInputFactory instanceof XMLInputFactory2) {
sr = _xmlInputFactory.createXMLStreamReader(new Stax2ByteArraySource(data, offset, len));
} else {
sr = _xmlInputFactory.createXMLStreamReader(new ByteArrayInputStream(data, offset, len));
}
} catch (XMLStreamException e) {
return StaxUtil.throwAsReadException(e, null);
}
return _fromXmlParser(readCtxt, ioCtxt, _initializeXmlReader(sr));
}
/**
* Overridable method to allow using custom FromXmlParser sub-classes.
*/
protected FromXmlParser _fromXmlParser(ObjectReadContext readCtxt, IOContext ioCtxt,
XMLStreamReader sr) {
return new FromXmlParser(readCtxt, ioCtxt,
readCtxt.getStreamReadFeatures(_streamReadFeatures),
readCtxt.getFormatReadFeatures(_formatReadFeatures),
sr, _nameProcessor, _cfgNameForTextElement);
}
/*
/**********************************************************************
/* Internal factory methods, XML-specific
/**********************************************************************
*/
protected XMLStreamWriter _createXmlWriter(OutputStream out)
{
XMLStreamWriter sw;
try {
sw = _xmlOutputFactory.createXMLStreamWriter(out, "UTF-8");
} catch (XMLStreamException e) {
return StaxUtil.throwAsWriteException(e, null);
}
return _initializeXmlWriter(sw);
}
protected XMLStreamWriter _createXmlWriter(Writer w)
{
XMLStreamWriter sw;
try {
sw = _xmlOutputFactory.createXMLStreamWriter(w);
} catch (XMLStreamException e) {
return StaxUtil.throwAsWriteException(e, null);
}
return _initializeXmlWriter(sw);
}
protected final XMLStreamWriter _initializeXmlWriter(XMLStreamWriter sw)
{
// And just for Sun Stax parser (JDK default), seems that we better define default namespace
// (Woodstox doesn't care) -- otherwise it'll add unnecessary odd declaration
try {
sw.setDefaultNamespace("");
} catch (Exception e) {
throw new StreamWriteException(null, e.getMessage(), e);
}
return sw;
}
protected final XMLStreamReader _initializeXmlReader(XMLStreamReader sr)
{
try {
// for now, nothing to do... except let's find the root element
while (sr.next() != XMLStreamConstants.START_ELEMENT) {
;
}
// [dataformat-xml#350]: Xerces-backed impl throws non-XMLStreamException so:
} catch (Exception e) {
throw new StreamReadException(null, e.getMessage(), e);
}
return sr;
}
}