blob: 7236ad4789eda08a4b764e7ff971e244a9f2dc0f [file] [log] [blame]
/*
* 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.format.MessageFormatterEx;
import org.apache.axis2.format.MessageFormatterExAdapter;
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
MessageFormatterEx messageFormatterEx;
if (messageFormatter instanceof MessageFormatterEx) {
messageFormatterEx = (MessageFormatterEx)messageFormatter;
} else {
messageFormatterEx = new MessageFormatterExAdapter(messageFormatter);
}
DataHandler dataHandler = new DataHandler(messageFormatterEx.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;
}
}