JasperReport with OutputStream not exporting to PDF - java

I am using JasperReport to export a report to a PDF. The code runs fine with no exception messages showing up in the console/log. However, the report does not export to the browser. In other words, the report is being created, I just cannot download or gain access to it.
Here is the export code:
public void generatePDFReport(Map<String, Object> parameters, JRDataSource jrDataSource, String resource, String filename)
{
OutputStream os = null;
try{
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
os = response.getOutputStream();
InputStream reportTemplate = this.getClass().getClassLoader().getResourceAsStream(resource);
byte[] pdf = null;
try {
JasperDesign masterDesign = JRXmlLoader.load(reportTemplate);
masterReport = JasperCompileManager.compileReport(masterDesign);
masterReport.setWhenNoDataType(WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL);
JasperPrint masterPrint = JasperFillManager.fillReport(masterReport, parameters, jrDataSource);
pdf = JasperExportManager.exportReportToPdf(masterPrint);
} catch (JRException e) {
log.error(e);
}
response.setContentType("application/pdf");
response.setContentLength(pdf.length);
response.setHeader("Content-disposition", "attachment; filename=\""+filename+"\"");
context.responseComplete();
os.write(pdf);
pdf = null;
}catch(Exception e){
log.error(e);
}finally{
try{
os.flush();
os.close();
}catch(IOException e){
log.error(e);
}
}
}
I am almost 100% certain that there is nothing wrong with the code as it works fine for different reports (I run the same export code for several other reports and it works as expected for all of them except for this one).
Knowing this, I figured it must have something to do with the report itself. The report is a jrxml JasperReport file. The report was created using iReport. However, I modified the above code to simply save it to the downloads folder and the report is being created perfectly fine.
So, the problem is that the report is successfully being created in the backend but it is not being sent to the front-end (browser) as expected.
I am open to any suggestions as to why this report would not be working.

