/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2016 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.platform.plugin;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.engine.IParameterProvider;
import org.pentaho.platform.api.engine.IPentahoSession;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.engine.core.audit.MessageTypes;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.util.RepositoryPathEncoder;
import org.pentaho.platform.util.UUIDUtil;
import org.pentaho.platform.util.web.MimeHelper;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTableModule;
import org.pentaho.reporting.engine.classic.core.util.StagingMode;
import org.pentaho.reporting.platform.plugin.async.ReportListenerThreadHolder;
import org.pentaho.reporting.platform.plugin.messages.Messages;
import org.pentaho.reporting.platform.plugin.staging.AbstractStagingHandler;
import org.pentaho.reporting.platform.plugin.staging.StagingHandler;
public class ExecuteReportContentHandler {
public static final String FORCED_BUFFERED_WRITING =
"org.pentaho.reporting.engine.classic.core.modules.output.table.html.ForceBufferedWriting";
private static final Log logger = LogFactory.getLog( ExecuteReportContentHandler.class );
private static final StagingMode DEFAULT = StagingMode.THRU;
private IPentahoSession userSession;
private ReportContentGenerator contentGenerator;
public ExecuteReportContentHandler( final ReportContentGenerator contentGenerator ) {
this.contentGenerator = contentGenerator;
this.userSession = contentGenerator.getUserSession();
}
public void createReportContent( final OutputStream outputStream, final Serializable fileId, final String path,
final boolean forceDefaultOutputTarget ) throws Exception {
this.createReportContent( outputStream, fileId, path, forceDefaultOutputTarget, new SimpleReportingComponent(), new AuditWrapper() );
}
public void createReportContent( final OutputStream outputStream, final Serializable fileId, final String path,
final boolean forceDefaultOutputTarget,
final SimpleReportingComponent reportComponent,
final AuditWrapper audit ) throws Exception {
ReportListenerThreadHolder.setRequestId( contentGenerator.getInstanceId() );
final long start = System.currentTimeMillis();
final Map<String, Object> inputs = contentGenerator.createInputs();
audit.audit( userSession.getId(), userSession.getName(), path, contentGenerator.getObjectName(), getClass()
.getName(), MessageTypes.INSTANCE_START, contentGenerator.getInstanceId(), "", 0, contentGenerator ); //$NON-NLS-1$
String result = MessageTypes.FAILED;
StagingHandler reportStagingHandler = null;
try {
final Object rawSessionId = inputs.get( ParameterXmlContentHandler.SYS_PARAM_SESSION_ID );
if ( ( rawSessionId instanceof String ) == false || "".equals( rawSessionId ) ) {
inputs.put( ParameterXmlContentHandler.SYS_PARAM_SESSION_ID, UUIDUtil.getUUIDAsString() );
}
// produce rendered report
reportComponent.setReportFileId( fileId );
reportComponent.setPaginateOutput( true );
reportComponent.setForceDefaultOutputTarget( forceDefaultOutputTarget );
reportComponent.setDefaultOutputTarget( HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE );
if ( path.endsWith( ".prpti" ) ) {
reportComponent.setForceUnlockPreferredOutput( true );
}
reportComponent.setInputs( inputs );
final MasterReport report = reportComponent.getReport();
final StagingMode stagingMode = getStagingMode( inputs, report );
reportStagingHandler = AbstractStagingHandler.getStagingHandlerImpl( outputStream, this.userSession, stagingMode );
if ( reportStagingHandler.isFullyBuffered() ) {
// it is safe to disable the buffered writing for the report now that we have a
// extra buffering in place.
report.getReportConfiguration().setConfigProperty( FORCED_BUFFERED_WRITING, "false" );
}
reportComponent.setOutputStream( reportStagingHandler.getStagingOutputStream() );
// the requested mime type can be null, in that case the report-component will resolve the desired
// type from the output-target.
// Hoever, the report-component will inspect the inputs independently from the mimetype here.
final IUnifiedRepository repository = PentahoSystem.get( IUnifiedRepository.class, userSession );
final RepositoryFile file = repository.getFileById( fileId );
// add all inputs (request parameters) to report component
final String mimeType = reportComponent.getMimeType();
// If we haven't set an accepted page, -1 will be the default, which will give us a report
// with no pages. This default is used so that when we do our parameter interaction with the
// engine we can spend as little time as possible rendering unused pages, making it no pages.
// We are going to intentionally reset the accepted page to the first page, 0, at this point,
// if the accepted page is -1.
final String outputTarget = reportComponent.getComputedOutputTarget();
if ( HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE.equals( outputTarget )
&& reportComponent.getAcceptedPage() < 0 ) {
reportComponent.setAcceptedPage( 0 );
}
if ( logger.isDebugEnabled() ) {
logger.debug( Messages.getInstance().getString( "ReportPlugin.logStartGenerateContent", mimeType, //$NON-NLS-1$
outputTarget, String.valueOf( reportComponent.getAcceptedPage() ) ) );
}
HttpServletResponse response = null;
boolean streamToBrowser = false;
final IParameterProvider pathProviders = contentGenerator.getParameterProviders().get( "path" );
if ( pathProviders != null ) {
final Object httpResponse = pathProviders.getParameter( "httpresponse" );
if ( httpResponse instanceof HttpServletResponse ) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
response = (HttpServletResponse) httpResponse; //$NON-NLS-1$ //$NON-NLS-2$
if ( reportStagingHandler.getStagingMode() == StagingMode.THRU ) {
// Direct back - check output stream...
final OutputStream respOutputStream = response.getOutputStream();
if ( respOutputStream == outputStream ) {
//
// Massive assumption here -
// Assume the container returns the same object on successive calls to response.getOutputStream()
streamToBrowser = true;
}
}
}
}
final String extension = MimeHelper.getExtension( mimeType );
String filename = file.getName();
if ( filename.lastIndexOf( "." ) != -1 ) { //$NON-NLS-1$
filename = filename.substring( 0, filename.lastIndexOf( "." ) ); //$NON-NLS-1$
}
String disposition = "inline; filename*=UTF-8''" + RepositoryPathEncoder.encode( RepositoryPathEncoder.encodeRepositoryPath( filename + extension ) );
final boolean validates = reportComponent.validate();
if ( !validates ) {
sendErrorResponse( response, outputStream, reportStagingHandler );
} else {
if ( response != null ) {
// set headers before we begin execution
response.setHeader( "Content-Disposition", disposition );
response.setHeader( "Content-Description", file.getName() ); //$NON-NLS-1$
response.setHeader( "Cache-Control", "private, max-age=0, must-revalidate" );
}
if ( reportComponent.execute() ) {
if ( response != null ) {
if ( reportStagingHandler.canSendHeaders() ) {
// we can set content lenght after execution - so we know exact response weight
response.setContentLength( reportStagingHandler.getWrittenByteCount() );
}
}
if ( logger.isDebugEnabled() ) {
logger.debug( Messages.getInstance().getString(
"ReportPlugin.logEndGenerateContent",
String.valueOf( reportStagingHandler.getWrittenByteCount() ) ) ); //$NON-NLS-1$
}
reportStagingHandler.complete(); // will copy bytes to final destination...
result = MessageTypes.INSTANCE_END;
} else { // failed execution
sendErrorResponse( response, outputStream, reportStagingHandler );
}
}
} finally {
if ( reportStagingHandler != null ) {
reportStagingHandler.close();
}
final long end = System.currentTimeMillis();
audit.audit( userSession.getId(), userSession.getName(), path, contentGenerator.getObjectName(),
getClass().getName(), result, contentGenerator.getInstanceId(),
"", ( (float) ( end - start ) / 1000 ), contentGenerator ); //$NON-NLS-1$
}
}
// default visibility for testing purposes
StagingMode getStagingMode( final Map<String, Object> inputs, final MasterReport report ) {
final Object o = inputs.get( "report-staging-mode" );
if ( o != null ) {
try {
return StagingMode.valueOf( String.valueOf( o ) );
} catch ( IllegalArgumentException ie ) {
logger.trace( "Staging mode was specified but invalid" );
}
}
StagingMode mode =
(StagingMode) report.getAttribute( AttributeNames.Pentaho.NAMESPACE, AttributeNames.Pentaho.STAGING_MODE );
if ( mode == null ) {
logger.trace( "Looking at default settings for mode" ); //$NON-NLS-1$
// Unable to use the plugin settings.xml because the
// classloader for the ReportContentGenerator isn't the plugin classloader
// IPluginResourceLoader resLoader = PentahoSystem.get(IPluginResourceLoader.class, null);
// String defaultStagingMode = resLoader.getPluginSetting(ReportContentGenerator.class, "settings/report-staging-mode"); //$NON-NLS-1$
//
// So - get default setting from the pentaho.xml instead
String defaultStagingMode = PentahoSystem.getSystemSetting( "report-staging-mode", null ); //$NON-NLS-1$
if ( defaultStagingMode == null ) {
// workaround for a bug in getPluginSetting that ignores the default passed in
defaultStagingMode = DEFAULT.toString(); //$NON-NLS-1$
logger.trace( "Nothing in settings/staging-mode - defaulting to MEMORY" ); //$NON-NLS-1$
} else {
logger.trace( "Read " + defaultStagingMode + " from settings/report-staging-mode" ); //$NON-NLS-1$//$NON-NLS-2$
}
try {
mode = StagingMode.valueOf( defaultStagingMode.toUpperCase() );
logger.trace( "Staging mode set from default - " + mode ); //$NON-NLS-1$
} catch ( IllegalArgumentException badStringInSettings ) {
mode = DEFAULT; // default state - handling staging in memory by default.
}
}
return mode;
}
private void sendErrorResponse( final HttpServletResponse response, final OutputStream outputStream,
final StagingHandler reportStagingHandler ) throws IOException {
if ( response != null ) {
response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
}
if ( logger.isDebugEnabled() ) {
logger.debug( Messages.getInstance().getString( "ReportPlugin.logErrorGenerateContent" ) ); //$NON-NLS-1$
}
if ( reportStagingHandler.canSendHeaders() ) {
//
// Can send headers is another way to check whether the real destination has been
// pre-polluted with data.
//
outputStream.write( Messages.getInstance().getString( "ReportPlugin.ReportValidationFailed" ).getBytes() ); //$NON-NLS-1$
outputStream.flush();
}
}
}