/**
* Copyright (C) 2011-2015 The XDocReport Team <xdocreport@googlegroups.com>
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package fr.opensagres.web.servlet.view.xdocreport;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.io.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
import fr.opensagres.xdocreport.converter.ConverterTypeTo;
import fr.opensagres.xdocreport.converter.IConverter;
import fr.opensagres.xdocreport.converter.MimeMapping;
import fr.opensagres.xdocreport.converter.Options;
import fr.opensagres.xdocreport.converter.XDocConverterException;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.core.utils.HttpHeaderUtils;
import fr.opensagres.xdocreport.core.utils.StringUtils;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.dump.DumperOptions;
import fr.opensagres.xdocreport.document.dump.IDumper;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
/**
* Spring MVC {@link View} implementation with XDocReport. You can use it like this with Spring bean :
*
* <pre>
* <bean id="reportConfiguration"
* class="fr.opensagres.xdocreport.samples.reporting.springmvc.report.DocxProjectWithVelocityListConfiguration" />
*
* <bean id="docxReport"
* class="fr.opensagres.web.servlet.view.xdocreport.XDocReportView"
* p:url="classpath:fr/opensagres/xdocreport/samples/reporting/springmvc/report/DocxProjectWithVelocityList.docx"
* p:templateEngineId="Velocity" >
* <property name="configuration" ref="reportConfiguration" />
* </bean>
*
* </pre>
*/
public class XDocReportView
extends AbstractUrlBasedView
{
/**
* If set to true, this view load the report when Spring {@link ApplicationContext} is initialized.
*/
private boolean loadOnInit = true;
/**
* The reportId of the report. Fill this report id if you wish to use {@link XDocReportRegistry#getReport(String)}
* in Java code on other part of your application.
*/
private String reportId;
/**
* The template engine id (Velocity|Freemarker) to use. This property is required.
*/
private String templateEngineId;
/**
* If set to true, the report is cached after loading of the report.
*/
private boolean cacheReport = true;
/**
* The convert to if the report should be converted to other format. See constants at {@link ConverterTypeTo}.
*/
private String convertTo;
/**
* The convert via if the report should be converted to other format. See constants at {@link ConverterTypeVia}.
*/
private String convertVia;
/**
* True if the content disposition should be generated in the HTTP header (download) or not (view teh report in teh
* browser).
*/
private boolean generateContentDisposition = true;
/**
* The configuration to use after the report is loaded.
*/
private IXDocReportConfiguration configuration;
/**
* Set to true, if the report must be loaded when Spring {@link ApplicationContext} is initialized and false
* otherwise. By default, the report is loaded when {@link ApplicationContext} is initialized.
*
* @param loadOnInit
*/
public void setLoadOnInit( boolean loadOnInit )
{
this.loadOnInit = loadOnInit;
}
/**
* Returns true, if the report must be loaded when Spring {@link ApplicationContext} is initialized and false
* otherwise. By default, the report is loaded when {@link ApplicationContext} is initialized.
*
* @return
*/
public boolean isLoadOnInit()
{
return loadOnInit;
}
/**
* Set the reportId of the report. Fill this report id if you wish to use
* {@link XDocReportRegistry#getReport(String)} in Java code on other part of your application.
*
* @param reportId the report id.
*/
public void setReportId( String reportId )
{
this.reportId = reportId;
}
/**
* Returns the id of the report if not null otherwise returns the bean/@id.
*
* @param request
* @return
*/
public String getReportId()
{
if ( reportId != null )
{
return reportId;
}
return getBeanName();
}
/**
* Set the template engine id (Velocity|Freemarker) to use. This property is required.
*
* @param templateEngineId the template engine id (Velocity|Freemarker) to use. This property is required.
*/
public void setTemplateEngineId( String templateEngineId )
{
this.templateEngineId = templateEngineId;
}
/**
* Returns the template engine id to use.
*
* @return
*/
protected String getTemplateEngineId()
{
return templateEngineId;
}
/**
* Set to true, if the report must be cached after loading of the report and false otherwise. By default the report
* is cached.
*
* @param cacheReport
*/
public void setCacheReport( boolean cacheReport )
{
this.cacheReport = cacheReport;
}
/**
* Returns true, if the report must be cached after loading of the report and false otherwise.
*/
public boolean isCacheReport()
{
return cacheReport;
}
/**
* Set the convert to if the report should be converted to other format. See constants at {@link ConverterTypeTo}.
*
* @param convertTo
*/
public void setConvertTo( String convertTo )
{
this.convertTo = convertTo;
}
/**
* Return convert to if the report should be converted to other format and null otherwise.
*
* @return
*/
public String getConvertTo()
{
return convertTo;
}
/**
* Set the convert via if the report should be converted to other format. See constants at {@link ConverterTypeVia}.
*
* @param convertVia
*/
public void setConvertVia( String convertVia )
{
this.convertVia = convertVia;
}
/**
* Returns convert via if the report should be converted to other format. See constants at {@link ConverterTypeVia}.
*
* @return
*/
public String getConvertVia()
{
return convertVia;
}
/**
* Set true if the content disposition should be generated in the HTTP header (download) or not (view teh report in
* teh browser).
*
* @param generateContentDisposition
*/
public void setGenerateContentDisposition( boolean generateContentDisposition )
{
this.generateContentDisposition = generateContentDisposition;
}
/**
* Returns true if the content disposition should be generated in the HTTP header (download) or not (view teh report
* in teh browser).
*
* @return
*/
public boolean isGenerateContentDisposition()
{
return generateContentDisposition;
}
/**
* Set the configuration to use after the report is loaded.
*
* @param configuration
*/
public void setConfiguration( IXDocReportConfiguration configuration )
{
this.configuration = configuration;
}
/**
* Returns the configuration to use after the report is loaded and null otherwise.
*
* @return
*/
public IXDocReportConfiguration getConfiguration()
{
return configuration;
}
@Override
protected final void initApplicationContext()
throws ApplicationContextException
{
if ( isLoadOnInit() )
{
// load the report when Spring bean is initialized.
getReport();
}
onInit();
}
/**
* Subclasses can override this to add some custom initialization logic. Called by {@link #initApplicationContext()}
* as soon as all standard initialization logic has finished executing.
*
* @see #initApplicationContext()
*/
protected void onInit()
{
}
@Override
public void afterPropertiesSet()
throws Exception
{
super.afterPropertiesSet();
if ( getTemplateEngineId() == null )
{
throw new IllegalArgumentException( "Property 'templateEngineId' is required" );
}
}
@Override
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response )
throws Exception
{
// 1) Get the report
IXDocReport report = getReport();
if ( report == null )
{
throw new IllegalStateException( "No main report defined for 'renderMergedOutputModel' - "
+ "specify a 'url' on this view or override 'getReport()'" );
}
// 2) Prepare Java model context
IContext context = createContext( report, model );
Options options = getOptionsConverter( model );
DumperOptions dumperOptions = getOptionsDumper( model );
if ( options == null )
{
// populateContext( context, report.getId(), request );
// 3) Generate report
doProcessReport( report, context, dumperOptions, request, response );
}
else
{
// Generate and convert report.
doProcessReportWithConverter( report, context, options, dumperOptions, request, response );
}
}
protected IContext createContext( IXDocReport report, Map<String, Object> model )
throws XDocReportException
{
return report.createContext( model );
}
protected Options getOptionsConverter( Map<String, Object> model )
{
String to = getConvertTo();
if ( to != null )
{
String via = getConvertVia();
Options options = Options.getTo( to );
if ( via != null )
{
options.via( via );
}
return options;
}
return CollectionUtils.findValueOfType( model.values(), Options.class );
}
protected DumperOptions getOptionsDumper( Map<String, Object> model )
{
return CollectionUtils.findValueOfType( model.values(), DumperOptions.class );
}
// ----------------- Get Report
/**
* @param request
* @return
* @throws IOException
* @throws XDocReportException
*/
public IXDocReport getReport()
throws ApplicationContextException
{
XDocReportRegistry registry = getRegistry();
// 1) Get report id
String reportId = getReportId();
if ( StringUtils.isNotEmpty( reportId ) )
{
// Search if report is cached in the registry
IXDocReport report = registry.getReport( reportId );
if ( report != null )
{
return report;
}
}
return loadReport( reportId, registry );
}
/**
* Load the main {@code XDocReport} from the specified {@code Resource}. If the {@code Resource} points to an
* uncompiled report design file then the report file is compiled dynamically and loaded into memory.
*
* @return a {@code XDocReport} instance, or {@code null} if no main report has been statically defined
*/
protected IXDocReport loadReport( String reportId, XDocReportRegistry registry )
{
String url = getUrl();
if ( url == null )
{
return null;
}
Resource resource = getApplicationContext().getResource( url );
try
{
InputStream is = resource.getInputStream();
String templateEngineId = getTemplateEngineId();
IXDocReport report = registry.loadReport( is, reportId, templateEngineId, isCacheReport() );
this.reportId = report.getId();
IXDocReportConfiguration configuration = getConfiguration();
if ( configuration != null )
{
configuration.configure( report );
}
return report;
}
catch ( Exception ex )
{
throw new ApplicationContextException( "Could not load XDocReport report from " + resource, ex );
}
}
// ------------------ XDocReport registry
/**
* Returns the XDocReport registry which load and cache document.
*
* @return
*/
protected XDocReportRegistry getRegistry()
{
return XDocReportRegistry.getRegistry();
}
/**
* Generate report with process.
*
* @param report
* @param entryName
* @param dumperOptions
* @param request
* @param response
* @throws XDocReportException
* @throws IOException
*/
private void doProcessReport( IXDocReport report, IContext context, DumperOptions dumperOptions,
HttpServletRequest request, HttpServletResponse response )
throws XDocReportException, IOException
{
if ( dumperOptions != null )
{
// dump must be done
// 2) Get dumper
IDumper dumper = report.getDumper( dumperOptions );
// 3) Prepare HTTP response content type
prepareHTTPResponse( report.getId(), dumper.getMimeMapping(), request, response );
// 4) Generate dump
report.dump( context, dumperOptions, response.getOutputStream() );
}
else
{
// 2) Prepare HTTP response content type
prepareHTTPResponse( report.getId(), report.getMimeMapping(), request, response );
// 3) Generate report
report.process( context, response.getOutputStream() );
}
}
/**
* Generate report with conversion.
*
* @param report
* @param options
* @param dumperOptions
* @param request
* @param response
* @throws XDocReportException
* @throws IOException
* @throws XDocConverterException
*/
protected void doProcessReportWithConverter( IXDocReport report, IContext context, Options options,
DumperOptions dumperOptions, HttpServletRequest request,
HttpServletResponse response )
throws XDocReportException, IOException, XDocConverterException
{
if ( dumperOptions != null )
{
// 2) Get dumper
IDumper dumper = report.getDumper( dumperOptions );
// 3) Prepare HTTP response content type
prepareHTTPResponse( report.getId(), dumper.getMimeMapping(), request, response );
// 4) Generate dump
report.dump( context, dumperOptions, response.getOutputStream() );
}
else
{
// 2) Get converter
IConverter converter = report.getConverter( options );
// 3) Prepare HTTP response content type
prepareHTTPResponse( report.getId(), converter.getMimeMapping(), request, response );
// 4) Generate report with conversion
report.convert( context, options, response.getOutputStream() );
}
}
protected void prepareHTTPResponse( String reportId, MimeMapping mimeMapping, HttpServletRequest request,
HttpServletResponse response )
{
if ( mimeMapping != null )
{
response.setContentType( mimeMapping.getMimeType() );
}
// Check if Content-Disposition must be generated?
if ( isGenerateContentDisposition() )
{
String contentDisposition = getContentDisposition( reportId, mimeMapping, request );
if ( StringUtils.isNotEmpty( contentDisposition ) )
{
response.setHeader( HttpHeaderUtils.CONTENT_DISPOSITION_HEADER, contentDisposition.toString() );
}
}
// Disable HTTP response cache
if ( isDisableHTTPResponCache() )
{
disableHTTPResponCache( response );
}
}
protected boolean isDisableHTTPResponCache()
{
return true;
}
protected String getContentDisposition( String reportId, MimeMapping mimeMapping, HttpServletRequest request )
{
if ( mimeMapping != null )
{
String fileName = mimeMapping.formatFileName( reportId );
return getContentDisposition( fileName );
}
return null;
}
protected String getContentDisposition( String fileName )
{
return HttpHeaderUtils.getAttachmentFileName( fileName );
}
/**
* Disable cache HTTP hearder.
*
* @param response
*/
protected void disableHTTPResponCache( HttpServletResponse response )
{
// see article http://onjava.com/pub/a/onjava/excerpt/jebp_3/index2.html
// Set to expire far in the past.
response.setHeader( HttpHeaderUtils.EXPIRES, HttpHeaderUtils.SAT_6_MAY_1995_12_00_00_GMT );
// Set standard HTTP/1.1 no-cache headers.
response.setHeader( HttpHeaderUtils.CACHE_CONTROL_HTTP_HEADER,
HttpHeaderUtils.NO_STORE_NO_CACHE_MUST_REVALIDATE );
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader( HttpHeaderUtils.CACHE_CONTROL_HTTP_HEADER, HttpHeaderUtils.POST_CHECK_0_PRE_CHECK_0 );
// Set standard HTTP/1.0 no-cache header.
response.setHeader( HttpHeaderUtils.PRAGMA, HttpHeaderUtils.NO_CACHE );
}
}