Running the code inside a bean is problematic because:
only one call to getOutputStream is allowed per HTTP request
the web framework (J2EE/JSF) has likely already written HTTP headers
the JSF page has likely already been written as HTML inside a temporary buffer (flushed upon calling responseComplete()).
the headers could be reset, but that won't help with the getOutputStream issue
calling responseComplete() flushes any HTML along with PDF content to the browser
Use a servlet. The send method of the servlet needn't be any more complex than:
protected void send(final byte[] content) throws IOException {
setContentLength(content.length);
try (final OutputStream out = getOutputStream()) {
out.write(content);
}
}
Also consider setting the cache so that stale reports are not possible:
protected void disableCache() {
// https://tools.ietf.org/html/rfc7234#section-7.1.3
setHeader(CACHE_CONTROL, "private, no-store, no-cache, must-revalidate");
// https://tools.ietf.org/html/rfc7234#section-5.3
setHeader(EXPIRES, "Thu, 01 Dec 1994 16:00:00 GMT");
// https://tools.ietf.org/html/rfc7234#section-5.4
setHeader(PRAGMA, "no-cache");
// https://tools.ietf.org/html/rfc7232#section-2.2
setHeader(LAST_MODIFIED, getServerTimestamp());
}
private String getServerTimestamp() {
final SimpleDateFormat rfc1123 = new SimpleDateFormat(DATE_RFC_1123, getDefault());
rfc1123.setTimeZone(getTimeZone("GMT"));
final Calendar calendar = Calendar.getInstance();
return rfc1123.format(calendar.getTime());
}
This implies, for example:
#WebServlet(
name = "ReportServlet",
urlPatterns = {PATH_SERVLET + "ReportServlet"}
)
public class ReportServlet extends AbstractServlet {
}
And then use a regular anchor link:
<h:outputLink value="/app/path/servlet/Reportservlet">Run Report</h:outputLink>
In summary, don't send binary report data by intercepting a request to a JSF page; use a servlet, instead.
Communications between servlets and JSF pages can be made via:
Session variables (HTTPSession on the servlet side)
URL parameters
Servlets have the advantage that the JSF overhead is completely avoided, which will make the report run faster from the user's perspective. Also, don't compile the report -- use the .jasper file directly, which will also have performance improvements. (I did not mean to imply using the .jrxml file was the problem, merely that it isn't a necessary step.)

I figured out a solution to my problem. Ultimately, I found that there was nothing wrong with the report generation code or the reports, but there was an ajax issue that was preventing the outputstream from exporting the report to the browser.

Related

How to download multiple files from a single directory in Java

How to download all files in the file directory when clicking the export or download at the same time?
At present, all the files in the file directory have been obtained, then all the files are placed in the list, and then the stream is written after traversing all the files. However, when importing the second file, it will report cannot reset buffer after response has been committed
The source of the problem is in this code: // response.reset();
Code:
String filePath = "/code/data/";
// Get all file addresses of the directory
List<String> filePathList = getFilePath(filePath);
//Create thread pool
for (String str : filePathList){
download(request, response, str);
}
private void download(HttpServletRequest request,
HttpServletResponse response,String filePath) {
File file = new File(filePath);
//Gets the file name.
String fileName = file.getName();
InputStream fis = null;
try {
fis = new FileInputStream(file);
request.setCharacterEncoding("UTF-8");
String agent = request.getHeader("User-Agent").toUpperCase();
if ((agent.indexOf("MSIE") > 0) || ((agent.indexOf("RV") != - 1) &&
(agent.indexOf("FIREFOX") == -1))) {
fileName = URLEncoder.encode(fileName, "UTF-8");
} else {
fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
}
// response.reset();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/force-download");
// Set forced download not to open
response.addHeader("Content-Disposition",
"attachment; filename=" + fileName);
response.setHeader("Content-Length", String.valueOf(file.length()));
byte[] b = new byte[1024];
int len;
while ((len = fis.read(b)) != - 1) {
response.getOutputStream().write(b, 0, len);
}
response.flushBuffer();
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
What are the good solutions Thanks
I have not read your code in detail because the bad formatting makes my head hurt.
However, from a superficial reading, it looks like this server-side code is trying to deliver multiple files in response to a single HTTP request.
AFAIK, that is not possible. The HTTP request / response model does not support this. It certainly does not allow a servlet to:
change response headers after the response output stream has been opened
do anything after the response output stream has been closed.
(Your code appears to be trying to do both of those things!)
So, you have to do it differently. Here are some possibilities:
On the server side, assemble all of the files to be downloaded into (say) a temporary ZIP file and then send that. Leave it to the user to unpack the ZIP file ... or not ... as they want.
This is often the best approach. Imagine how annoyed you would be if a few thousand separate files unexpectedly landed in your web browser's Downloads folder.
As 1. and also do something on the client side to transparently unpack the files from the ZIP and put them in the right place in the client's file system.
The "something" could be custom javascript embedded in the web page, or a custom client implemented in Java ... or any other language. (But in the former case, there may be a security issue in allowing sandboxed javascript to write files in arbitrary places without the user confirming each file ... tedious.)
You might be able to send a "multipart" document as the response. However from what I have read, most browsers don't support multipart for downloads; e.g. some browsers will discard all but the last part. (Note: multipart is not designed for this purpose ...)
Change things so that an HTTP request only downloads one file at a time from the directory, and add some client-side stuff to 1) fetch a list of files from the server and iterate the list, fetching each file.
See also: Download multiple files with a single action

Why am I downloading a file with no extension using servlet?

I'm downloading a file without an extension when I go to a servlet
This is code of doGet method (these are just test lines, don't take them seriously):
try {
PrintWriter pw = response.getWriter();
pw.write("test");
pw.println(request.getParameter("a"));
DAOFactory m = DAOFactory.getDAOFactory(1);
Connection conForTests = MySQLDAOFactory.getConnection();
UserDao s = m.getUserDao();
boolean check = s.validateUser("test1","test1",conForTests);
pw.write(String.valueOf(check));
User user = s.findUser("test1",conForTests);
int id = user.getUserId();
pw.write(11);
} catch (SQLException|IOException sqlException) {
System.out.println("asdsad");
sqlException.printStackTrace();
}
System.out.println("asdsad");
}
And I checked all the lines removing them line by line and I have found out that at this line:
pw.write(11);
And that's 11 was a user id so to not retrieve that id each time, I have just written 11. The servlet starts not showing a page, but downloading a file without an extension.
I checked that 11 number is staying for a Vertical Tab in ASCII table. Why is 11 code in ASCII table makes browser to not displaying but downloading file?
And that is content of this file:
Why am I downloading a file with no extension using servlet?
Because you just opened a response stream and started writing into it. In lay terms, you are just sending some bytes back to the browser, but the browser doesn't know what does bytes are. Is it html? Is it plain text? Is it an image? Some other thing?
So before starting to write the response, you need to say what that response is by setting a content type. Replace this code of yours:
try {
PrintWriter pw = response.getWriter();
....
with:
try {
response.setContentType("text/html")
PrintWriter pw = response.getWriter();
....
or whatever content type you prefer (a text/plain can also work for what you are doing).
See also:
what is the use of "response.setContentType("text/html")" in servlet
Java - Default contentType for Servlet

Tomcat HttpServletResponse writing to getOutputStream() always causes OutOfMemory error for large file downloads [duplicate]

I am using this code to download an existing file from the server on Liferay (6.2) into a local pc:
`
File file = getFile(diskImage.getImageType(), diskImage.getId());
HttpServletRequest httpReq = PortalUtil.getHttpServletRequest(request);
HttpServletResponse httpResp = PortalUtil.getHttpServletResponse(response);
httpResp.setContentType("application/octet-stream");
httpResp.setHeader("Content-Transfer-Encoding", "binary");
httpResp.setHeader("Content-Length", String.valueOf(file.length()));
httpResp.setHeader("Content-Disposition", "attachment; filename=" + file.getName());
try (InputStream input = new FileInputStream(file)) {
ServletResponseUtil.sendFile(httpReq, httpResp, file.getName(), input, "application/octet-stream");
} catch (Exception e) {
throw new FilesManagerException(e);
}
}
`
This code works fine only for small files. But downloading large files (cca 2GB) throws javax.portlet.PortletException: Error occurred during request processing: Java heap space.
How to fix this code so it works properly for larger files as well?
I guess that the suitable approach would be to use some kind of a buffer for large files and I try it but it wouldn't work even for the smaller files afterwards.
First of all: I'm assuming you're doing this in a render method - and this is just plain wrong. Sooner or later this will break, because you don't have control over the output stream: It might already be committed and transmit data to the browser when your portlet starts to render. In render you always must generate a portlet's HTML code.
Instead, you should go to the resource serving phase of a portlet. With the ResourceRequest and ResourceResponse, you have a very similar support for setting mimetypes as with HttpServletResponse.
And for exactly that reason, ServletResponseUtil is indeed the wrong place to look for. If you use anything from Liferay, you should look for PortletResponseUtil. There are various sendFile methods that accept a byte[], others accept a stream or a file. I'd recommend to try these, if they still fail, look at the implementation you are ending up with. In the worst case, just don't use any of the Util methods. Copying content from one stream to another is not too bad. (Actually, you give no clue about the static type of your variable input in the question: If that's a byte[], there's your solution)
You might want to file an issue with Liferay, if indeed the pure stream-transfer does read the whole file into memory, but your quickfix (in case this is indeed a bug) would be to copy the data yourself.
Thanks for thoughts, finally I used PortletResponseUtil.sendFile(...); method and changed actionURL to responseURL in .jsp file. So that I implemented serveResource()with above mentioned method. It seems that everything is working fine.
ServletResponseUtil.sendFile(httpReq, httpResp, file.getName(), input, "application/octet-stream"); what's this?
Don't read a file once time.Use a buffer.
response.reset();
response.setContentType("application/x-download");
response.addHeader("Content-Disposition","attachment;filename="+new String(filename.getBytes(),"utf-8"));
response.addHeader("Content-Length",""+file.length());
OutputStream toClient=new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
byte[] buffer=new byte[1024*1024*4];
int i=-1;
while((i=fis.read(buffer))!=-1){
toClient.write(buffer,0,i);
}
fis.close();
toClient.flush();
toClient.close();

File Downloads To Browser

I have been working on creating a series of buttons that uploads, downloads, and deletes files in Spring MVC, with JSP pages, and Java this past few days. I have the Upload and Delete working perfectly, and just got the download almost working. I stress almost because the download comes with a very odd condition.
If I upload say an exe or a jar file, and then go back and try and download it. A box will show up asking me if I want to open it or save it. If I want to do either it's not corrupted or anything, it's just fine.
If, however, the file is text based, as in a PDF, TXT, .doc, even XML, a browser tab will open, and it will show up in there.
So can anyone point me in the direction on how I might fix this?
The first block of code is my controller method, the second is the line in my jsp that triggers the button.
#RequestMapping("/FileDownload")
public ModelAndView FileDownload(
#RequestParam(value = "FileID", required = false) int fileID,
#RequestParam(value = "theFile", required = false) MultipartFile thefile,
#ModelAttribute("fileAttachment") #Valid fileAttachment, BindingResult result, HttpServletResponse response){
ModelAndView mav = new ModelAndView();
fileAttachment doc = attachmentService.getFileAttachment(fileID);
try {
response.setHeader("Content- Disposition", "inline;filename=\""
+ doc.getFileName() + "\"");
OutputStream out = response.getOutputStream();
response.setContentType(doc.getFileType());
FileCopyUtils.copy(doc.getFileContent(), response.getOutputStream());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
Here is the line that configures the button in the jsp
<button name="FileDownloadd" type="button" value="Download" onClick="location.href=FileDownload.html?FileID=${fileattach.FileID}'">Download</button> </td>
Marc's comment above was the answer. Since it was a comment, and I want to close this. I will post it in quotes.
you're telling the browser to display it inline. That means if the browser >knows how to render the file's contents, it will. text/pdf/xml can be rendered >directly by a browser (or at least via a plugin). If you want to force a >download, then use attachment as your disposition, and/or force a mime-type >like application/octet-stream, which the browser WON'T try to render.

Running JasperViewer on Tomcat as part of a web application

I have learned that JasperViewer (default preview component of JasperReports) is a Swing component, so is there any way to convert or embed it in a web application? Some say I should use Java Web Start, but from what i have learned from this link JWS is useful to download and install an application on client machine and this is not our case. the other work around that it may work (maybe just in theory) is converting jFrame to jApplet as briefly described in this link
Have you tried any of these solutions and did they work?
Do you know any other solution to this problem?
If you know how to generate a report, you can easily do it inside a servlet and send the generated file to the client. Using a JWS application or an Applet would most likely mean that the report is generated client-side and that the raw data plus all the dependencies are also available to the client.
The code below assumes that you're generating a PDF file
public class ReportServlet extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// initialize your report objects here
JasperReport jasperReport =
JasperPrint print =
JRPdfExporter exporter = new JRPdfExporter();
exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, resp.getOutputStream());
resp.setContentType("application/pdf");
exporter.exportReport();
} catch (Exception e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error generating report : " + e.getClass() + " " + e.getMessage());
}
}
You can extend the example above to support multiple export formats by setting the correct content type and using the matching JRXYZExporter (JRHtmlExporter, JExcelApiExporter,...)
If you need something more customizable, you might also want to look into Jasper Server

Categories

Resources