dunfa
dunfa

Reputation: 539

How to download a file as an InputStream through Struts 2 framework with Jersey?

I am working on client side of a RESTful service to let a user to download a file. I don't have access to server side code.

The client is under the Struts 2 and submits a POST request with some XML, and the server (under Spring) will generate a byte array representation of a zip file after processing that XML.

My problem is that, how to transfer the byte array as some InputStream, which is required by Struts 2 for downloading.

The client is using Struts 2 and here is the configuration in struts.xml for downloading a file:

<action name="getResponseMessage"
        class="DownloadAction"
        method="retrieveDownloadableMessage">
        <interceptor-ref name="logger" />
        <interceptor-ref name="defaultStack" />
        <result name="success" type="stream">
            <param name="contentType">application/octet-stream</param>
            <param name="inputName">inputStream</param>
            <param name="contentDisposition">attachment;filename="${bundleName}"</param>
            <param name="bufferSize">1024</param>
        </result>
</action>

and in the Java action class using Jersey (there are HTTP status check and appropriate getters for fields, that I omit for simplicity.):

public class DownloadAction extends ActionSupport {

    private InputStream inputStream;
    private String bundleName;

    public String retrieveDownloadableMessage() throws IOException {
        
        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);

        URI restfulURI = getRestfulURI();
        WebResource resource = client.resource(restfulURI);
                
        inputStream = resource.accept(MediaType.APPLICATION_OCTET_STREAM).post(InputStream.class, someXML); 

        bundleName = "response.zip";
        
        return SUCCESS;
    }
}

The backbone of the REST server side code:

@POST
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Consumes({ MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public Response getPtrXml(Source source) throws IOException {

    byte[] myByteArray = generateByteArr(source);  // I cannot modify this method.
    
    ByteArrayInputStream byteInputStream = new ByteArrayInputStream(myByteArray);
    
    return Response.ok(byteInputStream, MediaType.APPLICATION_OCTET_STREAM).build();

}

Running the client side code I see the output in console like this. Seems there is nothing being sent out to the client. What could be the problem?

Streaming result [inputStream] type=[application/octet-stream] length=[0] content-disposition=[attachment;filename="${packagePtrName}"] charset=[null]
    
WLTC0017E: Resources rolled back due to setRollbackOnly() being called.
com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[ServletNameNotFound]: java.lang.IllegalStateException: Response already committed.

UPDATE:

Struts 2 silently saves the file to a temp folder

I found if I used a File object to accept the returned ByteArrayInputStream, then Struts 2 somehow saves the file (exactly what I am looking for) to a local temp folder without opening a download dialog box to users. Any idea how to dig it out?

File fileToDownload = resource.accept(MediaType.APPLICATION_OCTET_STREAM).post(File.class, someXML);

inputStream = new FileInputStream(fileToDownload);

Upvotes: 1

Views: 1680

Answers (1)

Roman C
Roman C

Reputation: 1

You should check status code before you return the client response. Since you didn't do that you can't return an input stream until you read it.

if (response.getStatus() != 200) {
   throw new RuntimeException("Failed : HTTP error code : "
    + response.getStatus());
}

You can get input stream from response

inputStream = response.getEntityInputStream();
if (inputStream != null) {
  //read it to make sure the data is available

Also you didn't provide a public getter for bundleName and you got ${bundleName} filename.

Upvotes: 1

Related Questions