| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.axis2.transport.mail; |
| |
| import org.apache.axis2.transport.base.*; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.axis2.context.ConfigurationContext; |
| import org.apache.axis2.context.MessageContext; |
| import org.apache.axis2.description.*; |
| import org.apache.axis2.AxisFault; |
| import org.apache.axis2.addressing.AddressingConstants; |
| import org.apache.axis2.kernel.OutTransportInfo; |
| import org.apache.axis2.kernel.MessageFormatter; |
| import org.apache.axiom.mime.ContentType; |
| import org.apache.axiom.om.OMOutputFormat; |
| |
| import javax.mail.*; |
| import javax.mail.internet.AddressException; |
| import javax.mail.internet.InternetAddress; |
| import javax.mail.internet.MimeBodyPart; |
| import javax.mail.internet.MimeMultipart; |
| import javax.mail.internet.MimePart; |
| import javax.activation.DataHandler; |
| |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.io.IOException; |
| import java.text.ParseException; |
| |
| /** |
| * The mail transport sender sends mail using an SMTP server configuration defined |
| * in the axis2.xml's transport sender definition |
| */ |
| |
| public class MailTransportSender extends AbstractTransportSender |
| implements ManagementSupport { |
| |
| private String smtpUsername = null; |
| private String smtpPassword = null; |
| /** Default from address for outgoing messages */ |
| private InternetAddress smtpFromAddress = null; |
| /** A set of custom Bcc address for all outgoing messages */ |
| private InternetAddress[] smtpBccAddresses = null; |
| /** Default mail format */ |
| private String defaultMailFormat = "Text"; |
| /** The default Session which can be safely shared */ |
| private Session session = null; |
| |
| /** |
| * The public constructor |
| */ |
| public MailTransportSender() { |
| log = LogFactory.getLog(MailTransportSender.class); |
| } |
| |
| /** |
| * Initialize the Mail sender and be ready to send messages |
| * @param cfgCtx the axis2 configuration context |
| * @param transportOut the transport-out description |
| * @throws org.apache.axis2.AxisFault on error |
| */ |
| public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) throws AxisFault { |
| super.init(cfgCtx, transportOut); |
| |
| // initialize SMTP session |
| Properties props = new Properties(); |
| List<Parameter> params = transportOut.getParameters(); |
| for (Parameter p : params) { |
| props.put(p.getName(), p.getValue()); |
| } |
| |
| if (props.containsKey(MailConstants.MAIL_SMTP_FROM)) { |
| try { |
| smtpFromAddress = new InternetAddress( |
| (String) props.get(MailConstants.MAIL_SMTP_FROM)); |
| } catch (AddressException e) { |
| handleException("Invalid default 'From' address : " + |
| props.get(MailConstants.MAIL_SMTP_FROM), e); |
| } |
| } |
| |
| if (props.containsKey(MailConstants.MAIL_SMTP_BCC)) { |
| try { |
| smtpBccAddresses = InternetAddress.parse( |
| (String) props.get(MailConstants.MAIL_SMTP_BCC)); |
| } catch (AddressException e) { |
| handleException("Invalid default 'Bcc' address : " + |
| props.get(MailConstants.MAIL_SMTP_BCC), e); |
| } |
| } |
| |
| if (props.containsKey(MailConstants.TRANSPORT_MAIL_FORMAT)) { |
| defaultMailFormat = (String) props.get(MailConstants.TRANSPORT_MAIL_FORMAT); |
| } |
| |
| smtpUsername = (String) props.get(MailConstants.MAIL_SMTP_USERNAME); |
| smtpPassword = (String) props.get(MailConstants.MAIL_SMTP_PASSWORD); |
| |
| if (smtpUsername != null && smtpPassword != null) { |
| session = Session.getInstance(props, new Authenticator() { |
| public PasswordAuthentication getPasswordAuthentication() { |
| return new PasswordAuthentication(smtpUsername, smtpPassword); |
| } |
| }); |
| } else { |
| session = Session.getInstance(props, null); |
| } |
| |
| MailUtils.setupLogging(session, log, transportOut); |
| |
| // set the synchronise callback table |
| if (cfgCtx.getProperty(BaseConstants.CALLBACK_TABLE) == null){ |
| cfgCtx.setProperty(BaseConstants.CALLBACK_TABLE, new ConcurrentHashMap()); |
| } |
| } |
| |
| /** |
| * Send the given message over the Mail transport |
| * |
| * @param msgCtx the axis2 message context |
| * @throws AxisFault on error |
| */ |
| public void sendMessage(MessageContext msgCtx, String targetAddress, |
| OutTransportInfo outTransportInfo) throws AxisFault { |
| |
| MailOutTransportInfo mailOutInfo = null; |
| |
| if (targetAddress != null) { |
| if (targetAddress.startsWith(MailConstants.TRANSPORT_NAME)) { |
| targetAddress = targetAddress.substring(MailConstants.TRANSPORT_NAME.length()+1); |
| } |
| |
| if (msgCtx.getReplyTo() != null && |
| !AddressingConstants.Final.WSA_NONE_URI.equals(msgCtx.getReplyTo().getAddress()) && |
| !AddressingConstants.Final.WSA_ANONYMOUS_URL.equals(msgCtx.getReplyTo().getAddress())) { |
| |
| String replyTo = msgCtx.getReplyTo().getAddress(); |
| if (replyTo.startsWith(MailConstants.TRANSPORT_NAME)) { |
| replyTo = replyTo.substring(MailConstants.TRANSPORT_NAME.length()+1); |
| } |
| try { |
| mailOutInfo = new MailOutTransportInfo(new InternetAddress(replyTo)); |
| } catch (AddressException e) { |
| handleException("Invalid reply address/es : " + replyTo, e); |
| } |
| } else { |
| mailOutInfo = new MailOutTransportInfo(smtpFromAddress); |
| } |
| |
| try { |
| mailOutInfo.setTargetAddresses(InternetAddress.parse(targetAddress)); |
| } catch (AddressException e) { |
| handleException("Invalid target address/es : " + targetAddress, e); |
| } |
| } else if (outTransportInfo != null && outTransportInfo instanceof MailOutTransportInfo) { |
| mailOutInfo = (MailOutTransportInfo) outTransportInfo; |
| } |
| |
| if (mailOutInfo != null) { |
| try { |
| String messageID = sendMail(mailOutInfo, msgCtx); |
| // this is important in axis2 client side if the mail transport uses anonymous addressing |
| // the sender have to wait util the response comes. |
| if (!msgCtx.getOptions().isUseSeparateListener() && !msgCtx.isServerSide()){ |
| waitForReply(msgCtx, messageID); |
| } |
| } catch (MessagingException e) { |
| handleException("Error generating mail message", e); |
| } catch (IOException e) { |
| handleException("Error generating mail message", e); |
| } |
| } else { |
| handleException("Unable to determine out transport information to send message"); |
| } |
| } |
| |
| private void waitForReply(MessageContext msgContext, String mailMessageID) throws AxisFault { |
| // piggy back message constant is used to pass a piggy back |
| // message context in asnych model |
| if (!(msgContext.getAxisOperation() instanceof OutInAxisOperation) && |
| (msgContext.getProperty(org.apache.axis2.Constants.PIGGYBACK_MESSAGE) == null)) { |
| return; |
| } |
| |
| ConfigurationContext configContext = msgContext.getConfigurationContext(); |
| // if the mail message listner has not started we need to start it |
| if (!configContext.getListenerManager().isListenerRunning(MailConstants.TRANSPORT_NAME)) { |
| TransportInDescription mailTo = |
| configContext.getAxisConfiguration().getTransportIn(MailConstants.TRANSPORT_NAME); |
| if (mailTo == null) { |
| handleException("Could not find the transport receiver for " + |
| MailConstants.TRANSPORT_NAME); |
| } |
| configContext.getListenerManager().addListener(mailTo, false); |
| } |
| |
| SynchronousCallback synchronousCallback = new SynchronousCallback(msgContext); |
| Map callBackMap = (Map) msgContext.getConfigurationContext(). |
| getProperty(BaseConstants.CALLBACK_TABLE); |
| callBackMap.put(mailMessageID, synchronousCallback); |
| synchronized (synchronousCallback) { |
| try { |
| synchronousCallback.wait(msgContext.getOptions().getTimeOutInMilliSeconds()); |
| } catch (InterruptedException e) { |
| handleException("Error occured while waiting ..", e); |
| } |
| } |
| |
| if (!synchronousCallback.isComplete()){ |
| // when timeout occurs remove this entry. |
| callBackMap.remove(mailMessageID); |
| handleException("Timeout while waiting for a response"); |
| } |
| } |
| |
| /** |
| * Populate email with a SOAP formatted message |
| * @param outInfo the out transport information holder |
| * @param msgContext the message context that holds the message to be written |
| * @throws AxisFault on error |
| * @return id of the send mail message |
| */ |
| private String sendMail(MailOutTransportInfo outInfo, MessageContext msgContext) |
| throws AxisFault, MessagingException, IOException { |
| |
| OMOutputFormat format = BaseUtils.getOMOutputFormat(msgContext); |
| // Make sure that non textual attachements are sent with base64 transfer encoding |
| // instead of binary. |
| format.setProperty(OMOutputFormat.USE_CTE_BASE64_FOR_NON_TEXTUAL_ATTACHMENTS, true); |
| |
| MessageFormatter messageFormatter = BaseUtils.getMessageFormatter(msgContext); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Creating MIME message using message formatter " + |
| messageFormatter.getClass().getSimpleName()); |
| } |
| |
| WSMimeMessage message = null; |
| if (outInfo.getFromAddress() != null) { |
| message = new WSMimeMessage(session, outInfo.getFromAddress().getAddress()); |
| } else { |
| message = new WSMimeMessage(session, ""); |
| } |
| |
| |
| Map trpHeaders = (Map) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS); |
| if (log.isDebugEnabled() && trpHeaders != null) { |
| log.debug("Using transport headers: " + trpHeaders); |
| } |
| |
| // set From address - first check if this is a reply, then use from address from the |
| // transport out, else if any custom transport headers set on this message, or default |
| // to the transport senders default From address |
| if (outInfo.getTargetAddresses() != null && outInfo.getFromAddress() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting From header to " + outInfo.getFromAddress().getAddress() + |
| " from OutTransportInfo"); |
| } |
| message.setFrom(outInfo.getFromAddress()); |
| message.setReplyTo((new Address []{outInfo.getFromAddress()})); |
| } else if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_FROM)) { |
| InternetAddress from = |
| new InternetAddress((String) trpHeaders.get(MailConstants.MAIL_HEADER_FROM)); |
| if (log.isDebugEnabled()) { |
| log.debug("Setting From header to " + from.getAddress() + |
| " from transport headers"); |
| } |
| message.setFrom(from); |
| message.setReplyTo(new Address[] { from }); |
| } else { |
| if (smtpFromAddress != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting From header to " + smtpFromAddress.getAddress() + |
| " from transport configuration"); |
| } |
| message.setFrom(smtpFromAddress); |
| message.setReplyTo(new Address[] {smtpFromAddress}); |
| } else { |
| handleException("From address for outgoing message cannot be determined"); |
| } |
| } |
| |
| // set To address/es to any custom transport header set on the message, else use the reply |
| // address from the out transport information |
| if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_TO)) { |
| Address[] to = |
| InternetAddress.parse((String) trpHeaders.get(MailConstants.MAIL_HEADER_TO)); |
| if (log.isDebugEnabled()) { |
| log.debug("Setting To header to " + InternetAddress.toString(to) + |
| " from transport headers"); |
| } |
| message.setRecipients(Message.RecipientType.TO, to); |
| } else if (outInfo.getTargetAddresses() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting To header to " + InternetAddress.toString( |
| outInfo.getTargetAddresses()) + " from OutTransportInfo"); |
| } |
| message.setRecipients(Message.RecipientType.TO, outInfo.getTargetAddresses()); |
| } else { |
| handleException("To address for outgoing message cannot be determined"); |
| } |
| |
| // set Cc address/es to any custom transport header set on the message, else use the |
| // Cc list from original request message |
| if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_CC)) { |
| Address[] cc = |
| InternetAddress.parse((String) trpHeaders.get(MailConstants.MAIL_HEADER_CC)); |
| if (log.isDebugEnabled()) { |
| log.debug("Setting Cc header to " + InternetAddress.toString(cc) + |
| " from transport headers"); |
| } |
| message.setRecipients(Message.RecipientType.CC, cc); |
| } else if (outInfo.getCcAddresses() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting Cc header to " + InternetAddress.toString( |
| outInfo.getCcAddresses()) + " from OutTransportInfo"); |
| } |
| message.setRecipients(Message.RecipientType.CC, outInfo.getCcAddresses()); |
| } |
| |
| // set Bcc address/es to any custom addresses set at the transport sender level + any |
| // custom transport header |
| if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_BCC)) { |
| InternetAddress[] bcc = |
| InternetAddress.parse((String) trpHeaders.get(MailConstants.MAIL_HEADER_BCC)); |
| if (log.isDebugEnabled()) { |
| log.debug("Adding Bcc header values " + InternetAddress.toString(bcc) + |
| " from transport headers"); |
| } |
| message.addRecipients(Message.RecipientType.BCC, bcc); |
| } |
| if (smtpBccAddresses != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Adding Bcc header values " + InternetAddress.toString(smtpBccAddresses) + |
| " from transport configuration"); |
| } |
| message.addRecipients(Message.RecipientType.BCC, smtpBccAddresses); |
| } |
| |
| // set subject |
| if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_SUBJECT)) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting Subject header to '" + trpHeaders.get( |
| MailConstants.MAIL_HEADER_SUBJECT) + "' from transport headers"); |
| } |
| message.setSubject((String) trpHeaders.get(MailConstants.MAIL_HEADER_SUBJECT)); |
| } else if (outInfo.getSubject() != null) { |
| if (log.isDebugEnabled()) { |
| log.debug("Setting Subject header to '" + outInfo.getSubject() + |
| "' from transport headers"); |
| } |
| message.setSubject(outInfo.getSubject()); |
| } else { |
| if (log.isDebugEnabled()) { |
| log.debug("Generating default Subject header from SOAP action"); |
| } |
| message.setSubject(BaseConstants.SOAPACTION + ": " + msgContext.getSoapAction()); |
| } |
| |
| //TODO: use a combined message id for smtp so that it generates a unique id while |
| // being able to support asynchronous communication. |
| // if a custom message id is set, use it |
| // if (msgContext.getMessageID() != null) { |
| // message.setHeader(MailConstants.MAIL_HEADER_MESSAGE_ID, msgContext.getMessageID()); |
| // message.setHeader(MailConstants.MAIL_HEADER_X_MESSAGE_ID, msgContext.getMessageID()); |
| // } |
| |
| // if this is a reply, set reference to original message |
| if (outInfo.getRequestMessageID() != null) { |
| message.setHeader(MailConstants.MAIL_HEADER_IN_REPLY_TO, outInfo.getRequestMessageID()); |
| message.setHeader(MailConstants.MAIL_HEADER_REFERENCES, outInfo.getRequestMessageID()); |
| |
| } else { |
| if (trpHeaders != null && |
| trpHeaders.containsKey(MailConstants.MAIL_HEADER_IN_REPLY_TO)) { |
| message.setHeader(MailConstants.MAIL_HEADER_IN_REPLY_TO, |
| (String) trpHeaders.get(MailConstants.MAIL_HEADER_IN_REPLY_TO)); |
| } |
| if (trpHeaders != null && trpHeaders.containsKey(MailConstants.MAIL_HEADER_REFERENCES)) { |
| message.setHeader(MailConstants.MAIL_HEADER_REFERENCES, |
| (String) trpHeaders.get(MailConstants.MAIL_HEADER_REFERENCES)); |
| } |
| } |
| |
| // set Date |
| message.setSentDate(new Date()); |
| |
| |
| // set SOAPAction header |
| message.setHeader(BaseConstants.SOAPACTION, msgContext.getSoapAction()); |
| |
| // write body |
| DataHandler dataHandler = new DataHandler(messageFormatter.getDataSource(msgContext, format, msgContext.getSoapAction())); |
| |
| MimeMultipart mimeMultiPart = null; |
| |
| String mFormat = (String) msgContext.getProperty(MailConstants.TRANSPORT_MAIL_FORMAT); |
| if (mFormat == null) { |
| mFormat = defaultMailFormat; |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Using mail format '" + mFormat + "'"); |
| } |
| |
| MimePart mainPart; |
| if (MailConstants.TRANSPORT_FORMAT_MP.equals(mFormat)) { |
| mimeMultiPart = new MimeMultipart(); |
| MimeBodyPart mimeBodyPart1 = new MimeBodyPart(); |
| mimeBodyPart1.setContent("Web Service Message Attached","text/plain"); |
| MimeBodyPart mimeBodyPart2 = new MimeBodyPart(); |
| mimeMultiPart.addBodyPart(mimeBodyPart1); |
| mimeMultiPart.addBodyPart(mimeBodyPart2); |
| message.setContent(mimeMultiPart); |
| mainPart = mimeBodyPart2; |
| } else if (MailConstants.TRANSPORT_FORMAT_ATTACHMENT.equals(mFormat)) { |
| mimeMultiPart = new MimeMultipart(); |
| MimeBodyPart mimeBodyPart1 = new MimeBodyPart(); |
| mimeBodyPart1.setContent("Web Service Message Attached","text/plain"); |
| MimeBodyPart mimeBodyPart2 = new MimeBodyPart(); |
| mimeMultiPart.addBodyPart(mimeBodyPart1); |
| mimeMultiPart.addBodyPart(mimeBodyPart2); |
| message.setContent(mimeMultiPart); |
| |
| String fileName = (String) msgContext.getProperty( |
| MailConstants.TRANSPORT_FORMAT_ATTACHMENT_FILE); |
| if (fileName != null) { |
| mimeBodyPart2.setFileName(fileName); |
| } else { |
| mimeBodyPart2.setFileName("attachment"); |
| } |
| |
| mainPart = mimeBodyPart2; |
| } else { |
| mainPart = message; |
| } |
| |
| try { |
| mainPart.setHeader(BaseConstants.SOAPACTION, msgContext.getSoapAction()); |
| mainPart.setDataHandler(dataHandler); |
| |
| // AXIOM's idea of what is textual also includes application/xml and |
| // application/soap+xml (which JavaMail considers as binary). For these content types |
| // always use quoted-printable transfer encoding. Note that JavaMail is a bit smarter |
| // here because it can choose between 7bit and quoted-printable automatically, but it |
| // needs to scan the entire content to determine this. |
| if (msgContext.getOptions().getProperty("Content-Transfer-Encoding") != null) { |
| mainPart.setHeader("Content-Transfer-Encoding", |
| (String) msgContext.getOptions().getProperty("Content-Transfer-Encoding")); |
| } else { |
| ContentType contentType = new ContentType(dataHandler.getContentType()); |
| if (!contentType.getMediaType().hasPrimaryType("multipart") && contentType.isTextual()) { |
| mainPart.setHeader("Content-Transfer-Encoding", "quoted-printable"); |
| } |
| } |
| |
| //setting any custom headers defined by the user |
| if (msgContext.getOptions().getProperty(MailConstants.TRANSPORT_MAIL_CUSTOM_HEADERS) != null) { |
| Map customTransportHeaders = (Map)msgContext.getOptions().getProperty(MailConstants.TRANSPORT_MAIL_CUSTOM_HEADERS); |
| for (Object header: customTransportHeaders.keySet()){ |
| mainPart.setHeader((String)header,(String)customTransportHeaders.get(header)); |
| } |
| } |
| |
| |
| |
| log.debug("Sending message"); |
| Transport.send(message); |
| |
| // update metrics |
| metrics.incrementMessagesSent(msgContext); |
| long bytesSent = message.getBytesSent(); |
| if (bytesSent != -1) { |
| metrics.incrementBytesSent(msgContext, bytesSent); |
| } |
| |
| } catch (MessagingException | ParseException e) { |
| metrics.incrementFaultsSending(); |
| handleException("Error creating mail message or sending it to the configured server", e); |
| |
| } |
| return message.getMessageID(); |
| } |
| |
| @Override |
| public void stop() { |
| super.stop(); |
| metrics.reset(); |
| smtpUsername = null; |
| smtpPassword = null; |
| smtpBccAddresses = null; |
| smtpFromAddress = null; |
| session = null; |
| } |
| } |