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.
精彩评论