Posted by
Ryan Puddephatt on
URL: https://discuss.orbeon.com/Improvement-to-the-Email-Processor-tp32960.html
Alex/Erik,
I've attached a change to the email processor that allows it to pass
credentials by adding the following to the config.
<credentials>
<username>user</username>
<password>password</password>
</credentials>
Thanks
Ryan
Ryan Puddephatt
Software Engineer
Teleflex Group - IT UK
1 Michaelson Square
Livingston
West Lothian
Scotland
EH54 7DP
e>
[hidden email] <mailto:
[hidden email]>
t> +44(0)1506 407 110
f> +44(0)1506 407 108
w> www.teleflex.com <
http://www.teleflex.com>
mixed
alternative
related
multipart/mixed
multipart/alternative
multipart/related
multipart/.+
/**
* Copyright (C) 2004 Orbeon, Inc.
*
* This program 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 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.
*
* The full text of the license is available at
http://www.gnu.org/copyleft/lesser.html */
package org.orbeon.oxf.processor;
import org.apache.commons.fileupload.DefaultFileItemFactory;
import org.apache.commons.fileupload.FileItem;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.pipeline.api.PipelineContext;
import org.orbeon.oxf.processor.generator.RequestGenerator;
import org.orbeon.oxf.processor.generator.URLGenerator;
import org.orbeon.oxf.resources.URLFactory;
import org.orbeon.oxf.util.Base64;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.util.NetUtils;
import org.orbeon.oxf.util.SystemUtils;
import org.orbeon.oxf.xml.ForwardingContentHandler;
import org.orbeon.oxf.xml.ProcessorOutputXMLReader;
import org.orbeon.oxf.xml.TransformerUtils;
import org.orbeon.oxf.xml.XMLUtils;
import org.orbeon.oxf.xml.dom4j.LocationData;
import org.orbeon.oxf.xml.dom4j.LocationSAXWriter;
import org.orbeon.oxf.xml.dom4j.NonLazyUserDataDocument;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.Message;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.*;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.Iterator;
import java.util.Properties;
/**
* This processor allows sending emails. It supports multipart messages and inline as well as
* out-of-line attachments.
*
* For some useful JavaMail information:
http://java.sun.com/products/javamail/FAQ.html *
* TODO:
* o revise support of text/html
* o built-in support for HTML could handle src="cid:*" with part/message ids
* o support text/xml? or just XHTML?
* o build message with SAX, not DOM, so streaming of input is possible [not necessarily a big win]
*/
public class EmailProcessor extends ProcessorImpl {
static private Logger logger = LoggerFactory.createLogger(EmailProcessor.class);
// Properties for this processor
public static final String EMAIL_SMTP_HOST = "smtp-host";
public static final String EMAIL_TEST_TO = "test-to";
public static final String EMAIL_TEST_SMTP_HOST = "test-smtp-host";
public static final String EMAIL_FORCE_TO_DEPRECATED = "forceto"; // deprecated
public static final String EMAIL_HOST_DEPRECATED = "host"; // deprecated
public static final String EMAIL_CONFIG_NAMESPACE_URI = "
http://www.orbeon.com/oxf/email";
private static final String DEFAULT_MULTIPART = "mixed";
private static final String DEFAULT_TEXT_ENCODING = "iso-8859-1";
//private String username;
//private String password;
public EmailProcessor() {
addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA, EMAIL_CONFIG_NAMESPACE_URI));
}
public void start(PipelineContext pipelineContext) {
try {
Document dataDocument = readInputAsDOM4J(pipelineContext, INPUT_DATA);
Element messageElement = dataDocument.getRootElement();
// Get system id (will likely be null if document is generated dynamically)
LocationData locationData = (LocationData) messageElement.getData();
String dataInputSystemId = locationData.getSystemID();
// Set SMTP host
Properties properties = new Properties();
String testSmtpHostProperty = getPropertySet().getString(EMAIL_TEST_SMTP_HOST);
if (testSmtpHostProperty != null) {
// Test SMTP Host from properties overrides the local configuration
properties.setProperty("mail.smtp.host", testSmtpHostProperty);
} else {
// Try regular config parameter and property
String host = messageElement.element("smtp-host").getTextTrim();
if (host != null && !host.equals("")) {
// Precedence goes to the local config parameter
properties.setProperty("mail.smtp.host", host);
} else {
// Otherwise try to use a property
host = getPropertySet().getString(EMAIL_SMTP_HOST);
if (host == null)
host = getPropertySet().getString(EMAIL_HOST_DEPRECATED);
if (host == null)
throw new OXFException("Could not find SMTP host in configuration or in properties");
properties.setProperty("mail.smtp.host", host);
}
}
// Get credentials
Element credentials = messageElement.element("credentials");
// Create session variable, but don't assign it
Session session = null;
// Check if credentials are supplied
if(credentials != null && !credentials.equals("")) {
if(logger.isInfoEnabled())
logger.info("Authentication");
// Set the auth property to true
properties.setProperty("mail.smtp.auth", "true");
String username = credentials.element("username").getStringValue();
String password = credentials.element("password").getStringValue();
if(logger.isInfoEnabled())
logger.info("Username: "+username+"; Password: "+password);
// Create an authenticator
Authenticator auth = new SMTPAuthenticator(username,password);
// Create session with auth
session = Session.getInstance(properties,auth);
} else {
if(logger.isInfoEnabled())
logger.info("No Authentication");
session = Session.getInstance(properties);
}
// Create message
Message message = new MimeMessage(session);
// Set From
message.setFrom(createAddress(messageElement.element("from")));
// Set To
String testToProperty = getPropertySet().getString(EMAIL_TEST_TO);
if (testToProperty == null)
testToProperty = getPropertySet().getString(EMAIL_FORCE_TO_DEPRECATED);
if (testToProperty != null) {
// Test To from properties overrides local configuration
message.addRecipient(Message.RecipientType.TO, new InternetAddress(testToProperty));
} else {
// Regular list of To elements
for (Iterator i = messageElement.elements("to").iterator(); i.hasNext();) {
Element toElement = (Element) i.next();
InternetAddress address = createAddress(toElement);
message.addRecipient(Message.RecipientType.TO, address);
}
}
// Set Cc
for (Iterator i = messageElement.elements("cc").iterator(); i.hasNext();) {
Element toElement = (Element) i.next();
InternetAddress address = createAddress(toElement);
message.addRecipient(Message.RecipientType.CC, address);
}
// Set Bcc
for (Iterator i = messageElement.elements("bcc").iterator(); i.hasNext();) {
Element toElement = (Element) i.next();
InternetAddress address = createAddress(toElement);
message.addRecipient(Message.RecipientType.BCC, address);
}
// Set headers if any
for (Iterator i = messageElement.elements("header").iterator(); i.hasNext();) {
final Element headerElement = (Element) i.next();
final String headerName = headerElement.element("name").getTextTrim();
final String headerValue = headerElement.element("value").getTextTrim();
message.addHeader(headerName, headerValue);
}
// Set subject
message.setSubject(messageElement.element("subject").getStringValue());
// Handle body
Element textElement = messageElement.element("text");
Element bodyElement = messageElement.element("body");
if (textElement != null) {
// Old deprecated mechanism (simple text body)
message.setText(textElement.getStringValue());
} else if (bodyElement != null) {
// New mechanism with body and parts
handleBody(pipelineContext, dataInputSystemId, message, bodyElement);
} else {
throw new OXFException("Main text or body element not found");// TODO: location info
}
// Send message
Transport transport = session.getTransport("smtp");
Transport.send(message);
transport.close();
} catch (Exception e) {
throw new OXFException(e);
}
}
private void handleBody(PipelineContext pipelineContext, String dataInputSystemId, Part parentPart, Element bodyElement) throws Exception {
// Find out if there are embedded parts
Iterator parts = bodyElement.elementIterator("part");
String multipart;
if (bodyElement.getName().equals("body")) {
multipart = bodyElement.attributeValue("mime-multipart");
if (multipart != null && !parts.hasNext())
throw new OXFException("mime-multipart attribute on body element requires part children elements");
String contentTypeFromAttribute = NetUtils.getContentTypeMediaType(bodyElement.attributeValue("content-type"));
if (contentTypeFromAttribute != null && contentTypeFromAttribute.startsWith("multipart/"))
contentTypeFromAttribute.substring("multipart/".length());
if (parts.hasNext() && multipart == null)
multipart = DEFAULT_MULTIPART;
} else {
String contentTypeAttribute = NetUtils.getContentTypeMediaType(bodyElement.attributeValue("content-type"));
multipart = (contentTypeAttribute != null && contentTypeAttribute.startsWith("multipart/")) ? contentTypeAttribute.substring("multipart/".length()) : null;
}
if (multipart != null) {
// Multipart content is requested
MimeMultipart mimeMultipart = new MimeMultipart(multipart);
// Iterate through parts
for (Iterator i = parts; i.hasNext();) {
Element partElement = (Element) i.next();
MimeBodyPart mimeBodyPart = new MimeBodyPart();
handleBody(pipelineContext, dataInputSystemId, mimeBodyPart, partElement);
mimeMultipart.addBodyPart(mimeBodyPart);
}
// Set content on parent part
parentPart.setContent(mimeMultipart);
} else {
// No multipart, just use the content of the element and add to the current part (which can be the main message)
handlePart(pipelineContext, dataInputSystemId, parentPart, bodyElement);
}
}
private void handlePart(PipelineContext pipelineContext, String dataInputSystemId, Part parentPart, Element partOrBodyElement) throws Exception {
final String name = partOrBodyElement.attributeValue("name");
String contentTypeAttribute = partOrBodyElement.attributeValue("content-type");
final String contentType = NetUtils.getContentTypeMediaType(contentTypeAttribute);
final String charset;
{
String c = NetUtils.getContentTypeCharset(contentTypeAttribute);
charset = (c != null) ? c : DEFAULT_TEXT_ENCODING;
}
final String contentTypeWithCharset = contentType + "; charset=" + charset;
final String src = partOrBodyElement.attributeValue("src");
// Either a String or a FileItem
final Object content;
if (src != null) {
// Content of the part is not inline
// Generate a Document from the source
SAXSource source = getSAXSource(EmailProcessor.this, pipelineContext, src, dataInputSystemId, contentType);
content = handleStreamedPartContent(pipelineContext, source, contentType, charset);
} else {
// Content of the part is inline
// In the cases of text/html and XML, there must be exactly one root element
boolean needsRootElement = "text/html".equals(contentType);// || ProcessorUtils.isXMLContentType(contentType);
if (needsRootElement && partOrBodyElement.elements().size() != 1)
throw new ValidationException("The <body> or <part> element must contain exactly one element for text/html",
(LocationData) partOrBodyElement.getData());
// Create Document and convert it into a String
Element rootElement = (Element)(needsRootElement ? partOrBodyElement.elements().get(0) : partOrBodyElement);
Document partDocument = new NonLazyUserDataDocument();
partDocument.setRootElement((Element) rootElement.clone());
content = handleInlinePartContent(partDocument, contentType);
}
if (!(ProcessorUtils.isTextContentType(contentType) || ProcessorUtils.isXMLContentType(contentType))) {
// This is binary content
if (content instanceof FileItem) {
final FileItem fileItem = (FileItem) content;
parentPart.setDataHandler(new DataHandler(new DataSource() {
public String getContentType() {
return contentType;
}
public InputStream getInputStream() throws IOException {
return fileItem.getInputStream();
}
public String getName() {
return name;
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Write operation not supported");
}
}));
} else {
byte[] data = XMLUtils.base64StringToByteArray((String) content);
parentPart.setDataHandler(new DataHandler(new SimpleBinaryDataSource(name, contentType, data)));
}
} else {
// This is text content
if (content instanceof FileItem) {
// The text content was encoded when written to the FileItem
final FileItem fileItem = (FileItem) content;
parentPart.setDataHandler(new DataHandler(new DataSource() {
public String getContentType() {
// This always contains a charset
return contentTypeWithCharset;
}
public InputStream getInputStream() throws IOException {
// This is encoded with the appropriate charset (user-defined, or the default)
return fileItem.getInputStream();
}
public String getName() {
return name;
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Write operation not supported");
}
}));
} else {
parentPart.setDataHandler(new DataHandler(new SimpleTextDataSource(name, contentTypeWithCharset, (String) content)));
}
}
// Set content-disposition header
String contentDisposition = partOrBodyElement.attributeValue("content-disposition");
if (contentDisposition != null)
parentPart.setDisposition(contentDisposition);
// Set content-id header
String contentId = partOrBodyElement.attributeValue("content-id");
if (contentId != null)
parentPart.setHeader("content-id", "<" + contentId + ">");
//part.setContentID(contentId);
}
private String handleInlinePartContent(Document document, String contentType) throws SAXException {
if ("text/html".equals(contentType)) {
// Convert XHTML into an HTML String
StringWriter writer = new StringWriter();
TransformerHandler identity = TransformerUtils.getIdentityTransformerHandler();
identity.getTransformer().setOutputProperty(OutputKeys.METHOD, "html");
identity.setResult(new StreamResult(writer));
LocationSAXWriter saxw = new LocationSAXWriter();
saxw.setContentHandler(identity);
saxw.write(document);
return writer.toString();
} else {
// For other types, just return the text nodes
return document.getStringValue();
}
}
public static FileItem handleStreamedPartContent(PipelineContext pipelineContext, SAXSource source, String contentType, String encoding)
throws IOException, TransformerException {
final FileItem fileItem = new DefaultFileItemFactory(RequestGenerator.getMaxMemorySizeProperty(), SystemUtils.getTemporaryDirectory())
.createItem("dummy", "dummy", false, null);
// Make sure the file is deleted when the context is destroyed
pipelineContext.addContextListener(new PipelineContext.ContextListenerAdapter() {
public void contextDestroyed(boolean success) {
fileItem.delete();
}
});
// Write character content to the FileItem instance
Writer writer = null;
OutputStream os = null;
final boolean useWriter = ProcessorUtils.isTextContentType(contentType) || ProcessorUtils.isXMLContentType(contentType);
try {
os = fileItem.getOutputStream();
if (useWriter)
writer = new BufferedWriter(new OutputStreamWriter(os, encoding));
final OutputStream _os = os;
final Writer _writer = writer;
Transformer identity = TransformerUtils.getIdentityTransformer();
identity.transform(source, new SAXResult(new ForwardingContentHandler() {
public void characters(char[] chars, int start, int length) {
try {
if (useWriter)
_writer.write(chars, start, length);
else
_os.write(Base64.decode(new String(chars, start, length)));
} catch (IOException e) {
throw new OXFException(e);
}
}
}));
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
throw new OXFException(e);
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
throw new OXFException(e);
}
}
}
return fileItem;
}
private InternetAddress createAddress(Element addressElement) throws AddressException, UnsupportedEncodingException {
String email = addressElement.element("email").getStringValue();
Element nameElement = addressElement.element("name");
return nameElement == null ? new InternetAddress(email)
: new InternetAddress(email, nameElement.getStringValue());
}
private class SimpleTextDataSource implements DataSource {
String contentType;
String text;
String name;
public SimpleTextDataSource(String name, String contentType, String text) {
this.name = name;
this.contentType = contentType;
this.text = text;
}
public String getContentType() {
return contentType;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(text.getBytes("utf-8"));
}
public String getName() {
return name;
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Write operation not supported");
}
}
private class SimpleBinaryDataSource implements DataSource {
String contentType;
byte[] data;
String name;
public SimpleBinaryDataSource(String name, String contentType, byte[] data) {
this.name = name;
this.contentType = contentType;
this.data = data;
}
public String getContentType() {
return contentType;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(data);
}
public String getName() {
return name;
}
public OutputStream getOutputStream() throws IOException {
throw new IOException("Write operation not supported");
}
}
public static SAXSource getSAXSource(Processor processor, PipelineContext pipelineContext, String href, String base, String contentType) {
try {
// There are two cases:
// 1. We read the source as SAX
// o This is required when reading from a processor input; in this case, we behave like
// the inline case
// o When reading from another type of URI, the resource could be in theory any type
// of file.
// 2. We don't read the source as SAX
// o It is particularly useful to support this when resources are to be used as binary
// attachments such as images.
// o Here, we consider that the source can be XML, text/html, text/*,
// or binary. We do not handle reading Base64-encoded files. We leverage the URL
// generator to obtain the content in XML format.
XMLReader xmlReader;
{
String inputName = ProcessorImpl.getProcessorInputSchemeInputName(href);
if (inputName != null) {
// Resolve to input of current processor
xmlReader = new ProcessorOutputXMLReader(pipelineContext, processor.getInputByName(inputName).getOutput());
} else {
// Resolve to regular URI
Processor urlGenerator = (contentType == null)
? new URLGenerator(URLFactory.createURL(base, href))
: new URLGenerator(URLFactory.createURL(base, href), contentType, true);
xmlReader = new ProcessorOutputXMLReader(pipelineContext, urlGenerator.createOutput(ProcessorImpl.OUTPUT_DATA));
}
}
// Return SAX Source based on XML Reader
SAXSource saxSource = new SAXSource(xmlReader, new InputSource());
saxSource.setSystemId(href);
return saxSource;
} catch (IOException e) {
throw new OXFException(e);
}
}
private class SMTPAuthenticator extends javax.mail.Authenticator {
private String username;
private String password;
public SMTPAuthenticator(String user, String pass){
username = user;
password = pass;
}
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username,password);
}
}
}
// Set content-transfer-encoding header
// final String contentTransferEncoding = partElement.attributeValue("content-transfer-encoding");
//
// MimeBodyPart part = new MimeBodyPart() {
// protected void updateHeaders() throws MessagingException {
// super.updateHeaders();
// if (contentTransferEncoding != null)
// setHeader("Content-Transfer-Encoding", contentTransferEncoding);
// }
// };
// // Set content-disposition header
// String contentDisposition = partElement.attributeValue("content-disposition");
// if (contentDisposition != null)
// part.setDisposition(contentDisposition);
//
// part.setDataHandler(new DataHandler(new SimpleTextDataSource(name, contentType, content)));
// mimeMultipart.addBodyPart(part);
--
You receive this message as a subscriber of the
[hidden email] mailing list.
To unsubscribe: mailto:
[hidden email]
For general help: mailto:
[hidden email]?subject=help
ObjectWeb mailing lists service home page:
http://www.objectweb.org/wws