spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.67k stars 38.15k forks source link

CacheSeconds not working properly with jasper reports view [SPR-4325] #9004

Closed spring-projects-issues closed 12 years ago

spring-projects-issues commented 16 years ago

Shahid Zaman opened SPR-4325 and commented

From the spring forum post:


General introduction:


For our project, we are using the combination of spring 2.0 and jasper reports 2.0.2 for reporting. We rendered our report via an AbstractController and set the cacheSeconds property to zero (we know that for excel/pdf due to a bug in IE6 we need to set the cacheSeconds to -1, but say for now we are only interested about the HTML report without caching). But the problem is when the user logged out from our application and clicked the back button s/he can see the report from cache, but there should be no cache. (FYI: In other pages of our application we use the cacheSeconds property to prevent caching and it worked fine). Then we investigate bit further by using an application WebScarab to intercept the response and watched that in the report response only "Pragma" header is set, where as cacheSeconds instructs to set three headers in default settings - "Pragma", "Cache-Control" and "Expires".

We have manually setup those three headers in the view (JasperReportsMultiFormatView) in controller and it works fine. So where is the problem? why setting cacheSeconds is not sufficient to set those headers? Does Jasper overwrite/reset headers which are already set by spring or is it a spring-jasper integration code problem?

Detailed analysis:


I debugged the spring code to see where the response cache headers got lost after set via the WebContentGenerator. In WebContentGenerator the following method adds the cache prevention headers to response:

Code:

