1 package com.fasterxml.jackson.dataformat.xml;
2
3 import java.io.*;
4
5 import javax.xml.stream.*;
6
7 import org.codehaus.stax2.io.Stax2ByteArraySource;
8 import org.codehaus.stax2.io.Stax2CharArraySource;
9
10 import com.fasterxml.jackson.core.*;
11 import com.fasterxml.jackson.core.format.InputAccessor;
12 import com.fasterxml.jackson.core.format.MatchStrength;
13 import com.fasterxml.jackson.core.io.IOContext;
14 import com.fasterxml.jackson.core.util.VersionUtil;
15
16 import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
17 import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
18 import com.fasterxml.jackson.dataformat.xml.util.StaxUtil;
19
20
29 public class XmlFactory extends JsonFactory
30 {
31 private static final long serialVersionUID = 1;
32
33
37 public final static String FORMAT_NAME_XML = "XML";
38
39
43 final static int DEFAULT_XML_PARSER_FEATURE_FLAGS = FromXmlParser.Feature.collectDefaults();
44
45
49 final static int DEFAULT_XML_GENERATOR_FEATURE_FLAGS = ToXmlGenerator.Feature.collectDefaults();
50
51
56
57 protected int _xmlParserFeatures;
58
59 protected int _xmlGeneratorFeatures;
60
61
62 protected transient XMLInputFactory _xmlInputFactory;
63
64 protected transient XMLOutputFactory _xmlOutputFactory;
65
66 protected String _cfgNameForTextElement;
67
68
73
74
84 public XmlFactory() { this(null, null, null); }
85
86 public XmlFactory(ObjectCodec oc) {
87 this(oc, null, null);
88 }
89
90 public XmlFactory(XMLInputFactory xmlIn) {
91 this(null, xmlIn, null);
92 }
93
94 public XmlFactory(XMLInputFactory xmlIn, XMLOutputFactory xmlOut) {
95 this(null, xmlIn, xmlOut);
96 }
97
98 public XmlFactory(ObjectCodec oc, XMLInputFactory xmlIn, XMLOutputFactory xmlOut)
99 {
100 this(oc, DEFAULT_XML_PARSER_FEATURE_FLAGS, DEFAULT_XML_GENERATOR_FEATURE_FLAGS,
101 xmlIn, xmlOut, null);
102 }
103
104 protected XmlFactory(ObjectCodec oc, int xpFeatures, int xgFeatures,
105 XMLInputFactory xmlIn, XMLOutputFactory xmlOut,
106 String nameForTextElem)
107 {
108 super(oc);
109 _xmlParserFeatures = xpFeatures;
110 _xmlGeneratorFeatures = xgFeatures;
111 _cfgNameForTextElement = nameForTextElem;
112 if (xmlIn == null) {
113 xmlIn = XMLInputFactory.newInstance();
114
115 xmlIn.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
116
117 xmlIn.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
118 }
119 if (xmlOut == null) {
120 xmlOut = XMLOutputFactory.newInstance();
121 }
122 _initFactories(xmlIn, xmlOut);
123 _xmlInputFactory = xmlIn;
124 _xmlOutputFactory = xmlOut;
125 }
126
127
130 protected XmlFactory(XmlFactory src, ObjectCodec oc)
131 {
132 super(src, oc);
133 _xmlParserFeatures = src._xmlParserFeatures;
134 _xmlGeneratorFeatures = src._xmlGeneratorFeatures;
135 _cfgNameForTextElement = src._cfgNameForTextElement;
136 _xmlInputFactory = src._xmlInputFactory;
137 _xmlOutputFactory = src._xmlOutputFactory;
138 }
139
140
145 protected XmlFactory(XmlFactoryBuilder b)
146 {
147 super(b, false);
148 _xmlParserFeatures = b.formatParserFeaturesMask();
149 _xmlGeneratorFeatures = b.formatGeneratorFeaturesMask();
150 _cfgNameForTextElement = b.nameForTextElement();
151 _xmlInputFactory = b.xmlInputFactory();
152 _xmlOutputFactory = b.xmlOutputFactory();
153 _initFactories(_xmlInputFactory, _xmlOutputFactory);
154 }
155
156 public static XmlFactoryBuilder builder() {
157 return new XmlFactoryBuilder();
158 }
159
160 @Override
161 public XmlFactoryBuilder rebuild() {
162 return new XmlFactoryBuilder(this);
163 }
164
165 protected void _initFactories(XMLInputFactory xmlIn, XMLOutputFactory xmlOut)
166 {
167
168 xmlOut.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
169
170 xmlIn.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
171 }
172
173
178 @Override
179 public XmlFactory copy() {
180 _checkInvalidCopy(XmlFactory.class);
181 return new XmlFactory(this, null);
182 }
183
184 @Override
185 public Version version() {
186 return PackageVersion.VERSION;
187 }
188
189
194
195
198 protected transient String _jdkXmlInFactory;
199
200
203 protected transient String _jdkXmlOutFactory;
204
205
209 @Override
210 protected Object readResolve() {
211 if (_jdkXmlInFactory == null) {
212 throw new IllegalStateException("No XMLInputFactory class name read during JDK deserialization");
213 }
214 if (_jdkXmlOutFactory == null) {
215 throw new IllegalStateException("No XMLOutputFactory class name read during JDK deserialization");
216 }
217 final XMLInputFactory inf;
218 XMLOutputFactory outf;
219 try {
220 inf = (XMLInputFactory) Class.forName(_jdkXmlInFactory).getDeclaredConstructor().newInstance();
221 outf = (XMLOutputFactory) Class.forName(_jdkXmlOutFactory).getDeclaredConstructor().newInstance();
222 } catch (Exception e) {
223 throw new IllegalArgumentException(e);
224 }
225 return new XmlFactory(_objectCodec, _xmlParserFeatures, _xmlGeneratorFeatures,
226 inf, outf, _cfgNameForTextElement);
227 }
228
229
233 private void readObject(ObjectInputStream in)
234 throws IOException, ClassNotFoundException
235 {
236 in.defaultReadObject();
237 _jdkXmlInFactory = in.readUTF();
238 _jdkXmlOutFactory = in.readUTF();
239 }
240
241
245 private void writeObject(ObjectOutputStream out) throws IOException {
246 out.defaultWriteObject();
247 out.writeUTF(_xmlInputFactory.getClass().getName());
248 out.writeUTF(_xmlOutputFactory.getClass().getName());
249 }
250
251
256
257
260 public void setXMLTextElementName(String name) {
261 _cfgNameForTextElement = name;
262 }
263
264
267 public String getXMLTextElementName() {
268 return _cfgNameForTextElement;
269 }
270
271
276
277
280 public final XmlFactory configure(FromXmlParser.Feature f, boolean state)
281 {
282 if (state) {
283 enable(f);
284 } else {
285 disable(f);
286 }
287 return this;
288 }
289
290
293 public XmlFactory enable(FromXmlParser.Feature f) {
294 _xmlParserFeatures |= f.getMask();
295 return this;
296 }
297
298
301 public XmlFactory disable(FromXmlParser.Feature f) {
302 _xmlParserFeatures &= ~f.getMask();
303 return this;
304 }
305
306
309 public final boolean isEnabled(FromXmlParser.Feature f) {
310 return (_xmlParserFeatures & f.getMask()) != 0;
311 }
312
313 @Override
314 public int getFormatParserFeatures() {
315 return _xmlParserFeatures;
316 }
317
318 @Override
319 public int getFormatGeneratorFeatures() {
320 return _xmlGeneratorFeatures;
321 }
322
323
328
329
332 public final XmlFactory configure(ToXmlGenerator.Feature f, boolean state) {
333 if (state) {
334 enable(f);
335 } else {
336 disable(f);
337 }
338 return this;
339 }
340
341
344 public XmlFactory enable(ToXmlGenerator.Feature f) {
345 _xmlGeneratorFeatures |= f.getMask();
346 return this;
347 }
348
349
352 public XmlFactory disable(ToXmlGenerator.Feature f) {
353 _xmlGeneratorFeatures &= ~f.getMask();
354 return this;
355 }
356
357
360 public final boolean isEnabled(ToXmlGenerator.Feature f) {
361 return (_xmlGeneratorFeatures & f.getMask()) != 0;
362 }
363
364
369
370
371 public XMLInputFactory getXMLInputFactory() {
372 return _xmlInputFactory;
373 }
374
375 public void setXMLInputFactory(XMLInputFactory f) {
376 _xmlInputFactory = f;
377 }
378
379
380 public XMLOutputFactory getXMLOutputFactory() {
381 return _xmlOutputFactory;
382 }
383
384 public void setXMLOutputFactory(XMLOutputFactory f) {
385 _xmlOutputFactory = f;
386 }
387
388
393
394
401 @Override
402 public String getFormatName() {
403 return FORMAT_NAME_XML;
404 }
405
406 @Override
407 public MatchStrength hasFormat(InputAccessor acc) throws IOException {
408 return hasXMLFormat(acc);
409 }
410
411
417 @Override
418 public boolean requiresCustomCodec() { return true; }
419
420
425
426
432 @Override
433 public boolean canUseCharArrays() { return false; }
434
435 @Override
436 public Class<FromXmlParser.Feature> getFormatReadFeatureType() {
437 return FromXmlParser.Feature.class;
438 }
439
440 @Override
441 public Class<ToXmlGenerator.Feature> getFormatWriteFeatureType() {
442 return ToXmlGenerator.Feature.class;
443 }
444
445
450
451
456 @SuppressWarnings("resource")
457 @Override
458 public JsonParser createParser(String content) throws IOException {
459 Reader r = new StringReader(content);
460 IOContext ctxt = _createContext(r, true);
461 if (_inputDecorator != null) {
462 r = _inputDecorator.decorate(ctxt, r);
463 }
464 return _createParser(r, ctxt);
465 }
466
467
472
473 @Override
474 public ToXmlGenerator createGenerator(OutputStream out) throws IOException {
475 return createGenerator(out, JsonEncoding.UTF8);
476 }
477
478 @Override
479 public ToXmlGenerator createGenerator(OutputStream out, JsonEncoding enc) throws IOException
480 {
481
482 final IOContext ctxt = _createContext(out, false);
483 ctxt.setEncoding(enc);
484 return new ToXmlGenerator(ctxt,
485 _generatorFeatures, _xmlGeneratorFeatures,
486 _objectCodec, _createXmlWriter(ctxt, out));
487 }
488
489 @Override
490 public ToXmlGenerator createGenerator(Writer out) throws IOException
491 {
492 final IOContext ctxt = _createContext(out, false);
493 return new ToXmlGenerator(ctxt,
494 _generatorFeatures, _xmlGeneratorFeatures,
495 _objectCodec, _createXmlWriter(ctxt, out));
496 }
497
498 @SuppressWarnings("resource")
499 @Override
500 public ToXmlGenerator createGenerator(File f, JsonEncoding enc) throws IOException
501 {
502 OutputStream out = new FileOutputStream(f);
503
504 final IOContext ctxt = _createContext(out, true);
505 ctxt.setEncoding(enc);
506 return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
507 _objectCodec, _createXmlWriter(ctxt, out));
508 }
509
510
515
516
522 public FromXmlParser createParser(XMLStreamReader sr) throws IOException
523 {
524
525 if (sr.getEventType() != XMLStreamConstants.START_ELEMENT) {
526 sr = _initializeXmlReader(sr);
527 }
528
529
530 FromXmlParser xp = new FromXmlParser(_createContext(sr, false),
531 _parserFeatures, _xmlParserFeatures, _objectCodec, sr);
532 if (_cfgNameForTextElement != null) {
533 xp.setXMLTextElementName(_cfgNameForTextElement);
534 }
535 return xp;
536 }
537
538
545 public ToXmlGenerator createGenerator(XMLStreamWriter sw) throws IOException
546 {
547 sw = _initializeXmlWriter(sw);
548 IOContext ctxt = _createContext(sw, false);
549 return new ToXmlGenerator(ctxt, _generatorFeatures, _xmlGeneratorFeatures,
550 _objectCodec, sw);
551 }
552
553
558
559 @Override
560 protected FromXmlParser _createParser(InputStream in, IOContext ctxt) throws IOException
561 {
562 XMLStreamReader sr;
563 try {
564 sr = _xmlInputFactory.createXMLStreamReader(in);
565 } catch (XMLStreamException e) {
566 return StaxUtil.throwAsParseException(e, null);
567 }
568 sr = _initializeXmlReader(sr);
569 FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
570 _objectCodec, sr);
571 if (_cfgNameForTextElement != null) {
572 xp.setXMLTextElementName(_cfgNameForTextElement);
573 }
574 return xp;
575 }
576
577 @Override
578 protected FromXmlParser _createParser(Reader r, IOContext ctxt) throws IOException
579 {
580 XMLStreamReader sr;
581 try {
582 sr = _xmlInputFactory.createXMLStreamReader(r);
583 } catch (XMLStreamException e) {
584 return StaxUtil.throwAsParseException(e, null);
585 }
586 sr = _initializeXmlReader(sr);
587 FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
588 _objectCodec, sr);
589 if (_cfgNameForTextElement != null) {
590 xp.setXMLTextElementName(_cfgNameForTextElement);
591 }
592 return xp;
593 }
594
595 @Override
596 protected FromXmlParser _createParser(char[] data, int offset, int len, IOContext ctxt,
597 boolean recycleBuffer) throws IOException
598 {
599
600
601 XMLStreamReader sr;
602 try {
603 sr = _xmlInputFactory.createXMLStreamReader(new Stax2CharArraySource(data, offset, len));
604 } catch (XMLStreamException e) {
605 return StaxUtil.throwAsParseException(e, null);
606 }
607 sr = _initializeXmlReader(sr);
608 FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
609 _objectCodec, sr);
610 if (_cfgNameForTextElement != null) {
611 xp.setXMLTextElementName(_cfgNameForTextElement);
612 }
613 return xp;
614 }
615
616 @Override
617 protected FromXmlParser _createParser(byte[] data, int offset, int len, IOContext ctxt) throws IOException
618 {
619 XMLStreamReader sr;
620 try {
621 sr = _xmlInputFactory.createXMLStreamReader(new Stax2ByteArraySource(data, offset, len));
622 } catch (XMLStreamException e) {
623 return StaxUtil.throwAsParseException(e, null);
624 }
625 sr = _initializeXmlReader(sr);
626 FromXmlParser xp = new FromXmlParser(ctxt, _parserFeatures, _xmlParserFeatures,
627 _objectCodec, sr);
628 if (_cfgNameForTextElement != null) {
629 xp.setXMLTextElementName(_cfgNameForTextElement);
630 }
631 return xp;
632 }
633
634 @Override
635 protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException {
636
637 VersionUtil.throwInternal();
638 return null;
639 }
640
641
646
647 protected XMLStreamWriter _createXmlWriter(IOContext ctxt, OutputStream out) throws IOException
648 {
649 XMLStreamWriter sw;
650 try {
651 sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, out), "UTF-8");
652 } catch (Exception e) {
653 throw new JsonGenerationException(e.getMessage(), e, null);
654 }
655 return _initializeXmlWriter(sw);
656 }
657
658 protected XMLStreamWriter _createXmlWriter(IOContext ctxt, Writer w) throws IOException
659 {
660 XMLStreamWriter sw;
661 try {
662 sw = _xmlOutputFactory.createXMLStreamWriter(_decorate(ctxt, w));
663 } catch (Exception e) {
664 throw new JsonGenerationException(e.getMessage(), e, null);
665 }
666 return _initializeXmlWriter(sw);
667 }
668
669 protected final XMLStreamWriter _initializeXmlWriter(XMLStreamWriter sw) throws IOException
670 {
671
672
673 try {
674 sw.setDefaultNamespace("");
675 } catch (Exception e) {
676 throw new JsonGenerationException(e.getMessage(), e, null);
677 }
678 return sw;
679 }
680
681 protected final XMLStreamReader _initializeXmlReader(XMLStreamReader sr) throws IOException
682 {
683 try {
684
685 while (sr.next() != XMLStreamConstants.START_ELEMENT) {
686 ;
687 }
688
689 } catch (Exception e) {
690 throw new JsonParseException(null, e.getMessage(), e);
691 }
692 return sr;
693 }
694
695
700
701 private final static byte UTF8_BOM_1 = (byte) 0xEF;
702 private final static byte UTF8_BOM_2 = (byte) 0xBB;
703 private final static byte UTF8_BOM_3 = (byte) 0xBF;
704
705 private final static byte BYTE_x = (byte) 'x';
706 private final static byte BYTE_m = (byte) 'm';
707 private final static byte BYTE_l = (byte) 'l';
708 private final static byte BYTE_D = (byte) 'D';
709
710 private final static byte BYTE_LT = (byte) '<';
711 private final static byte BYTE_QMARK = (byte) '?';
712 private final static byte BYTE_EXCL = (byte) '!';
713 private final static byte BYTE_HYPHEN = (byte) '-';
714
715
723 public static MatchStrength hasXMLFormat(InputAccessor acc) throws IOException
724 {
725
728 if (!acc.hasMoreBytes()) {
729 return MatchStrength.INCONCLUSIVE;
730 }
731 byte b = acc.nextByte();
732
733 if (b == UTF8_BOM_1) {
734 if (!acc.hasMoreBytes()) {
735 return MatchStrength.INCONCLUSIVE;
736 }
737 if (acc.nextByte() != UTF8_BOM_2) {
738 return MatchStrength.NO_MATCH;
739 }
740 if (!acc.hasMoreBytes()) {
741 return MatchStrength.INCONCLUSIVE;
742 }
743 if (acc.nextByte() != UTF8_BOM_3) {
744 return MatchStrength.NO_MATCH;
745 }
746 if (!acc.hasMoreBytes()) {
747 return MatchStrength.INCONCLUSIVE;
748 }
749 b = acc.nextByte();
750 }
751
752 boolean maybeXmlDecl = (b == BYTE_LT);
753 if (!maybeXmlDecl) {
754 int ch = skipSpace(acc, b);
755 if (ch < 0) {
756 return MatchStrength.INCONCLUSIVE;
757 }
758 b = (byte) ch;
759
760 if (b != BYTE_LT) {
761 return MatchStrength.NO_MATCH;
762 }
763 }
764 if (!acc.hasMoreBytes()) {
765 return MatchStrength.INCONCLUSIVE;
766 }
767 b = acc.nextByte();
768
769 if (b == BYTE_QMARK) {
770 b = acc.nextByte();
771 if (b == BYTE_x) {
772 if (maybeXmlDecl) {
773 if (acc.hasMoreBytes() && acc.nextByte() == BYTE_m) {
774 if (acc.hasMoreBytes() && acc.nextByte() == BYTE_l) {
775 return MatchStrength.FULL_MATCH;
776 }
777 }
778 }
779
780 return MatchStrength.SOLID_MATCH;
781 }
782
783 if (validXmlNameStartChar(acc, b)) {
784 return MatchStrength.SOLID_MATCH;
785 }
786 } else if (b == BYTE_EXCL) {
787
790 if (!acc.hasMoreBytes()) {
791 return MatchStrength.INCONCLUSIVE;
792 }
793 b = acc.nextByte();
794 if (b == BYTE_HYPHEN) {
795 if (!acc.hasMoreBytes()) {
796 return MatchStrength.INCONCLUSIVE;
797 }
798 if (acc.nextByte() == BYTE_HYPHEN) {
799 return MatchStrength.SOLID_MATCH;
800 }
801 } else if (b == BYTE_D) {
802 return tryMatch(acc, "OCTYPE", MatchStrength.SOLID_MATCH);
803 }
804 } else {
805
806 if (validXmlNameStartChar(acc, b)) {
807 return MatchStrength.SOLID_MATCH;
808 }
809 }
810 return MatchStrength.NO_MATCH;
811 }
812
813 private final static boolean validXmlNameStartChar(InputAccessor acc, byte b)
814 throws IOException
815 {
816
819 int ch = (int) b & 0xFF;
820 if (ch >= 'A') {
821
822 return true;
823 }
824 return false;
825 }
826
827 private final static MatchStrength tryMatch(InputAccessor acc, String matchStr, MatchStrength fullMatchStrength)
828 throws IOException
829 {
830 for (int i = 0, len = matchStr.length(); i < len; ++i) {
831 if (!acc.hasMoreBytes()) {
832 return MatchStrength.INCONCLUSIVE;
833 }
834 if (acc.nextByte() != matchStr.charAt(i)) {
835 return MatchStrength.NO_MATCH;
836 }
837 }
838 return fullMatchStrength;
839 }
840
841 private final static int skipSpace(InputAccessor acc, byte b) throws IOException
842 {
843 while (true) {
844 int ch = (int) b & 0xFF;
845 if (!(ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')) {
846 return ch;
847 }
848 if (!acc.hasMoreBytes()) {
849 return -1;
850 }
851 b = acc.nextByte();
852 ch = (int) b & 0xFF;
853 }
854 }
855
856
861
862 protected OutputStream _decorate(IOContext ioCtxt, OutputStream out) throws IOException
863 {
864 if (_outputDecorator != null) {
865 OutputStream out2 = _outputDecorator.decorate(ioCtxt, out);
866 if (out2 != null) {
867 return out2;
868 }
869 }
870 return out;
871 }
872
873 protected Writer _decorate(IOContext ioCtxt, Writer out) throws IOException
874 {
875 if (_outputDecorator != null) {
876 Writer out2 = _outputDecorator.decorate(ioCtxt, out);
877 if (out2 != null) {
878 return out2;
879 }
880 }
881 return out;
882 }
883 }
884