/*!
* 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-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.extensions.modules.mailer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.DataFactory;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.MultiStreamReportProcessTask;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.ReportParameterValidationException;
import org.pentaho.reporting.engine.classic.core.ReportProcessTask;
import org.pentaho.reporting.engine.classic.core.ReportProcessTaskUtil;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.StaticDataRow;
import org.pentaho.reporting.engine.classic.core.metadata.ReportProcessTaskRegistry;
import org.pentaho.reporting.engine.classic.core.parameters.DefaultParameterContext;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterValidator;
import org.pentaho.reporting.engine.classic.core.parameters.ValidationResult;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.libraries.repository.ContentIOException;
import org.pentaho.reporting.libraries.repository.ContentLocation;
import org.pentaho.reporting.libraries.repository.DefaultNameGenerator;
import org.pentaho.reporting.libraries.repository.email.EmailRepository;
import javax.activation.DataHandler;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.util.ByteArrayDataSource;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
/**
* The mail-processor performs the bursting operation.
*
* @author Thomas Morgner
* @noinspection ThrowableResultOfMethodCallIgnored
*/
public class MailProcessor {
private static class WrapperTableModel implements TableModel {
private TableModel parent;
private DataRow parameters;
private String[] parameterNames;
private WrapperTableModel( final DataRow parameters, final TableModel parent ) {
this.parent = parent;
this.parameters = parameters;
this.parameterNames = parameters.getColumnNames();
}
/**
* Returns the number of rows in the model. A <code>JTable</code> uses this method to determine how many rows it
* should display. This method should be quick, as it is called frequently during rendering.
*
* @return the number of rows in the model
* @see #getColumnCount
*/
public int getRowCount() {
return parent.getRowCount();
}
/**
* Returns the number of columns in the model. A <code>JTable</code> uses this method to determine how many columns
* it should create and display by default.
*
* @return the number of columns in the model
* @see #getRowCount
*/
public int getColumnCount() {
return parameterNames.length + parent.getColumnCount();
}
/**
* Returns the value for the cell at <code>columnIndex</code> and <code>rowIndex</code>.
*
* @param rowIndex
* the row whose value is to be queried
* @param columnIndex
* the column whose value is to be queried
* @return the value Object at the specified cell
*/
public Object getValueAt( final int rowIndex, final int columnIndex ) {
if ( columnIndex < parameterNames.length ) {
return parameters.get( parameterNames[columnIndex] );
}
return parent.getValueAt( rowIndex, columnIndex - parameterNames.length );
}
/**
* Returns a default name for the column using spreadsheet conventions: A, B, C, ... Z, AA, AB, etc. If
* <code>column</code> cannot be found, returns an empty string.
*
* @param columnIndex
* the column being queried
* @return a string containing the default name of <code>column</code>
*/
public String getColumnName( final int columnIndex ) {
if ( columnIndex < parameterNames.length ) {
return parameterNames[columnIndex];
}
return parent.getColumnName( columnIndex - parameterNames.length );
}
/**
* Returns the most specific superclass for all the cell values in the column. This is used by the
* <code>JTable</code> to set up a default renderer and editor for the column.
*
* @param columnIndex
* the index of the column
* @return the common ancestor class of the object values in the model.
*/
public Class getColumnClass( final int columnIndex ) {
if ( columnIndex < parameterNames.length ) {
return Object.class;
}
return parent.getColumnClass( columnIndex - parameterNames.length );
}
/**
* Returns true if the cell at <code>rowIndex</code> and <code>columnIndex</code> is editable. Otherwise,
* <code>setValueAt</code> on the cell will not change the value of that cell.
*
* @param rowIndex
* the row whose value to be queried
* @param columnIndex
* the column whose value to be queried
* @return true if the cell is editable
* @see #setValueAt
*/
public boolean isCellEditable( final int rowIndex, final int columnIndex ) {
return false;
}
/**
* Sets the value in the cell at <code>columnIndex</code> and <code>rowIndex</code> to <code>aValue</code>.
*
* @param aValue
* the new value
* @param rowIndex
* the row whose value is to be changed
* @param columnIndex
* the column whose value is to be changed
* @see #getValueAt
* @see #isCellEditable
*/
public void setValueAt( final Object aValue, final int rowIndex, final int columnIndex ) {
}
/**
* Adds a listener to the list that is notified each time a change to the data model occurs.
*
* @param l
* the TableModelListener
*/
public void addTableModelListener( final TableModelListener l ) {
}
/**
* Removes a listener from the list that is notified each time a change to the data model occurs.
*
* @param l
* the TableModelListener
*/
public void removeTableModelListener( final TableModelListener l ) {
}
}
private static final Log logger = LogFactory.getLog( MailProcessor.class );
private MailProcessor() {
}
public static MimeMessage createReport( final MailDefinition mailDefinition, final Session session )
throws ReportProcessingException, ContentIOException, MessagingException {
return createReport( mailDefinition, session, new StaticDataRow() );
}
public static MimeMessage createReport( final MailDefinition mailDefinition, final Session session,
final DataRow parameters ) throws ReportProcessingException, ContentIOException, MessagingException {
final MasterReport bodyReport = mailDefinition.getBodyReport();
final String[] paramNames = parameters.getColumnNames();
final ReportParameterValues parameterValues = bodyReport.getParameterValues();
for ( int i = 0; i < paramNames.length; i++ ) {
final String paramName = paramNames[i];
if ( isParameterDefined( bodyReport, paramName ) ) {
parameterValues.put( paramName, parameters.get( paramName ) );
}
}
final ReportProcessTaskRegistry registry = ReportProcessTaskRegistry.getInstance();
final String bodyType = mailDefinition.getBodyType();
final ReportProcessTask processTask = registry.createProcessTask( bodyType );
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
ReportProcessTaskUtil.configureBodyStream( processTask, bout, "report", null );
processTask.setReport( bodyReport );
if ( processTask instanceof MultiStreamReportProcessTask ) {
final MultiStreamReportProcessTask mtask = (MultiStreamReportProcessTask) processTask;
mtask.setBulkLocation( mtask.getBodyContentLocation() );
mtask.setBulkNameGenerator( new DefaultNameGenerator( mtask.getBodyContentLocation(), "data" ) );
mtask.setUrlRewriter( new MailURLRewriter() );
}
processTask.run();
if ( processTask.isTaskSuccessful() == false ) {
if ( processTask.isTaskAborted() ) {
logger.info( "EMail Task received interrupt." );
return null;
} else {
logger.info( "EMail Task failed:", processTask.getError() );
throw new ReportProcessingException( "EMail Task failed", processTask.getError() );
}
}
final EmailRepository repository = new EmailRepository( session );
final MimeBodyPart messageBodyPart = repository.getBodypart();
final ByteArrayDataSource dataSource =
new ByteArrayDataSource( bout.toByteArray(), processTask.getReportMimeType() );
messageBodyPart.setDataHandler( new DataHandler( dataSource ) );
final int attachmentsSize = mailDefinition.getAttachmentCount();
for ( int i = 0; i < attachmentsSize; i++ ) {
final MasterReport report = mailDefinition.getAttachmentReport( i );
final String type = mailDefinition.getAttachmentType( i );
final ContentLocation location = repository.getRoot();
final ContentLocation bulkLocation = location.createLocation( "attachment-" + i );
final ReportProcessTask attachmentProcessTask = registry.createProcessTask( type );
attachmentProcessTask.setBodyContentLocation( bulkLocation );
attachmentProcessTask.setBodyNameGenerator( new DefaultNameGenerator( bulkLocation, "report" ) );
attachmentProcessTask.setReport( report );
if ( attachmentProcessTask instanceof MultiStreamReportProcessTask ) {
final MultiStreamReportProcessTask mtask = (MultiStreamReportProcessTask) attachmentProcessTask;
mtask.setBulkLocation( bulkLocation );
mtask.setBulkNameGenerator( new DefaultNameGenerator( bulkLocation, "data" ) );
mtask.setUrlRewriter( new MailURLRewriter() );
}
attachmentProcessTask.run();
if ( attachmentProcessTask.isTaskSuccessful() == false ) {
if ( attachmentProcessTask.isTaskAborted() ) {
logger.info( "EMail Task received interrupt." );
} else {
logger.info( "EMail Task failed:", attachmentProcessTask.getError() );
throw new ReportProcessingException( "EMail Task failed", attachmentProcessTask.getError() );
}
}
}
return repository.getEmail();
}
private static boolean isParameterDefined( final MasterReport bodyReport, final String paramName ) {
final ParameterDefinitionEntry[] definitionEntries = bodyReport.getParameterDefinition().getParameterDefinitions();
for ( int i = 0; i < definitionEntries.length; i++ ) {
final ParameterDefinitionEntry definitionEntry = definitionEntries[i];
if ( definitionEntry.getName().equals( paramName ) ) {
return true;
}
}
return false;
}
public static void performBursting( final MailDefinition definition ) throws ReportProcessingException,
MessagingException, ContentIOException {
final Session session = Session.getInstance( definition.getSessionProperties(), definition.getAuthenticator() );
performBursting( definition, session );
}
public static void performBursting( final MailDefinition definition, final Session session )
throws MessagingException, ReportProcessingException, ContentIOException {
if ( session == null ) {
throw new NullPointerException();
}
// process parameters - validate!
final ReportParameterValues parameterValues = definition.getParameterValues();
final DefaultParameterContext parameterContext =
new DefaultParameterContext( definition.getDataFactory(), parameterValues, ClassicEngineBoot.getInstance()
.getGlobalConfig(), definition.getResourceBundleFactory(), definition.getResourceManager(), definition
.getContextKey(), definition.getReportEnvironment() );
try {
final ReportParameterDefinition parameterDefinition = definition.getParameterDefinition();
final ReportParameterValidator reportParameterValidator = parameterDefinition.getValidator();
final ValidationResult validationResult =
reportParameterValidator.validate( new ValidationResult(), parameterDefinition, parameterContext );
if ( validationResult.isEmpty() == false ) {
throw new ReportParameterValidationException( "The parameters provided for this report are not valid.",
validationResult );
}
} finally {
parameterContext.close();
}
// definition: Single mail or multi-mail
final TableModel burstingData;
final DataFactory dataFactory = definition.getDataFactory();
if ( definition.getBurstQuery() != null
&& dataFactory.isQueryExecutable( definition.getBurstQuery(), parameterValues ) ) {
burstingData =
wrapWithParameters( dataFactory.queryData( definition.getBurstQuery(), parameterValues ), parameterValues );
} else {
burstingData = wrapWithParameters( new DefaultTableModel( 1, 0 ), parameterValues );
}
if ( burstingData.getRowCount() > 0 ) {
// final Transport transport = session.getTransport();
// transport.connect();
for ( int i = 0; i < burstingData.getRowCount(); i++ ) {
final DataRow parameterDataRow = createReportParameterDataRow( burstingData, i );
final MimeMessage message = createReport( definition, session, parameterDataRow );
parameterContext.setParameterValues( parameterDataRow );
final MailHeader[] headers = definition.getHeaders();
for ( int j = 0; j < headers.length; j++ ) {
final MailHeader header = headers[j];
message.addHeader( header.getName(), header.getValue( parameterContext ) );
}
processRecipients( definition, message, dataFactory, parameterDataRow );
// transport.sendMessage(message, message.getAllRecipients());
}
// transport.close();
}
}
private static void processRecipients( final MailDefinition definition, final MimeMessage message,
final DataFactory dataFactory, final DataRow parameterDataRow ) throws ReportDataFactoryException,
MessagingException {
if ( definition.getRecipientsQuery() != null
&& dataFactory.isQueryExecutable( definition.getRecipientsQuery(), parameterDataRow ) ) {
final TableModel model =
wrapWithParameters( dataFactory.queryData( definition.getRecipientsQuery(), parameterDataRow ),
parameterDataRow );
for ( int r = 0; r < model.getRowCount(); r++ ) {
String address = null;
String name = null;
String type = "TO";
if ( model.getColumnCount() >= 3 ) {
type = (String) model.getValueAt( 0, 2 );
}
if ( model.getColumnCount() >= 2 ) {
name = (String) model.getValueAt( 0, 1 );
}
if ( model.getColumnCount() >= 1 ) {
address = (String) model.getValueAt( 0, 0 );
}
if ( address == null ) {
continue;
}
if ( name == null ) {
message.addRecipient( parseType( type ), new InternetAddress( address, true ) );
} else {
try {
message.addRecipient( parseType( type ), new InternetAddress( address, name, "UTF-8" ) );
} catch ( UnsupportedEncodingException e ) {
// Should not happen - UTF-8 is safe to use
throw new MessagingException( "Failed to encode recipient", e );
}
}
}
}
}
private static Message.RecipientType parseType( final String type ) {
if ( "TO".equalsIgnoreCase( type ) ) {
return MimeMessage.RecipientType.TO;
}
if ( "CC".equalsIgnoreCase( type ) ) {
return MimeMessage.RecipientType.CC;
}
if ( "BCC".equalsIgnoreCase( type ) ) {
return MimeMessage.RecipientType.BCC;
}
return MimeMessage.RecipientType.TO;
}
private static DataRow createReportParameterDataRow( final TableModel burstingData, final int row ) {
final int columnCount = burstingData.getColumnCount();
final String[] columnNames = new String[columnCount];
final Object[] columnValues = new Object[columnCount];
for ( int i = 0; i < columnCount; i++ ) {
columnValues[i] = burstingData.getValueAt( row, i );
columnNames[i] = burstingData.getColumnName( i );
}
return new StaticDataRow( columnNames, columnValues );
}
private static TableModel wrapWithParameters( final TableModel model, final DataRow parameter ) {
return new WrapperTableModel( parameter, model );
}
}