protected final void preventCaching(HttpServletResponse response) { response.setHeader(HEADER_PRAGMA, "No-cache"); if (this.useExpiresHeader) { // HTTP 1.0 header response.setDateHeader(HEADER_EXPIRES, 1L); } if (this.useCacheControlHeader) { // HTTP 1.1 header: "no-cache" is the standard value, // "no-store" is necessary to prevent caching on FireFox. response.setHeader(HEADER_CACHE_CONTROL, "no-cache"); response.addHeader(HEADER_CACHE_CONTROL, "no-store"); } }

But in the AbstractJasperReportsView the response is reset and the headers are populated again in the following way in renderMergedOutputModel method:

Code:

protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)
        throws Exception {

    if (this.subReports != null) {
        // Expose sub-reports as model attributes.
        model.putAll(this.subReports);

        // Transform any collections etc into JRDataSources for sub reports.
        if (this.subReportDataKeys != null) {
            for (int i = 0; i < this.subReportDataKeys.length; i++) {
                String key = this.subReportDataKeys[i];
                model.put(key, convertReportData(model.get(key)));
            }
        }
    }

    // Expose Spring-managed Locale and MessageSource.
    exposeLocalizationContext(model, request);

    // Fill the report.
    JasperPrint filledReport = fillReport(model);
    postProcessReport(filledReport, model);

    // Prepare response and render report.
    response.reset();
    populateHeaders(response);
    renderReport(filledReport, model, response);
}

I don't understand actually why do we need to call the reset method? The reset method call clears all the previous headers in the response (which includes the cache headers set previously in WebContentGenerator). Setting the headers manually in the view cause no problem as after the reset call, in the populateHeaders method headers are added from view's headers property (so the cache headers are set again if you add those to headers property prior).

Code:

private void populateHeaders(HttpServletResponse response) { // Apply the headers to the response. for (Enumeration en = this.headers.propertyNames(); en.hasMoreElements();) { String key = (String) en.nextElement(); response.addHeader(key, this.headers.getProperty(key)); } }

Rob, Juergen - Is this a problem? Is the reset call necessary? If it's necessary then shouldn't we check the cache headers before resetting the response and keep somewhere to add again later?


Affects: 2.0.5, 2.5 final

Attachments:

Issue Links:

spring-projects-issues commented 16 years ago

Juergen Hoeller commented

I've finally revised this workaround for IE's HTTPS handling thoroughly...

I've added a "prepareResponse"/"generatesBinaryContent" method pair to AbstractView, implementing the HTTPS cache header workaround in a generic fashion if the view class indicates that it generates binary content. What we do there now - only in case of an actual HTTPS request - is setting "Pragma: private" and "Cache-Control: private, must-revalidate", which seems to be the common solution for this IE problem. This means that any cache headers - whether set by Spring configuration or set by Tomcat - will be overridden for views that explicitly indicate binary content (in particular for PDF and Excel documents).

AbstractPdfView and Abstract(J)ExcelView simply participate in AbstractView's HTTPS cache header workaround for IE now, and so does AbstractJasperReportsView instead of its hard response reset. So you should be able to specify custom cache settings now for HTTP requests that generate documents, except for HTTPS requests where only the "Expires" header will be taken from your custom configuration - "Pragma" and "Cache-Control" will always be set to "private" for such a request (which makes sense in general, not just as an IE workaround; otherwise you may override the View's "prepareResponse" method accordingly).

This will be available in tonight's 2.5.2 snapshot (-385 or above; available from http://static.springframework.org/downloads/nightly/snapshot-download.php?project=SPR). Please give it a try and let me know whether it works for you!

Juergen

spring-projects-issues commented 16 years ago

Shahid Zaman commented

Juergen,

Thanks. It works perfectly for my scenario (setting cache seconds zero prevents caching for Jasper Reports). I have couple of naive questions. Is the issue # #5645 some how related with this "Excel View Error With Internet Explorer" (http://forum.springframework.org/showthread.php?t=24466) ?

Now AbstractExcelView, AbstractPdfView as well as AbstractJasperReportsView returns true for generatesBinaryContent and prepareResponse method handle this value appropriately. Previously AbstractJasperReportsView do this by a response.reset(), which is not needed now. I can't find looking at the 2.5 version code, what was the previous workaround for AbstractExcelView and AbstractPdfView?

Thanks.

Shahid

spring-projects-issues commented 16 years ago

Juergen Hoeller commented

That forum thread might indeed be related. Pre 2.5.2, we didn't have a built-in workaround for AbstractExcelView and AbstractPdfView at all... So people had to care for those headers - or the resetting of those headers - in their custom subclasses. This should hopefully be resolved with a consistent solution for all those views now.

Juergen

spring-projects-issues commented 16 years ago

Shahid Zaman commented

Then the problem of downloading binary files i.e. an error message in IE ("C:\Temp\Temporary Internet Files\Content.IE5\S1244HKJD\sheet[1].xls chould not be found. check the spelling of the file name and verify that the file location is correct.") shouldn't appear. The caching problem is resolved for the HTML view using cacheSeconds 0, but I still got the error message downloading the PDF or Excel version of the report using the same view.

spring-projects-issues commented 16 years ago

Juergen Hoeller commented

So with no cacheSeconds property specified, displaying basically works for any kind of view, with HTTP and HTTPs - except that it's not preventing caching?

With cacheSeconds=0, it works for HTML views but not for PDF/Excel views? Does this apply to HTTPS only or to HTTP as well? I assume for HTTPS only, in which case this would indicate that well-known IE bug?

I could imagine that the "Expires" header still triggers undesirable IE behavior here. Does it make a difference if you specify useExpiresHeader=false next to your cacheSeconds setting?

Juergen

spring-projects-issues commented 16 years ago

Shahid Zaman commented

Hi,

Finally I got time to throughly test this. I have tested both with spring 2.5 stable release and the latest nightly build. I attached the test result. Let me know, it its not explanatory to you. Now the only problem seems with PDF files and for HTTP requests.

Shahid

spring-projects-issues commented 16 years ago

Shahid Zaman commented

Test result for latest nightly build and 2.5 stable release.

spring-projects-issues commented 16 years ago

Juergen Hoeller commented

Thanks for the tests! It seems like the special "private" headers that allow for temporary storage on the clients need to be set independent from HTTP/HTTPS mode then. I've revised our code to always send those headers for download views now, not just for HTTPS requests.

This should be available in tonight's snapshot (-392 or above). Please give it a try, in particular with PDF/Excel files served by plain HTTP requests...

Juergen

spring-projects-issues commented 16 years ago

Shahid Zaman commented

Hi, now its working fine and no need to set up cacheSeconds to -1 for binary files. Thanks for the short but useful fix.

BTW, I couldn't run with the latest nightly build, got an exception like the following: java.lang.IllegalStateException: ApplicationObjectSupport instance [org.springframework.web.servlet.view.InternalResourceView: unnamed; URL [/fpage/dashboar d]] does not run in an ApplicationContext at org.springframework.context.support.ApplicationObjectSupport.getApplicationContext(ApplicationObjectSupport.java:125) at org.springframework.web.context.support.WebApplicationObjectSupport.getWebApplicationContext(WebApplicationObjectSupport.java:70) at org.springframework.web.context.support.WebApplicationObjectSupport.getServletContext(WebApplicationObjectSupport.java:87) at org.springframework.web.servlet.view.InternalResourceView.exposeForwardRequestAttributes(InternalResourceView.java:292) at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:186) at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:252) ................................................................................................ ................................................................................................

Its working for the new fix for GlassFish as described in the change log - "InternalResourceView only exposes forward attributes when running on Servlet <2.5 (for GlassFish compatibility)". Later I commented out the line 275, 276 and 278 of InternalResourceView to do my test.

Shahid

spring-projects-issues commented 16 years ago

Juergen Hoeller commented

Thanks for the feedback! Indeed, InternalResourceView should keep working with direct instantiation as well, i.e. not rely on the the availability of a ServletContext (since all it really does there is a version number check). I've fixed this for the next 2.5.2 snapshot.

Juergen