/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.mail.internal; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import org.apache.commons.lang3.exception.ExceptionUtils; import org.xwiki.component.annotation.Component; import org.xwiki.component.annotation.InstantiationStrategy; import org.xwiki.component.descriptor.ComponentInstantiationStrategy; import org.xwiki.component.phase.Initializable; import org.xwiki.component.phase.InitializationException; import org.xwiki.context.Execution; import org.xwiki.mail.ExtendedMimeMessage; import org.xwiki.mail.MailContentStore; import org.xwiki.mail.MailState; import org.xwiki.mail.MailStatus; import org.xwiki.mail.MailStatusResult; import org.xwiki.mail.MailStatusStore; import org.xwiki.mail.MailStorageConfiguration; import org.xwiki.mail.MailStoreException; import com.xpn.xwiki.XWikiContext; /** * Saves mail statuses in the database. * * @version $Id: 99ea17229745f5283b6098a14597175ec2c1640a $ * @since 6.4M3 */ @Component @Named("database") @InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP) public class DatabaseMailListener extends AbstractMailListener implements Initializable { @Inject private Execution execution; @Inject @Named("filesystem") private MailContentStore mailContentStore; @Inject @Named("database") private MailStatusStore mailStatusStore; @Inject private MailStorageConfiguration configuration; private DatabaseMailStatusResult mailStatusResult; @Override public void initialize() throws InitializationException { mailStatusResult = new DatabaseMailStatusResult(this.mailStatusStore); } @Override public void onPrepareBegin(String batchId, Map<String, Object> parameters) { super.onPrepareBegin(batchId, parameters); mailStatusResult.setBatchId(batchId); } @Override public void onPrepareMessageSuccess(ExtendedMimeMessage message, Map<String, Object> parameters) { super.onPrepareMessageSuccess(message, parameters); MailStatus status = new MailStatus(getBatchId(), message, MailState.PREPARE_SUCCESS); status.setWiki( ((XWikiContext) execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY)).getWikiId()); saveStatus(status, parameters); } @Override public void onPrepareMessageError(ExtendedMimeMessage message, Exception exception, Map<String, Object> parameters) { super.onPrepareMessageError(message, exception, parameters); MailStatus status = new MailStatus(getBatchId(), message, MailState.PREPARE_ERROR); status.setWiki( ((XWikiContext) execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY)).getWikiId()); status.setError(exception); saveStatus(status, parameters); // This mail will not reach the send queue, so its processing is done now. mailStatusResult.incrementCurrentSize(); } @Override public void onPrepareFatalError(Exception exception, Map<String, Object> parameters) { super.onPrepareFatalError(exception, parameters); //TODO: Store failure exception logger.error("Failure during preparation phase of thread [" + getBatchId() + "]", exception); } @Override public void onSendMessageSuccess(ExtendedMimeMessage message, Map<String, Object> parameters) { super.onSendMessageSuccess(message, parameters); String uniqueMessageId = message.getUniqueMessageId(); MailStatus status = retrieveExistingMailStatus(uniqueMessageId, MailState.SEND_SUCCESS); if (status != null) { status.setState(MailState.SEND_SUCCESS); } else { this.logger.warn("Forcing a new mail status for message [{}] of batch [{}] to send_success state.", uniqueMessageId, getBatchId()); status = new MailStatus(getBatchId(), message, MailState.SEND_SUCCESS); } // Since the mail was sent successfully we don't need to keep its serialized content deleteMailContent(status); // If the user doesn't want to keep success status, we remove the mail status, otherwise we just update it if (configuration.discardSuccessStatuses()) { deleteStatus(status, parameters); } else { saveStatus(status, parameters); } mailStatusResult.incrementCurrentSize(); } @Override public void onSendMessageFatalError(String uniqueMessageId, Exception exception, Map<String, Object> parameters) { super.onSendMessageFatalError(uniqueMessageId, exception, parameters); MailStatus status = retrieveExistingMailStatus(uniqueMessageId, MailState.SEND_FATAL_ERROR); if (status != null) { status.setState(MailState.SEND_FATAL_ERROR); status.setError(exception); saveStatus(status, parameters); } else { this.logger.error("Unable to report the fatal error encountered during mail sending for message [{}] " + "of batch [{}].", uniqueMessageId, getBatchId(), exception); } this.mailStatusResult.incrementCurrentSize(); } @Override public void onSendMessageError(ExtendedMimeMessage message, Exception exception, Map<String, Object> parameters) { super.onSendMessageError(message, exception, parameters); String uniqueMessageId = message.getUniqueMessageId(); MailStatus status = retrieveExistingMailStatus(uniqueMessageId, MailState.SEND_ERROR); if (status != null) { status.setState(MailState.SEND_ERROR); } else { this.logger.warn("Forcing a new mail status for message [{}] of batch [{}] to send_error state.", uniqueMessageId, getBatchId()); status = new MailStatus(getBatchId(), message, MailState.SEND_ERROR); } status.setError(exception); saveStatus(status, parameters); this.mailStatusResult.incrementCurrentSize(); } private MailStatus retrieveExistingMailStatus(String uniqueMessageId, MailState state) { MailStatus status; try { status = mailStatusStore.load(uniqueMessageId); if (status == null) { // It's not normal to have no status in the mail status store since onPrepare should have been called // before. this.logger.error( "Failed to find a previous mail status for message [{}] of batch [{}] and state [{}].", uniqueMessageId, getBatchId(), state); } } catch (MailStoreException e) { this.logger.error( "Error when looking for a previous mail status for message [{}] of batch [{}] and state [{}].", uniqueMessageId, getBatchId(), state, e); status = null; } return status; } @Override public MailStatusResult getMailStatusResult() { return mailStatusResult; } private void saveStatus(MailStatus status, Map<String, Object> parameters) { try { mailStatusStore.save(status, parameters); } catch (MailStoreException e) { // Failed to save the status in the DB, we continue but log an error logger.error("Failed to save mail status [{}] to the database", status, e); } } private void deleteStatus(MailStatus status, Map<String, Object> parameters) { try { mailStatusStore.delete(status.getMessageId(), parameters); } catch (MailStoreException e) { // Failed to delete the status in the DB, we continue but log an error logger.error("Failed to delete mail status [{}] from the database", status, e); } } private void deleteMailContent(MailStatus currentStatus) { if (currentStatus != null) { try { mailContentStore.delete(currentStatus.getBatchId(), currentStatus.getMessageId()); } catch (MailStoreException e) { // Failed to delete saved mail, raise a warning but continue since it's not critical this.logger.warn("Failed to remove previously failing message [{}] (batch id [{}]) from the file " + "system. Reason [{}].", currentStatus.getMessageId(), currentStatus.getBatchId(), ExceptionUtils.getRootCauseMessage(e)); } } } }