In struts2, file is downloaded as stream using StreamResult class.
To download file of dynamic name, size or type we need to extend this StreamResult class.
StreamResult result type takes the following parameters:
* contentType - the stream mime-type as sent to the web browser (default = text/plain).
* contentLength - the stream length in bytes (the browser displays a progress bar).
* contentDispostion - the content disposition header value for specifing the file name (default = inline, values are typically filename="document.pdf".
* inputName - the name of the InputStream property from the chained action (default = inputStream).
* bufferSize - the size of the buffer to copy from input to output (default = 1024).
Example:
<result name="success" type="stream">
<param name="contentType">image/jpeg</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">filename="document.pdf"</param>
<param name="bufferSize">1024</param>
</result>
So in the extended StreamResult class we need to set these parameters dynamically.
You could download source code from here, its same as following:
package downloadexample;
import org.apache.struts2.dispatcher.StreamResult;
import com.opensymphony.xwork2.ActionInvocation;
/**
* This class for result-type="myStream"
*
* <result-types> <result-type name="myStream" default="false"
* class="downloadexample.DynamicStreamResult"/>
*
* </result-types>
*
* It extends StreamResult Used to download file as a stream.
*
* @author sheetal
*
*/
public class DynamicStreamResult extends StreamResult{
@Override
protected void doExecute(String finalLocation,
ActionInvocation invocation)
throws Exception {
//get name of downloaded file
String downloadedFileName = invocation.getStack().
findValue(conditionalParse
("name", invocation));
contentDisposition = "filename=\""
+downloadedFileName + "\"";
//get file size
contentLength = ""+ invocation.getStack().findValue(
conditionalParse("size", invocation));
// get type of file
contentType = ""+ invocation.getStack().
findValue(
conditionalParse("description", invocation));
/*
Executes the result given a final location
(jsp page, action, etc) and
the action invocation (the state in which
the action was executed).
*/
super.doExecute(finalLocation, invocation);
}
}
Let, our site is a search site where user inputs name of a file. our system searches the file in server's local directory and lets the user download it if found.
To do this, .jsp file should be include the following:
<s:form action="downloadFile" validate="true">
<s:textfield label="Search file"
name="name" required="true"/>
<s:textfield label="Define file type (image/jpeg, text/plain, application/pdf)"
name="description" required="true"/>
<s:submit value="Find file"/>
</s:form>
where downloadFile is the action for downloading file.
In your action class, add the following lines:
private String name;
//holds name of downloaded file
private InputStream inputStream;
//holds stream of downloaded file
private String description;
//holds the content type of the downloaded file
private long size;
//holds the content size of the downloaded file
//method for downloading file
public String downloadFile()
{
/*
let, method searchFile(String fileName)
does the searching for us
& returns InputStream of the file if found
and null otherwise.
*/
this.inputStream = searchFile(name);
if(inputStream !=null)
{
return Action.SUCCESS;
}
else
{
//handle error
return Action.ERROR;
}
}
//write setter getter methods
public InputStream getInputStream() throws Exception
{
return inputStream;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getDescription()
{
return this.description;
}
public void setDescription(String description)
{
this.description = description;
}
// write getter setter for attribute size
Now, edit your struts.xml file:
<!-- custom result type for file download -->
<result-types>
<result-type name="myStream"
default="false"
class="downloadexample.DynamicStreamResult"/>
</result-types>
<!-- action for downloading file-->
<action name="downloadFile"
method="downloadFile"
class="<action-class-name>">
<result type="myStream"/>
<result name="error">jsps/your_error_page.jsp</result>
</action>
syntax highlighted by Code2HTML, v. 0.9.1
& we are done :D.
...................................................
May be a Better method:
the time i wrote this post i was too naive to find other solution for this. One person commented a quick solution on the blog.... thank you again. i haven't tried it, so i'm not sure if it works, check yourself...here is the comment.........
Actually, there's no need to extend the StreamResult class. You can dynamically pass the contentType (and other Stream parameters) by using parameter substitution in your Action mapping, like so:
<result name="success" type="stream">
<param name="contentType">${contentType}</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">filename="${fileName}"</param>
<param name="bufferSize">${bufferSize}</param>
</result>
You then add methods set/getContentType(), set/getFileName() and set/getBufferSize() to your Action class. In the Action method which handles your business logic, all you have to do is call this.setContentType(), this.setFileName() and this.setBufferSize() and supply whatever values you like.
---------------------------------------
36 comments:
In the .jsp "despription" should be "description".
thanks a lot :)
First of all thank you for the fix, this is the only way I've found to download files using struts2.
In DynamicStreamResult class, inputName attribute must be the InputStream. In the example, just:
inputName = "inputStream";
In the action example, the size getter is missing.
inputName attibute is by-default inputStream.
size getter is missing,...sorry i haven't noticed. thank you :)
This was a very useful and great post, I believe the only one that I found online. It solved my entire problem. Thank you Sheethal.
Hello Sheetalji,
Could you please send the proper source code for this, as i am not able to understand it fully. my email id is sunilg2000@hotmail.com
thanks in advance.
Sunil Gupta
hi sheetal,
i am somehow able to run this example but still have few problems like if i download a zip file it downloads but if i download a image file or a text/xml file it does not download but opens in the browser itself. any idea, how to download all type of file using this technology.
sunilg2000@hotmail.com
sunil
hi sheetal,
Good day to u. Nice code snippet you posted here. I'm interested to see the whole code like from JSP, action mappings, servlet and config.xml file for this file download thing. I need it for a demo here. can u post it for me? Plz, it's bit urgent. i need the whole parts.
send to durgasravaz@gmail.com
I found your example very useful.
I am using it on my current project!
Keep up the good work!
Great Post.
Just One Issue that i am facing. When trying to download the pdf file, it shows the size as '0' zero and is not opening the file.
As guess as to why I am getting this issue. Please reply back at nanda1505@rediffmail.com. Also would request you to please send me the complete code sample.
Quick Question, how does struts closes the inputStream handle??
Regards
Sunny
hi can u pls send me the working code of struts2 file download. am still baby in this struts2 world but now i have to implement file upload and download. my mail id is chandrucsb@gmail.com
hi sheetal,
nice work!
can plz send me the sample source code on my id satishtale@gmail.com
Thanks lot......
I tried using your code, however at the time of download, it simply shows up a blank page and if I try to save target, it shows the download as 0 bytes. I tried debugging and the data is being read into the inputStream. Is there any reason why this problem should occur? Could you please reply at ameya.gargesh@gmail.com
Result parameters can be set from action properties by using OGNL expressions in the result configuration.
It's unclear to me what problem you're trying to solve that isn't already solved by the framework.
I believe you have the contentDisposition incorrect. Valid values are "inline" or "attachment". The attachment also allow the specification of a filename attribute, but the syntax would be:
Content-Type: attachment; filename="attachment.ext"
(*Chris*)
hi chris,
*default* value for contentDisposition is "inline", the format i mentioned is taken from struts.apache.org and it worked for me, so i don't think its wrong.
check this-
http://struts.apache.org/2.x/struts2-core/apidocs/org/apache/struts2/dispatcher/StreamResult.html
"It worked for me (when I tried it with a browser specifically designed to deal with broken values)." doesn't mean "It's right."
http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1
this
if its wrong then some doc of stream result would mention that, but i failed to find any.
Dave,
the project for which i did this, we had to download different types of files from the web site. But in action properties, i did not find any other ways to set the values of filenames and contentType values dynamically.
in the case file of size is more than 100MB then if you use InputStream => occure outofMemory exception
Can you resolve this problem?
@sheetal: Use dynamic results; action properties are available in the result configuration.
If you are getting size of the file as zero , you may need to set the size of the file in action class or comment
contentLength = ""+ invocation.getStack().findValue(conditionalParse("size", invocation)); , it should work.
Actually, there's no need to extend the StreamResult class. You can dynamically pass the contentType (and other Stream parameters) by using parameter substitution in your Action mapping, like so:
<result name="success" type="stream">
<param name="contentType">${contentType}</param>
<param name="inputName">imageStream</param>
<param name="contentDisposition">filename="${fileName}"</param>
<param name="bufferSize">${bufferSize}</param>
</result>
You then add methods set/getContentType(), set/getFileName() and set/getBufferSize() to your Action class. In the Action method which handles your business logic, all you have to do is call this.setContentType(), this.setFileName() and this.setBufferSize() and supply whatever values you like.
thaaaaaank you :D... i'll add this to the post....thanks a lot :)
/bows humbly
Hi-
Thanks so much for you post. It was very very helpful.
I was getting the errors if use the below line in struts.xml for a tag.
param name="bufferSize">${bufferSize}/param
result>
if I make the line with int number, then works fine.
< param >name="bufferSize">1024 < / param >
result
Errors:
2010-05-04 12:50:26,198 ERROR com.opensymphony.xwork2.ObjectFactory.error:27 - Unable to set parameter [bufferSize] in result of type [org.apache.struts2.dispatcher.StreamResult]
Caught OgnlException while setting property 'bufferSize' on type 'org.apache.struts2.dispatcher.StreamResult'. - Class: ognl.OgnlRuntime
I hope someone one might have found this issue already, but I do not have answer.
Thanks a lot again Sheetal.
Thanks for pointing that out :)
Can I ask you a question?
Did you try the 2nd option I mentioned or the first one?
hi sheetal,
I m able to download a file using this code but if i cancel the download it is giving me the following exception:
java.lang.IllegalStateException
at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:406)
at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:770)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:505)
at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:228)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:212)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:818)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:624)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:445)
at java.lang.Thread.run(Thread.java:619)
Thanks,
Sumita
Hey Sumita,
Could you try the 2nd option I mentioned at the end of the post and see what happens?
hi,
Actually i m using following code in struts.xml:
param name="inputName" inputStream param
param name="contentDisposition" filename="abc.zip" param
inputStream is the object in my Action class. This zip file is being created dynamically. when i download it, it gets downloaded but when i click on cancel option it is giving me above exception at the back end..
Thanks,
Sumita
Very Good Tutorial.....
hai
i am fresher to struts2.i have atask to develop the file download concept using struts2 .i understand your example , but want the complete example once ..
will u plz send me
my mail id....
suresh@gaitview.com
tanks in advance
hai
i am fresher to struts2.i have atask to develop the file download concept using struts2 .i understand your example , but want the complete example once ..
will u plz send me
my mail id....
suresh@gaitview.com
tanks in advance
hai
i am fresher to struts2.i have atask to develop the file download concept using struts2 .i understand your example , but want the complete example once ..
will u plz send me
my mail id....
suresh@gaitview.com
tanks in advance
Thanks a lot, it really helped me.
I have one question why does the download file sometimes show in browser and sometimes as an attachment.
Post a Comment