I have a question about using MTOM/XOP with JAX-WS. I'm writing a web service which sends large amounts of binary data. The client requests a number of files and the server returns the files in the response.
I'm able to get it to build the response correctly so that it correctly implements XOP, but I run into memory-related issues becasuse it stores the entire response in memory before sending it. The files this web service sends can get very large (like, giga-bytes large), so storing the response in memory is not an option.
This Oracle website (and along with this one) seems to solve this problem, but I just don't understand it. I think they use a DataHandler
object to stream the request/response, but I can't figure out how they instantiate it.
I'm generating my JAX-WS class files from an existing WSDL using wsimport
. I'm using JAX-WS RI 2.1.6 with Java 6.
How do I send the response as I'm building it without having to store in all in memory first?
Thanks in advance for your help.
UPDATE 12/17: I added the following attributes to the schema element in the WSDL that holds the binary data. This causes wsimport
to add a DataHandler
object to the JAXB class. A FileDataHandler
can then be added to the response, instead of adding the entire contents of the file, allowing the server to stream the contents of each file, instead of holding them all in memory:
xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
xmime:expectedContentTypes="application/octet-stream"
So, the server correctly builds the response now, and client properly saves each file to disk when it receives the request. However, the client still reads the entire response into memory for some reason.
The server code (SIB):
@MTOM
@StreamingAttachment(parseEagerly = true, memoryThreshold = 4000000L)
@WebService(...)
public class DownloadFilesPortTypeImpl implements DownloadFilesPortType {
@Override
public FileSetResponseType downloadFileSet(FileSetRequestType body) {
FileSetResponseType response = new FileSetResponseType();
for (FileRequest freq : body.getFileRequest()){
try{
//find the file on disk
File file = findFile(freq.getFileId());
//read the file data into memory
byte[] fileData;
{
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte buf[] = new byte[8192];
int read;
while ((read = in.read(buf)) != -1){
out.write(buf, 0, read);
}
in.close();
out.close();
fileData = out.toByteArray();
}
//add the file to the response
FileResponse fresp = new FileResponse();
fresp.setFileId(freq.getFileId());
fresp.setData(fileData); //<-- type "xs:base64Binary"
response.getFileResponse().add(fresp);
}
catch (IOException e){
}
}
return response;
}
}
The client code:
DownloadFilesService service = new DownloadFilesService();
MTOMFeature mtomFeature = new MTOMFeature();
StreamingAttachmentFeature stf = new StreamingAttachmentFeature(null, true, 4000000L);
DownloadFilesPortType port = service.getDownloadFilesPortSoap12(mtomFeature, stf);
FileSetRequestType request = new FileSetRequestType();
FileRequest freq = new FileRequest();
freq.setFileId("1234");
request.getFileRequest().add(freq);
freq = new FileRequest();
freq.setFileId("9876");
request.getFileRequest().add(freq);
//...
FileSetResponseType response = port.downloadFileSet(request); //reads entire response into memory
for (FileResponse fres : response.getFileResponse()){
byte[] data = fres.getFileData();
//...
}
You make your own class that implements DataSource
and construct the DataHandler passing it in. It can even be anonymous.
In Apache CXF, we make it much easier to do this. You can just have a 'getter' that returns a DataSource or a DataHandler. The elaborate scheme in the code you've posted is not something familiar to me.
I think that the same methods work with the JDK's JAX-WS+JAXB. See this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With