Repository: camel Updated Branches: refs/heads/master 241cc204e -> a9d093faf
CAMEL-10070: Add XMLStreamReader to InputStream/Reader converter Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a9d093fa Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a9d093fa Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a9d093fa Branch: refs/heads/master Commit: a9d093faf22c57b18777b5f6de6b03731157146b Parents: 241cc20 Author: Akitoshi Yoshida <a...@apache.org> Authored: Sat Jun 18 00:26:33 2016 +0200 Committer: Akitoshi Yoshida <a...@apache.org> Committed: Sat Jun 18 00:35:21 2016 +0200 ---------------------------------------------------------------------- .../camel/converter/jaxp/StaxConverter.java | 21 +++ .../jaxp/XMLStreamReaderInputStream.java | 170 +++++++++++++++++++ .../converter/jaxp/XMLStreamReaderReader.java | 162 ++++++++++++++++++ .../camel/converter/jaxp/StaxConverterTest.java | 91 +++++++++- 4 files changed, 442 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a9d093fa/camel-core/src/main/java/org/apache/camel/converter/jaxp/StaxConverter.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/StaxConverter.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/StaxConverter.java index 6a908e6..d41ee7b 100644 --- a/camel-core/src/main/java/org/apache/camel/converter/jaxp/StaxConverter.java +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/StaxConverter.java @@ -284,6 +284,27 @@ public class StaxConverter { } } + @Converter + public InputStream createInputStream(XMLStreamReader reader, Exchange exchange) { + XMLOutputFactory factory = getOutputFactory(); + try { + String charsetName = IOHelper.getCharsetName(exchange, false); + return new XMLStreamReaderInputStream(reader, charsetName, factory); + } finally { + returnXMLOutputFactory(factory); + } + } + + @Converter + public Reader createReader(XMLStreamReader reader, Exchange exchange) { + XMLOutputFactory factory = getOutputFactory(); + try { + return new XMLStreamReaderReader(reader, factory); + } finally { + returnXMLOutputFactory(factory); + } + } + private static boolean isWoodstox(Object factory) { return factory.getClass().getPackage().getName().startsWith("com.ctc.wstx"); } http://git-wip-us.apache.org/repos/asf/camel/blob/a9d093fa/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderInputStream.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderInputStream.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderInputStream.java new file mode 100644 index 0000000..1e3158f --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderInputStream.java @@ -0,0 +1,170 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.camel.converter.jaxp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +/** + * + */ +class XMLStreamReaderInputStream extends InputStream { + private static final int BUFFER_SIZE = 4096; + private XMLStreamReader reader; + private XMLStreamWriter writer; + private TrimmableByteArrayOutputStream chunk; + private byte[] buffer; + private String charset; + private int bpos; + + public XMLStreamReaderInputStream(XMLStreamReader reader, String charset, XMLOutputFactory outfactory) { + this.reader = reader; + this.buffer = new byte[BUFFER_SIZE]; + this.chunk = new TrimmableByteArrayOutputStream(); + this.charset = charset == null ? "utf-8" : charset; + try { + this.writer = outfactory.createXMLStreamWriter(chunk, charset); + } catch (XMLStreamException e) { + //ignore + } + } + + public XMLStreamReaderInputStream(XMLStreamReader reader, XMLOutputFactory outfactory) { + this(reader, "utf-8", outfactory); + } + + @Override + public int read() throws IOException { + byte[] ba = new byte[1]; + return read(ba, 0, 1) == 1 ? ba[0] : -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int tlen = 0; + while (len > 0) { + int n = ensureBuffering(len); + if (n < 0) { + break; + } + int clen = len > n ? n : len; + System.arraycopy(buffer, 0, b, off, clen); + System.arraycopy(buffer, clen, buffer, 0, buffer.length - clen); + bpos -= clen; + len -= clen; + off += clen; + tlen += clen; + } + + return tlen > 0 ? tlen : -1; + } + + private int ensureBuffering(int size) throws IOException { + if (size < bpos) { + return bpos; + } + // refill the buffer as more buffering is requested than the current buffer status + try { + + // very first event + if (XMLStreamConstants.START_DOCUMENT == reader.getEventType()) { + writer.writeStartDocument(charset, "1.0"); + } + if (chunk.size() < buffer.length) { + while (reader.hasNext()) { + int code = reader.next(); + switch (code) { + case XMLStreamConstants.END_DOCUMENT: + writer.writeEndDocument(); + break; + case XMLStreamConstants.START_ELEMENT: + QName qname = reader.getName(); + writer.writeStartElement(qname.getPrefix(), qname.getLocalPart(), qname.getNamespaceURI()); + for (int i = 0; i < reader.getAttributeCount(); i++) { + writer.writeAttribute( + reader.getAttributePrefix(i), reader.getAttributeNamespace(i), reader.getAttributeLocalName(i), + reader.getAttributeValue(i)); + } + for (int i = 0; i < reader.getNamespaceCount(); i++) { + writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); + } + break; + case XMLStreamConstants.END_ELEMENT: + writer.writeEndElement(); + break; + case XMLStreamConstants.CHARACTERS: + writer.writeCharacters(reader.getText()); + break; + case XMLStreamConstants.COMMENT: + writer.writeComment(reader.getText()); + break; + case XMLStreamConstants.CDATA: + writer.writeCData(reader.getText()); + break; + default: + break; + } + + // check if the chunk is full + final int csize = buffer.length - bpos; + if (chunk.size() > csize) { + System.arraycopy(chunk.getByteArray(), 0, buffer, bpos, csize); + bpos = buffer.length; + chunk.trim(csize, 0); + return buffer.length; + } + } + } + final int csize = chunk.size() < buffer.length - bpos ? chunk.size() : buffer.length - bpos; + if (csize > 0) { + System.arraycopy(chunk.getByteArray(), 0, buffer, bpos, csize); + bpos += csize; + chunk.trim(csize, 0); + return bpos; + } else { + return bpos > 0 ? bpos : -1; + } + } catch (XMLStreamException e) { + throw new IOException(e); + } + } + + static class TrimmableByteArrayOutputStream extends ByteArrayOutputStream { + public void trim(int head, int tail) { + System.arraycopy(buf, head, buf, 0, count - head - tail); + count -= head + tail; + } + + public byte[] toByteArray(int len) { + byte[] b = new byte[len]; + System.arraycopy(buf, 0, b, 0, len); + return b; + } + + byte[] getByteArray() { + return buf; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a9d093fa/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderReader.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderReader.java b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderReader.java new file mode 100644 index 0000000..571c21b --- /dev/null +++ b/camel-core/src/main/java/org/apache/camel/converter/jaxp/XMLStreamReaderReader.java @@ -0,0 +1,162 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.camel.converter.jaxp; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.Reader; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +/** + * + */ +class XMLStreamReaderReader extends Reader { + private static final int BUFFER_SIZE = 4096; + private XMLStreamReader reader; + private XMLStreamWriter writer; + private TrimmableCharArrayWriter chunk; + private char[] buffer; + private int bpos; + + public XMLStreamReaderReader(XMLStreamReader reader, XMLOutputFactory outfactory) { + this.reader = reader; + this.buffer = new char[BUFFER_SIZE]; + this.chunk = new TrimmableCharArrayWriter(); + try { + this.writer = outfactory.createXMLStreamWriter(chunk); + } catch (XMLStreamException e) { + //ignore + } + } + + @Override + public void close() throws IOException { + + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int tlen = 0; + while (len > 0) { + int n = ensureBuffering(len); + if (n < 0) { + break; + } + int clen = len > n ? n : len; + System.arraycopy(buffer, 0, cbuf, off, clen); + System.arraycopy(buffer, clen, buffer, 0, buffer.length - clen); + bpos -= clen; + len -= clen; + off += clen; + tlen += clen; + } + return tlen > 0 ? tlen : -1; + } + + private int ensureBuffering(int size) throws IOException { + if (size < bpos) { + return bpos; + } + // refill the buffer as more buffering is requested than the current buffer status + try { + + // very first event + if (XMLStreamConstants.START_DOCUMENT == reader.getEventType()) { + writer.writeStartDocument("utf-8", "1.0"); + } + if (chunk.size() < buffer.length) { + while (reader.hasNext()) { + int code = reader.next(); + switch (code) { + case XMLStreamConstants.END_DOCUMENT: + writer.writeEndDocument(); + break; + case XMLStreamConstants.START_ELEMENT: + QName qname = reader.getName(); + writer.writeStartElement(qname.getPrefix(), qname.getLocalPart(), qname.getNamespaceURI()); + for (int i = 0; i < reader.getAttributeCount(); i++) { + writer.writeAttribute( + reader.getAttributePrefix(i), reader.getAttributeNamespace(i), reader.getAttributeLocalName(i), + reader.getAttributeValue(i)); + } + for (int i = 0; i < reader.getNamespaceCount(); i++) { + writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); + } + break; + case XMLStreamConstants.END_ELEMENT: + writer.writeEndElement(); + break; + case XMLStreamConstants.CHARACTERS: + writer.writeCharacters(reader.getText()); + break; + case XMLStreamConstants.COMMENT: + writer.writeComment(reader.getText()); + break; + case XMLStreamConstants.CDATA: + writer.writeCData(reader.getText()); + break; + default: + break; + } + + // check if the chunk is full + final int csize = buffer.length - bpos; + if (chunk.size() > csize) { + System.arraycopy(chunk.getCharArray(), 0, buffer, bpos, csize); + bpos = buffer.length; + chunk.trim(csize, 0); + return buffer.length; + } + } + } + final int csize = chunk.size() < buffer.length - bpos ? chunk.size() : buffer.length - bpos; + if (csize > 0) { + System.arraycopy(chunk.getCharArray(), 0, buffer, bpos, csize); + bpos += csize; + chunk.trim(csize, 0); + return bpos; + } else { + return bpos > 0 ? bpos : -1; + } + } catch (XMLStreamException e) { + throw new IOException(e); + } + } + + static class TrimmableCharArrayWriter extends CharArrayWriter { + public void trim(int head, int tail) { + System.arraycopy(buf, head, buf, 0, count - head - tail); + count -= head + tail; + } + + public char[] toCharArray(int len) { + char[] c = new char[len]; + System.arraycopy(buf, 0, c, 0, len); + return c; + } + + char[] getCharArray() { + return buf; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a9d093fa/camel-core/src/test/java/org/apache/camel/converter/jaxp/StaxConverterTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/converter/jaxp/StaxConverterTest.java b/camel-core/src/test/java/org/apache/camel/converter/jaxp/StaxConverterTest.java index d6d37e0..10e916e 100644 --- a/camel-core/src/test/java/org/apache/camel/converter/jaxp/StaxConverterTest.java +++ b/camel-core/src/test/java/org/apache/camel/converter/jaxp/StaxConverterTest.java @@ -16,9 +16,9 @@ */ package org.apache.camel.converter.jaxp; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.*; import java.nio.charset.Charset; +import java.util.Arrays; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventWriter; @@ -45,6 +45,21 @@ public class StaxConverterTest extends ContextTestSupport { private static final String TEST_XML_WITH_XML_HEADER = "<?xml version=\"1.0\"?>" + TEST_XML; + private static final String TEST_XML_7000; + + static { + StringBuilder sb = new StringBuilder(7000); + sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>").append("<list>"); + int n = 6963 - TEST_XML.length(); + while (n > 0) { + sb.append(TEST_XML); + n -= TEST_XML.length(); + } + sb.append("</list>"); + + TEST_XML_7000 = sb.toString(); + } + public void testEncodingXmlEventReader() throws Exception { TEST_XML_WITH_XML_HEADER_ISO_8859_1_AS_BYTE_ARRAY_STREAM.reset(); XMLEventReader reader = null; @@ -136,4 +151,76 @@ public class StaxConverterTest extends ContextTestSupport { assertEquals(TEST_XML, result); } + public void testToReaderByXmlStreamReader() throws Exception { + StringReader src = new StringReader(TEST_XML_7000); + XMLStreamReader xreader = null; + Reader reader = null; + try { + xreader = context.getTypeConverter().mandatoryConvertTo(XMLStreamReader.class, src); + reader = context.getTypeConverter().mandatoryConvertTo(Reader.class, xreader); + + // verify + StringReader expected = new StringReader(TEST_XML_7000); + char[] tmp1 = new char[512]; + char[] tmp2 = new char[512]; + for (;;) { + int n1 = 0; + int n2 = 0; + try { + n1 = expected.read(tmp1, 0, tmp1.length); + n2 = reader.read(tmp2, 0, tmp2.length); + } catch (IOException e) { + fail("unable to read data"); + } + assertEquals(n1, n2); + if (n2 < 0) { + break; + } + assertTrue(Arrays.equals(tmp1, tmp2)); + } + } finally { + if (xreader != null) { + xreader.close(); + } + if (reader != null) { + reader.close(); + } + } + } + public void testToInputSreamByXmlStreamReader() throws Exception { + StringReader src = new StringReader(TEST_XML_7000); + XMLStreamReader xreader = null; + InputStream in = null; + try { + xreader = context.getTypeConverter().mandatoryConvertTo(XMLStreamReader.class, src); + in = context.getTypeConverter().mandatoryConvertTo(InputStream.class, xreader); + + // verify + InputStream expected = new ByteArrayInputStream(TEST_XML_7000.getBytes("utf-8")); + byte[] tmp1 = new byte[512]; + byte[] tmp2 = new byte[512]; + for (;;) { + int n1 = 0; + int n2 = 0; + try { + n1 = expected.read(tmp1, 0, tmp1.length); + n2 = in.read(tmp2, 0, tmp2.length); + } catch (IOException e) { + fail("unable to read data"); + } + assertEquals(n1, n2); + if (n2 < 0) { + break; + } + assertTrue(Arrays.equals(tmp1, tmp2)); + } + } finally { + if (xreader != null) { + xreader.close(); + } + if (in != null) { + in.close(); + } + } + } }