Java tutorial
/* * R Service Bus * * Copyright (c) Copyright of Open Analytics NV, 2010-2015 * * =========================================================================== * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package eu.openanalytics.rsb.component; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage.RecipientType; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; import org.springframework.integration.mail.AbstractMailReceiver; import org.springframework.integration.mail.ImapMailReceiver; import org.springframework.integration.mail.MailReceivingMessageSource; import org.springframework.integration.mail.Pop3MailReceiver; import org.springframework.integration.message.GenericMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMailMessage; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.stereotype.Component; import eu.openanalytics.rsb.Constants; import eu.openanalytics.rsb.config.Configuration.CatalogSection; import eu.openanalytics.rsb.config.Configuration.DepositEmailConfiguration; import eu.openanalytics.rsb.message.AbstractWorkItem.Source; import eu.openanalytics.rsb.message.MultiFilesJob; import eu.openanalytics.rsb.message.MultiFilesResult; import eu.openanalytics.rsb.security.ApplicationPermissionEvaluator; import eu.openanalytics.rsb.si.HeaderSettingMessageSourceWrapper; /** * Handles email based R job and result exchanges. * * @author "OpenAnalytics <rsb.development@openanalytics.eu>" */ @Component("emailDepositHandler") public class EmailDepositHandler extends AbstractComponentWithCatalog implements BeanFactoryAware { public static final String EMAIL_CONFIG_HEADER_NAME = DepositEmailConfiguration.class.getName(); public static final String EMAIL_REPLY_CC_META_NAME = "emailReplyCC"; public static final String EMAIL_REPLY_TO_META_NAME = "emailReplyTo"; public static final String EMAIL_ADDRESSEE_META_NAME = "emailAddressee"; public static final String EMAIL_SUBJECT_META_NAME = "emailSubject"; public static final String EMAIL_BODY_META_NAME = "emailBody"; @Resource private JavaMailSender mailSender; @Resource(name = "emailDepositChannel") private MessageChannel emailDepositChannel; @Resource(name = "outboundEmailChannel") private MessageChannel outboundEmailChannel; private BeanFactory beanFactory; private final List<SourcePollingChannelAdapter> channelAdapters = new ArrayList<SourcePollingChannelAdapter>(); @Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } // exposed for testing void setMailSender(final JavaMailSender mailSender) { this.mailSender = mailSender; } void setOutboundEmailChannel(final MessageChannel outboundEmailChannel) { this.outboundEmailChannel = outboundEmailChannel; } @PostConstruct public void setupChannelAdapters() throws URISyntaxException { final List<DepositEmailConfiguration> depositEmailConfigurations = getConfiguration() .getDepositEmailAccounts(); if ((depositEmailConfigurations == null) || (depositEmailConfigurations.isEmpty())) { return; } for (final DepositEmailConfiguration depositEmailConfiguration : depositEmailConfigurations) { final PeriodicTrigger trigger = new PeriodicTrigger(depositEmailConfiguration.getPollingPeriod(), TimeUnit.MILLISECONDS); trigger.setInitialDelay(5000L); AbstractMailReceiver mailReceiver = null; final URI emailAccountURI = depositEmailConfiguration.getAccountURI(); if (StringUtils.equals(emailAccountURI.getScheme(), "pop3")) { mailReceiver = new Pop3MailReceiver(emailAccountURI.toString()); } else if (StringUtils.equals(emailAccountURI.getScheme(), "imap")) { mailReceiver = new ImapMailReceiver(emailAccountURI.toString()); ((ImapMailReceiver) mailReceiver).setShouldMarkMessagesAsRead(true); } else { throw new IllegalArgumentException("Invalid email account URI: " + emailAccountURI); } mailReceiver.setBeanFactory(beanFactory); mailReceiver.setBeanName("rsb-email-ms-" + emailAccountURI.getHost() + emailAccountURI.hashCode()); mailReceiver.setShouldDeleteMessages(true); mailReceiver.setMaxFetchSize(1); mailReceiver.afterPropertiesSet(); final MailReceivingMessageSource fileMessageSource = new MailReceivingMessageSource(mailReceiver); final HeaderSettingMessageSourceWrapper<javax.mail.Message> messageSource = new HeaderSettingMessageSourceWrapper<javax.mail.Message>( fileMessageSource, EMAIL_CONFIG_HEADER_NAME, depositEmailConfiguration); final SourcePollingChannelAdapter channelAdapter = new SourcePollingChannelAdapter(); channelAdapter.setBeanFactory(beanFactory); channelAdapter.setBeanName("rsb-email-ca-" + emailAccountURI.getHost() + emailAccountURI.hashCode()); channelAdapter.setOutputChannel(emailDepositChannel); channelAdapter.setSource(messageSource); channelAdapter.setTrigger(trigger); channelAdapter.afterPropertiesSet(); channelAdapter.start(); getLogger().info("Started channel adapter: " + channelAdapter); channelAdapters.add(channelAdapter); } } @PreDestroy public void closeChannelAdapters() { for (final SourcePollingChannelAdapter channelAdapter : channelAdapters) { channelAdapter.stop(); getLogger().info("Stopped channel adapter: " + channelAdapter); } } @SuppressWarnings("unchecked") public void handleJob(final Message<MimeMessage> message) throws MessagingException, IOException { final DepositEmailConfiguration depositEmailConfiguration = message.getHeaders() .get(EMAIL_CONFIG_HEADER_NAME, DepositEmailConfiguration.class); final String applicationName = depositEmailConfiguration.getApplicationName(); final MimeMessage mimeMessage = message.getPayload(); final Address[] replyTo = mimeMessage.getReplyTo(); Validate.notEmpty(replyTo, "no reply address found for job emailed with headers:" + Collections.list(mimeMessage.getAllHeaders())); final Map<String, Serializable> meta = new HashMap<String, Serializable>(); meta.put(EMAIL_SUBJECT_META_NAME, mimeMessage.getSubject()); meta.put(EMAIL_ADDRESSEE_META_NAME, getPrimaryAddressee(mimeMessage)); meta.put(EMAIL_REPLY_TO_META_NAME, replyTo[0].toString()); meta.put(EMAIL_REPLY_CC_META_NAME, getCCAddressees(mimeMessage)); meta.put(EMAIL_BODY_META_NAME, getResponseBody(depositEmailConfiguration)); final MultiFilesJob job = new MultiFilesJob(Source.EMAIL, applicationName, ApplicationPermissionEvaluator.NO_AUTHENTICATED_USERNAME, UUID.randomUUID(), (GregorianCalendar) GregorianCalendar.getInstance(), meta); try { addEmailAttachmentsToJob(depositEmailConfiguration, mimeMessage, job); getMessageDispatcher().dispatch(job); } catch (final Exception e) { final MultiFilesResult errorResult = job.buildErrorResult(e, getMessages()); handleResult(errorResult); } } public void handleResult(final MultiFilesResult result) throws MessagingException, IOException { final Serializable responseBody = result.getMeta().get(EMAIL_BODY_META_NAME); final String responseText = responseBody instanceof File ? FileUtils.readFileToString((File) responseBody) : responseBody.toString(); final MimeMessage mimeMessage = mailSender.createMimeMessage(); final MimeMessageHelper mmh = new MimeMessageHelper(mimeMessage, true); mmh.setFrom((String) result.getMeta().get(EMAIL_ADDRESSEE_META_NAME)); mmh.setTo((String) result.getMeta().get(EMAIL_REPLY_TO_META_NAME)); mmh.setCc((String[]) result.getMeta().get(EMAIL_REPLY_CC_META_NAME)); mmh.setSubject("RE: " + result.getMeta().get(EMAIL_SUBJECT_META_NAME)); if (result.isSuccess()) { mmh.setText(responseText); for (final File resultFile : result.getPayload()) { mmh.addAttachment(resultFile.getName(), resultFile); } } else { mmh.setText(FileUtils.readFileToString(result.getPayload()[0])); } final Message<MimeMailMessage> message = new GenericMessage<MimeMailMessage>(new MimeMailMessage(mmh)); outboundEmailChannel.send(message); } private void addEmailAttachmentsToJob(final DepositEmailConfiguration depositEmailConfiguration, final MimeMessage mimeMessage, final MultiFilesJob job) throws MessagingException, IOException, FileNotFoundException { final String jobConfigurationFileName = depositEmailConfiguration.getJobConfigurationFileName(); if (StringUtils.isNotBlank(jobConfigurationFileName)) { final File jobConfigurationFile = getJobConfigurationFile( depositEmailConfiguration.getApplicationName(), jobConfigurationFileName); job.addFile(Constants.MULTIPLE_FILES_JOB_CONFIGURATION, new FileInputStream(jobConfigurationFile)); } final Object content = mimeMessage.getContent(); Validate.isTrue(content instanceof Multipart, "only multipart emails can be processed"); final Multipart multipart = (Multipart) content; for (int i = 0, n = multipart.getCount(); i < n; i++) { final Part part = multipart.getBodyPart(i); final String disposition = part.getDisposition(); if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT) || (disposition.equals(Part.INLINE))))) { final String name = part.getFileName(); final String contentType = StringUtils.substringBefore(part.getContentType(), ";"); MultiFilesJob.addDataToJob(contentType, name, part.getInputStream(), job); } } } private Serializable getResponseBody(final DepositEmailConfiguration depositEmailConfiguration) { if (StringUtils.isBlank(depositEmailConfiguration.getResponseFileName())) { return getMessages().getMessage("email.result.body", null, null); } return getCatalogManager().internalGetCatalogFile(CatalogSection.EMAIL_REPLIES, depositEmailConfiguration.getApplicationName(), depositEmailConfiguration.getResponseFileName()); } private String getPrimaryAddressee(final MimeMessage mimeMessage) throws MessagingException { final Address[] recipients = mimeMessage.getRecipients(RecipientType.TO); return recipients != null && recipients.length > 0 ? recipients[0].toString() : "?"; } private String[] getCCAddressees(final MimeMessage mimeMessage) throws MessagingException { final Address[] recipients = mimeMessage.getRecipients(RecipientType.CC); if (recipients == null) { return ArrayUtils.EMPTY_STRING_ARRAY; } final String[] result = new String[recipients.length]; for (int i = 0; i < recipients.length; i++) { result[i] = recipients[i].toString(); } return result; } }