/*******************************************************************************
* Imixs Workflow
* Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,
* http://www.imixs.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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
* General Public License for more details.
*
* You can receive a copy of the GNU General Public
* License at http://www.gnu.org/licenses/gpl.html
*
* Project:
* http://www.imixs.org
* http://java.net/projects/imixs-workflow
*
* Contributors:
* Imixs Software Solutions GmbH - initial API and implementation
* Ralph Soika - Software Developer
*******************************************************************************/
package org.imixs.marty.plugins;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.util.ByteArrayDataSource;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.imixs.marty.ejb.ProfileService;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.WorkflowContext;
import org.imixs.workflow.exceptions.PluginException;
/**
* This Plugin extends the Imixs Workflow Plugin.
*
* The Plugin translates recipient addresses with the mail address stored in the
* users profile
*
* In addition this plugin adds the attachments from the blob workItem into the
* mail body if the tag <attachment/> was found.
*
*
* @author rsoika
* @version 2.0
*/
public class MailPlugin extends org.imixs.workflow.engine.plugins.MailPlugin {
private ProfileService profileService = null;
public static String PROFILESERVICE_NOT_BOUND = "PROFILESERVICE_NOT_BOUND";
public static String PROPERTYSERVICE_NOT_BOUND = "PROPERTYSERVICE_NOT_BOUND";
public static String INVALID_EMAIL = "INVALID_EMAIL";
private static Logger logger = Logger.getLogger(MailPlugin.class.getName());
@Override
public void init(WorkflowContext actx) throws PluginException {
super.init(actx);
// lookup profile service EJB
String jndiName = "ejb/ProfileService";
InitialContext ictx;
try {
ictx = new InitialContext();
Context ctx = (Context) ictx.lookup("java:comp/env");
profileService = (ProfileService) ctx.lookup(jndiName);
} catch (NamingException e) {
throw new PluginException(MailPlugin.class.getSimpleName(), PROFILESERVICE_NOT_BOUND,
"ProfileService not bound", e);
}
}
/**
* This method adds the attachments of the blob workitem to the MimeMessage
*/
@Override
public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException {
// run default functionality
ItemCollection result = super.run(documentContext, documentActivity);
// now get the Mail Session object
MimeMessage mailMessage = (MimeMessage) super.getMailMessage();
if (mailMessage != null) {
// test for blob workitem to add attachemtns
ItemCollection blobWorkitem = BlobWorkitemHandler.load(this.getWorkflowService().getDocumentService(),
documentContext);
if (blobWorkitem != null) {
try {
attachFiles(blobWorkitem);
} catch (MessagingException e) {
logger.warning("unable to attach files!");
e.printStackTrace();
}
}
}
return result;
}
/**
* this helper method creates an internet address from a string if the
* string has illegal characters like whitespace the string will be
* surrounded with "". If you subclass this MailPlugin Class you can
* overwrite this method to return a different mail-address name or lookup a
* mail attribute in a directory like a ldap directory.
*
* @param aAddr
* @return
* @throws AddressException
*/
public InternetAddress getInternetAddress(String aAddr) throws AddressException {
// is smtp address skip profile lookup?
if (aAddr.indexOf('@') > -1)
return super.getInternetAddress(aAddr);
// try to get email from the users profile
try {
aAddr = fetchEmail(aAddr);
if (aAddr.indexOf('@') == -1) {
logger.warning("smtp mail address for '" + aAddr + "' could not be resolved!");
return null;
}
} catch (NamingException e) {
// no valid email was found!
logger.warning("smtp mail address for '" + aAddr + "' could not be resolved - " + e.getMessage());
// e.printStackTrace();
// avoid sending mail to this address!
return null;
}
return super.getInternetAddress(aAddr);
}
/**
* This method lookups the emailadress for a given user account through the
* ProfileService. If no profile is found or email is not valid the method
* throws a NamingException.
*
* @param aUserID
* @return
* @throws NamingException
*/
private String fetchEmail(String aUserID) throws NamingException {
ItemCollection itemColProfile = profileService.findProfileById(aUserID);
if (itemColProfile == null)
throw new NamingException("[MartyMailPlugin] No Profile found for: " + aUserID);
String sEmail = itemColProfile.getItemValueString("txtEmail");
logger.fine("ProfileService - EmailLookup =" + sEmail);
if (sEmail != null && !"".equals(sEmail)) {
if (sEmail.indexOf("http") > -1 || sEmail.indexOf("//") > -1)
throw new NamingException("[MartyMailPlugin] Invalid Email: ID=" + aUserID + " Email=" + sEmail);
return sEmail;
}
// test if account contains protokoll information - this
if (aUserID.indexOf("http") > -1 || aUserID.indexOf("//") > -1)
throw new NamingException("[MartyMailPlugin] Invalid Email: ID=" + aUserID);
return aUserID;
}
/**
* This method adds all files of a given BlobWOrkitem to the current
* MailMessage
*
*
* @param blobWorkitem
* @throws MessagingException
*/
private void attachFiles(ItemCollection blobWorkitem) throws MessagingException {
String sFilePattern = null;
while ((sFilePattern = getAttachmentName()) != null) {
logger.fine("MailPlugin attach file pattern: \"" + sFilePattern + "\"");
// get all fileNames....
List<String> fileNames = blobWorkitem.getFileNames();
// iterate over all files ....
for (String aFileName : fileNames) {
// test if aFilename matches the pattern
if (sFilePattern.isEmpty() || Pattern.matches(sFilePattern, aFileName)) {
// fetch the file content
FileInfo fileInfo = getFileFromWorkItem(aFileName, blobWorkitem);
logger.fine("MailPlugin - attach : " + aFileName);
// get Mulitpart Message
Multipart multipart = super.getMultipart();
// now attache the file
MimeBodyPart attachmentPart = new MimeBodyPart();
// construct the body part from the byte array
DataSource dataSource = new ByteArrayDataSource(fileInfo.content, fileInfo.contentType);
attachmentPart.setDataHandler(new DataHandler(dataSource));
attachmentPart.setFileName(aFileName);
attachmentPart.setDescription("");
multipart.addBodyPart(attachmentPart);
}
}
}
}
/**
* this method parses a mail body for the xml tag
* <attachments>name</attachments>. If an tag exists the method removes the
* tag and returns the value. The value is used by the method attachFile()
* to add files into the mail body.
*
*
*/
private String getAttachmentName() {
int iTagStartPos;
int iTagEndPos;
int iContentStartPos;
int iContentEndPos;
String content = null;
MimeBodyPart messagePart = null;
Multipart multipart = super.getMultipart();
try {
messagePart = (MimeBodyPart) multipart.getBodyPart(0);
content = (String) messagePart.getContent();
} catch (MessagingException e) {
logger.warning("Unable to parse tag 'attachments' !");
e.printStackTrace();
return null;
} catch (IOException e) {
logger.warning("Unable to parse tag 'attachments' !");
e.printStackTrace();
return null;
}
if (content == null || content.isEmpty())
return null;
// test if a <value> tag exists...
if ((iTagStartPos = content.toLowerCase().indexOf("<attachments")) != -1) {
iTagEndPos = content.toLowerCase().indexOf("</attachments>", iTagStartPos);
// if no end tag found return string unchanged...
if (iTagEndPos == -1)
return null;
// reset pos vars
iContentStartPos = 0;
iContentEndPos = 0;
// so we now search the beginning of the tag content
iContentEndPos = iTagEndPos;
// start pos is the last > before the iContentEndPos
String sTestString = content.substring(0, iContentEndPos);
iContentStartPos = sTestString.lastIndexOf('>') + 1;
// if no end tag found return string unchanged...
if (iContentStartPos > iContentEndPos)
return null;
iTagEndPos = iTagEndPos + "</attachments>".length();
// now we have the start and end position of a tag and also the
// start and end pos of the value
// extract Item Value
String sFilename = content.substring(iContentStartPos, iContentEndPos);
String sEMTY = "";
// now replace the tag with an empty string
content = content.substring(0, iTagStartPos) + sEMTY + content.substring(iTagEndPos);
// update mail body
try {
messagePart.setContent(content, this.getContentType());
} catch (MessagingException e) {
logger.warning("Unable to parse tag 'attachments' !");
e.printStackTrace();
}
// return the file pattern
return sFilename;
}
return null;
}
/**
* This method returns a FileInfo object for a specific FilenName. The
* FileInfo contains the Content (byte[]) and the ContentType
*
* @param uniqueid
* @param fileName
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private FileInfo getFileFromWorkItem(String fileName, ItemCollection blobWorkitem) {
// fetch $file from hashmap....
HashMap mapFiles = null;
Vector vFiles = (Vector) blobWorkitem.getItemValue("$file");
if (vFiles != null && vFiles.size() > 0) {
mapFiles = (HashMap) vFiles.elementAt(0);
Vector<Object> vectorFileInfo = new Vector<Object>();
vectorFileInfo = (Vector) mapFiles.get(fileName);
if (vectorFileInfo != null) {
String sContentType = vectorFileInfo.elementAt(0).toString();
byte[] fileContent = (byte[]) vectorFileInfo.elementAt(1);
FileInfo fileInfo = new FileInfo(fileContent, sContentType);
return fileInfo;
}
}
return null;
}
/**
* Cache implementation to hold userData objects
*
* @author rsoika
*
*/
class FileInfo {
String contentType;
byte[] content = null;
public FileInfo(byte[] acontent, String aContentType) {
this.content = acontent;
this.contentType = aContentType;
}
}
}