I use JSF and I have an h:commandButton to prompt for a file download. The file is in a PDF format. The downloaded file has correct number of pages but they are blank.
When the file is opened in the browser I get this message:
This PDF document might not be displayed correctly.
This is my commandButton:
<h:form>
<h:commandButton action="#{fileDownloadView.fileDownloadView}" value="Download"/>
</h:form>
And this is my class:
#ManagedBean
public class FileDownloadView {
private static final String FILENAME = "manual.pdf";
private static final String CONTENT_TYPE = "application/pdf";
public FileDownloadView() throws IOException, DocumentException {
Resource resource = new ClassPathResource(FILENAME);
InputStream stream = resource.getInputStream();
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
externalContext.responseReset();
externalContext.setResponseContentType(CONTENT_TYPE);
externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + FILENAME + "\"");
OutputStream outputStream = externalContext.getResponseOutputStream();
byte[] bytes = IOUtils.toByteArray(stream);
outputStream.write(bytes);
facesContext.responseComplete();
}
}
What could be the cause of this?
EDIT:
Claimed duplicate post gives this pice of code:
public void download() throws IOException {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.
OutputStream output = ec.getResponseOutputStream();
// Now you can write the InputStream of the file to the above OutputStream the usual way.
// ...
fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}
If you look closer my code is the same with the exception of the comment part that writes:
// Now you can write the InputStream of the file to the above
OutputStream the usual way.
// ...
What i have writen for this is
byte[] bytes = IOUtils.toByteArray(stream);
outputStream.write(bytes);
What is wrong with that? Isn't that a byte array that is written in the output stream? Why is this not working?
You are right that the body of your beans' constructor does the same as the body of the download method given in the example.
Your command link <h:commandButton action="#{fileDownloadView.fileDownloadView}" .../> action method expression is trying to find and invoke a method named fileDownloadView on your bean but that method does not exist.
Your public FileDownloadView is a constructor because it has no return value type and the same name as the class. If you'd change that to public void download the bean would have no explicit constructor anymore but finally a method that is invokable by a command button like this:
<h:commandButton action="#{fileDownloadView.download}" value="Download"/>
Capitalization matters, so don't call the method public void Download or the like.
I'm not sure what actually happens in your current implementation, but I guess while #{fileDownloadView.fileDownloadView} is beeing resolved, there is a new instance created of the bean fileDownloadView from first part of the expression and the code in constructor is successfully executed. Some CPU cylcles later the ELResolver fails to resolve second .fileDownloadView part of the expression and throws an exception which kind of messes things up.
Related
In a Java (only) Play 2.3 project we need to send a non-chunked response of an InputStream directly to the client. The InputStream comes from a remote service from which we want to stream directly to the client, without blocking or buffering to a local file. Since we know the size before reading the input stream, we do not want a chunked response.
What is the best way to return a result for an input stream with a known size? (preferable without using Scala).
When looking at the default ok(file, ..) method for returning File objects it goes deep into play internals which are only accessible from scala, and it uses the play-internal execution context which can't even be accessed from outside. Would be nice if it would work identical, just with an InputStream.
FWIW I have now found a way to serve an InputStream, which basically duplicates the logic which the Results.ok(File) method to allow directly passing in an InputStream.
The key is to use the scala call to create an Enumerator from an InputStream: play.api.libs.iteratee.Enumerator$.MODULE$.fromStream
private final MessageDispatcher fileServeContext = Akka.system().dispatchers().lookup("file-serve-context");
protected void serveInputStream(InputStream inputStream, String fileName, long contentLength) {
response().setHeader(
HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"");
// Set Content-Type header based on file extension.
scala.Option<String> contentType = MimeTypes.forFileName(fileName);
if (contentType.isDefined()) {
response().setHeader(CONTENT_TYPE, contentType.get());
} else {
response().setHeader(CONTENT_TYPE, ContentType.DEFAULT_BINARY.getMimeType());
}
response().setHeader(CONTENT_LENGTH, Long.toString(contentLength));
return new WrappedScalaResult(new play.api.mvc.Result(
new ResponseHeader(StatusCode.OK, toScalaMap(response().getHeaders())),
// Enumerator.fromStream() will also close the input stream once it is done.
play.api.libs.iteratee.Enumerator$.MODULE$.fromStream(
inputStream,
FILE_SERVE_CHUNK_SIZE,
fileServeContext),
play.api.mvc.HttpConnection.KeepAlive()));
}
/**
* A simple Result which wraps a scala result so we can call it from our java controllers.
*/
private static class WrappedScalaResult implements Result {
private play.api.mvc.Result scalaResult;
public WrappedScalaResult(play.api.mvc.Result scalaResult) {
this.scalaResult = scalaResult;
}
#Override
public play.api.mvc.Result toScala() {
return scalaResult;
}
}
I want to create a link that would initiate a file download which would be asynchronous to the page itself, i.e. I want the page not to be locked during the file download. Should I make it be initiated outside wicket? Or is there something inside wicket that would let me set up a resource stream which would bypass the page locks?
Things I tried:
DownloadLink - locks the page, as stated in its doc. This was my starting point.
ResourceLink - did not state the locking explicitly in the doc, so I tried this, but it also locked the page.
At this point I've investigated the code of both links a bit and noticed they both schedule the download via ResourceStreamRequestHandler. Expecting that his kind of behavior could be just handler-specific I've attempted to schedule a custom handler I've written:
private void sendFile(final File file) throws IOException {
IRequestHandler fileDownloadHandler = new IRequestHandler() {
#Override
public void respond(IRequestCycle requestCycle) {
WebResponse response = (WebResponse) requestCycle.getResponse();
OutputStream outStream = response.getOutputStream();
response.setContentType("audio/x-wav");
response.setContentLength((int)file.length());
String fileName = "Somethingsomething.wav";
// sets HTTP header
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
byte[] byteBuffer = new byte[1024];
DataInputStream in = null;
try {
in = new DataInputStream(new FileInputStream(file));
int length = 0;
// reads the file's bytes and writes them to the response stream
while ((in != null) && ((length = in.read(byteBuffer)) != -1))
{
outStream.write(byteBuffer,0,length);
}
in.close();
outStream.close();
} catch (IOException e) {
throw new PortalError("IOException trying to write the response", e);
}
}
#Override
public void detach(IRequestCycle requestCycle) {
}
};
getRequestCycle().scheduleRequestHandlerAfterCurrent(fileDownloadHandler);
}
This did not quite work either, so I've investigated further. I've noticed that unlike I expected, the "scheduled" request handlers would not get executed on a separate request, as I expected, but on the same one. I figured that it must be that the page gets locked for the first handler and then remains locked while the second one is executing as well. So I've attempted to force the download handler into a separate request (via an ajax behaviour):
public void startDownload(AjaxRequestTarget target) throws DownloadTargetNotFoundException{
target.appendJavaScript("setTimeout(\"window.location.href='" + getCallbackUrl() + "'\", 100);");
}
#Override
public void onRequest() {
sendFile(getFile());
logger.debug("Download initiated");
}
I've found this here and hoped it could potentially be what I've been looking for. However, unsurprisingly so, the page gets locked still (I would imagine because the behaviour still has to be retrieved from the page, for which the page lock has to be acquired).
I'm at a loss where I should be looking next, especially after all this time trying to get a simple download link working. I was considering creating another web filter one layer above wicket, which could be signaled from within wicket to create the download after the wicket filter is finished with its work (and hence the page lock is already released), but that seems a bit excessive for a task like this.
Any suggestions are welcome.
You have to download from a resource, see
http://wicketinaction.com/2012/11/uploading-files-to-wicket-iresource/ and read http://wicket.apache.org/guide/guide/resources.html
I have an h:datatable that contains a column with a link that will call the backing bean and "should" return a PDF document generated.
The column looks like this :
<h:form>
...
<h:datatable>
...
<h:column >
<h:commandLink action="#{bean.downloadPDF}" target="_blank" >
<f:param name="value1" value="#{bean.val1}"/>
<f:param name="value2" value="#{bean.val2}"/>
<f:param name="value3" value="#{bean.val3}"/>
<h:graphicImage name="certificate.jpg" library="images"/>
</h:commandLink>
</h:column>
...
</h:datatable>
...
</h:form>
I have no javascript errors on my page (according to chrome and firebug). The backing bean looks like this :
public void downloadPDF() {
...
File outputPDF = new File(outputFileName);
//Get ready to return pdf to user
BufferedInputStream input = null;
BufferedOutputStream output = null;
try {
// Open file.
input = new BufferedInputStream(new FileInputStream(outputPDF), 10240);
//Return PDF to user
// Init servlet response.
response.reset();
response.setHeader("Content-Type", "application/pdf");
response.setHeader("Content-Length", String.valueOf(outputPDF.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + pdfName + "\"");
output = new BufferedOutputStream(response.getOutputStream(), 10240);
// Write file contents to response.
byte[] buffer = new byte[10240];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
// Finalize task.
output.flush();
} catch(IOException e) {
e.printStackTrace();
} finally {
output.close();
input.close();
}
facesContext.responseComplete();
}
Now, I know my PDF generation works because I can create the custom PDF and save it on the drive.
Then, if I add <f:ajax/> to the h:commandLink The method is called. Without that, it reloads the current page without the action method ever being called.
I have tried a few different things... with or without any f:params. Adding a String return value to the downloadPDF() function. EDIT : Taking away the target="_blank" parameter. Using actionListener instead of action. When using action, the bean method does not get called, but I get this error message in the h:messages : Conversion Error setting value '' for 'null Converter'..
Cannot get it to call the function. What I am hoping to get is that when one clicks on the downloadPDF link, it opens a new window and downloads the PDF.
Any help would be greatly appreciated.
EDIT 2 / Solution (temporary)
I have managed to get this working using the attribute immediate="true". Still havn't found exact field that is giving me the Conversion Error, but am assuming is a field in the form that does not need to be submitted for this specific function.
Logic associated with the question was good. The issue was with other items within the form that were staying null. As with the Edit 2, adding the immediate="true" made it so that the fields I needed alone were sent to the servlet.
Lesson learned
When submitting part of a form in JSF, make sure only the fields you need are being submitted. Especially in large forms and integrating ajax with sub sections of the form.
I need to implement a call to a method that I have in my controller. But I want to call this method from a scriptlet and I don't know how to do it. I am trying to export data that I get from the server to CSV.
This is the scriptlet that I have so far:
<%
String csvDataIn = request.getParameter("exportCSVParam");
String csvFileName = request.getParameter("exportCSVFileName");
if (csvFileName == null || csvFileName == "") csvFileName = "export.csv";
String strHeader = "attachment; filename=" + csvFileName;
String contentType = "application/octet-stream";
response.setContentType(contentType);
response.addHeader("content-disposition",strHeader);
ServletOutputStream ostr = response.getOutputStream();
String data=csvDataIn;//DATA GOES HERE;
ostr.write(data.getBytes("ISO-8859-1"));
ostr.flush();
ostr.close();
%>
Assume that I want to call a method getDataAsCsv() that I have in my controller that returns a String with the CSV data that I want to print in that file. Lines 1 and 2 (csvDataIn, csvFileName) should be deleted since I am not going to send parameters to this jsp. How do you do that?. How do you bind the controller bean with this scriptlet.
I am new to spring and I am still learning about this. Probably the solution is very simple but I am stuck with this.
You can't, because you shouldn't. All this code should go in the controller.
(technically, you can have a JSTL function and call it, or simply call a static method, or even get the controller with WebApplicationContextUtils.getRequiredWebApplicationContext(..).getBean(..), but all these will be ugly)
I got the following to work already:
User can upload a file (i.e. a compressed archive)
User can uncompress the file on the server
User can execute some stuff on these files, which results in more files to be generated
Now I need to get step 4 to work:
User can download the files to his own computer again
Can anyone give me a hint? I tried to understand the stuff I found on Google, but it does not work quite as expected. Do I have to set a content type? When I set application/octet stream only txt and csv files would display correctly (in the browser, not as download popup as I wanted) other files would not work...
JSP:
<a4j:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
<f:param name="file" value="#{file.absoluteFilename}" />
</a4j:commandLink>
appController:
public String downloadFile() {
String filename = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("file");
File file = new File(filename);
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
writeOutContent(response, file, file.getName());
FacesContext.getCurrentInstance().responseComplete();
return null;
}
private void writeOutContent(final HttpServletResponse res, final File content, final String theFilename) {
if (content == null) {
return;
}
try {
res.setHeader("Pragma", "no-cache");
res.setDateHeader("Expires", 0);
res.setHeader("Content-disposition", "attachment; filename=" + theFilename);
FileInputStream fis = new FileInputStream(content);
ServletOutputStream os = res.getOutputStream();
int bt = fis.read();
while (bt != -1) {
os.write(bt);
bt = fis.read();
}
os.flush();
fis.close();
os.close();
} catch (final IOException ex) {
Logger.getLogger(ApplicationController.class.getName()).log(Level.SEVERE, null, ex);
}
}
Your concrete problem is that you're attempting to download files by Ajax. This is not correct. JavaScript can't deal with binary responses nor has it any facilities to force a Save As dialogue. You need to make it a normal synchronous request instead so that it's the webbrowser itself who has to deal with it.
<h:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
<f:param name="file" value="#{file.absoluteFilename}" />
</h:commandLink>
As to setting the content type, if you have a file name with extension at your hands, you could use ServletContext#getMimeType() to resolve it based on <mime-mapping> in web.xml (either the server's default one or your webapp's one).
ServletContext servletContext = (ServletContext) externalContext.getContext();
String contentType = servletContext.getMimeType(file.getName());
if (contentType == null) {
contentType = "application/octet-stream";
}
response.setContentType(contentType);
// ...
(note that I assume that you're using JSF 1.x, seeing the way how you obtained the servlet response, you could since JSF 2.x otherwise also use ExternalContext#getMimeType())
I have done the step 4 some weeks ago, and let me give you some advices:
Use a link html tag component. For this, I recommend the a4j:htmlCommandLink tag component (it's like the common h:commandLink with the difference that the <f:param /> components are always rendered, you can check more in the component documentation).
If you don't know the type of the file to download (), then you must set the Content as application/octet-stream.
After setting up the file to download to your response, you should set that the response has been completed.
I'll put my Backing Bean code for this request:
public void descargaArchivo() {
//sorry but the programming standard says that we MUST declare
//our variables at the beginning of any function =(
HttpServletResponse objResponse;
FileInputStream objFileInputStream;
String strNombreCompletoArchivo, strNombreArchivo;
byte[] arrDatosArchivo;
try {
//Here I get the <f:param> with the full name of the file. It encapsulates
// the Faces.getCurrentInstance... call.
strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
//The function obtieneNombreArchivo retrieves the name of the file
//based on the full name and the file separator (/ for Windows, \ for Linux)
strNombreArchivo = UFuncionesGenerales.obtieneNombreArchivo(strNombreCompletoArchivo);
//Getting the response from Faces.getCurrentInstance...
objResponse = UManejadorSesionWeb.obtieneHttpResponse();
//Setting up the response content type and header (don't set the length!)
objResponse.setContentType("application/octet-stream");
objResponse.setHeader("Content-Disposition", "attachment; filename=\"" + strNombreArchivo + "\"");
//Create the FileInputStream for the file to download
objFileInputStream = new FileInputStream(strNombreCompletoArchivo);
//Setting the file on the response
arrDatosArchivo = new byte[UConstante.BUFFER_SIZE];
while(objFileInputStream.read(arrDatosArchivo, 0, UConstante.BUFFER_SIZE) != -1) {
objResponse.getOutputStream().write(arrDatosArchivo, 0, UConstante.BUFFER_SIZE);
}
objFileInputStream.close();
objResponse.getOutputStream().flush();
objResponse.getOutputStream().close();
//Telling the framework that the response has been completed.
FacesContext.getCurrentInstance().responseComplete();
} catch (Exception objEx) {
//manage the errors...
}
}
//The constant used for byte array size
public class UConstante {
public static final int BUFFER_SIZE = 2048;
}
The jsp fragment I used looks like this:
<a4j:htmlCommandLink rendered="#{documento.rutaDestino != null}"
action="#{documentoRequerido.descargaArchivo}">
<f:param name="nombreCompletoArchivo" value="#{documento.rutaDestino}" />
<h:graphicImage value="/Resource/iconos/mover-abajo.png" styleClass="pic" />
</a4j:htmlCommandLink>
Hope this helps you.
EDIT: I had some spare time, sorry but we are kinda busy in the project. The Java code was updated and tested in IE8, Firefox 9 and 10, and Chrome 16. For the value of buffer size constant, I did some research and found a good answer in this site.
P.S.: I don't take this as a competition, I just try to help people when I can. If my code is not good then thanks for letting me know it, that's the better way for everyone to grow better and healthy :).
EDIT: Thanks to #lisa
To achieve the following manually, just changing this part in the snippet
//strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
String parameterStr = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap.get("nombreCompletoArchivo");
strNombreCompletoArchivo = parameterStr;
You don't need the richfaces a4j:commandLink tag for the download, the standard jsf tag h:commandLink would be enough.
Then make sure you have the following headers set on your response (you can check with firebug in firefox):
Content-Disposition attachment; filename="your_file_name.xxx"
Content-Type application/xxx
Content-Length 1234
Content-Length: number of bytes.