开发者

Multiple XML "files" in one stream

While developing an adapter for a webservice, I've ended up facing a response like this:

<?xml version="1.0" encoding="UTF-8"?>
<ResponseHeader version="1.0">
    <ResponseCode>T100</ResponseCode>
    <SubmissionIdentifier>1</SubmissionIdentifier>
</ResponseHeader>

<?xml version="1.0" encoding="UTF-8"?>
<SubmissionProgress xmlns="sss"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        
    status="inProgress"
    submissionIdentifier="1"
    submissionType="live">
    <PFile status="rejected"
        index="1"
        pFileIdentifier="999">
        <Exception errorCode="2001" outcomeType="rejectFile">
            <Description>There.file.  </Description>
            <SourceRecord index="3">...</SourceRecord>
        </Exception>
    </PFile>
</SubmissionProgress>

ResponseHeader and SubmissionProgress (and every element inside) classes have been successfully generated by xjc and, if I split this string into 2 different string I can unmarshall both classes perfectly.

But, if I keep it in the same String and try to pass it to both unmarshallers sequentially it breaks in the first unmarshall.

I'm using this code to unmarshall both from one String:

Reader reader = new StringReader(response);
JAXBContext jcrh = JAXBContext.newInstance(ResponseHeader.class);
JAXBContext jcsp = JAXBContext.newInstance(SubmissionProgress.class);
Unmarshaller urh = jcrh.crea开发者_C百科teUnmarshaller();
Unmarshaller usp = jcsp.createUnmarshaller();
ResponseHeader rh = (ResponseHeader) urh.unmarshal(reader);
SubmissionProgress sr = (SubmissionProgress) usp.unmarshal(reader);

And I get the following exception (at ResponseHeader rh = (ResponseHeader) urh.unmarshal(reader);):

uk.co.bacs.submissions.ResponseHeader@fced4
javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed.]
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:315)
(...)

Is there some JAXB tweak to use in these cases (multiple XML files in one single stream)?


I don't know of a JAXB tweak; the way I've done this kind of thing is to implement an XmlEventReader (or XmlStreamReader) that simulates end-of-document when needed. Note that Unmarshaller.unmarshal() will take one of these as an argument. To make sure you get the event sequence right, watch a "normal" document's event sequence. You'll do two unmarshal()s.


As there is no way for JAXB to read through the files by itself, I've found 2 working solutions.

The first and simpler one, in case the stream is small, would be to read it all into one string and split it

String xml = "<?xml ... <?xml ...";
String[] xmlArray = xml.split("<\\?xml");
ObjectA a = (ResponseHeader) u.unmarshal(new StringReader("<?xml"+xmlArray[1]);
ObjectB b = (SubmissionProgress) u2.unmarshal(new StringReader("<?xml"+xmlArray[2));

But, as an exercise, for cleaner code and future use with bigger streams (dealing with one object at a time), I made MultiXMLDocReader class

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

public class MultiXMLDocReader extends Reader {
    private BufferedReader reader;
    private String buffer;
    private int bufferPos;
    private boolean firstDocument;
    private boolean realEOF;
    private boolean enforceEOF;

    public MultiXMLDocReader(Reader reader) {
        this.reader = new BufferedReader(reader);
        firstDocument = true;
        buffer = "";
        bufferPos = 0;
        realEOF = enforceEOF = false;
    }

    @Override
    public void close() throws IOException {
        enforceEOF = false;
        if (realEOF) reader.close();
    }

    @Override
    public int read() throws IOException {
        char[] buffer = new char[1];
        int result = read(buffer, 0, 1);
        if (result < 0) return -1;
        return buffer[0];
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        if (enforceEOF) return -1;
        int lenLeft = len;
        int read = 0;
        while (lenLeft > 0) {
            if (buffer.length()>0) {
                char[] lbuffer = buffer.toCharArray();
                int bufLen = buffer.length() - bufferPos;
                int newBufferPos = 0;
                if (lenLeft < bufLen) {
                    bufLen = lenLeft;
                    newBufferPos = bufferPos + bufLen;
                }
                else buffer = "";
                System.arraycopy(lbuffer, bufferPos, cbuf, off, bufLen);
                read += bufLen;
                lenLeft -= bufLen;
                off += bufLen;
                bufferPos = newBufferPos;
                continue;
            }
            buffer = reader.readLine();
            if (buffer == null) {
                realEOF = true;
                enforceEOF = true;
                return (read == 0 ? -1 : read);
            }
            else
                buffer += "\n";
            if (buffer.startsWith("<?xml")) {
                if (firstDocument) firstDocument = false;
                else {
                    enforceEOF = true;
                    return (read == 0 ? -1 : read);
                }
            }
        }
        return read;
    }
}

which can be used as easily as

MultiXMLDocReader xmlReader = new MultiXMLDocReader(new InputStreamReader(anyInputStream));
ObjectA a = (ResponseHeader) u.unmarshal(xmlReader);
ObjectB b = (SubmissionProgress) u2.unmarshal(xmlReader);

without loading the whole stream to a string.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