Tuesday, 1 September 2009

Process E-Mail with Oracle BPEL PM 10.1.2

This week I was asked to start with a new service that should process e-mail. It has to receive e-mail and split it up in different parts. It should save the content to a file-store and also put attachment to the same file store and keep track of the metadata of the mail, eg. from and to addresses, etc.

The BPEL version we use is still 10.1.2. I googled around and to find some examples. I found a pretty good example from Lucas Jellema, who based his one on an article byMatt Wright.

To initiate BPEL processes on incoming email is done through an Activation Agent. This is described in chapter 17. 3.6 of the documentation.

Lucas used Apache James as a demo email server, described here. It is really simple to set it up. Installation is nothing more than unzip the download, and start it using run.bat or run.sh from the <james_home>/bin folder. Then you have to kill it once, since the first run is not really a startup. At first startup it apparently unpacks some libraries, etc. After the second startup you can telnet to it using telnet localhost 4555. Login as root with password root and then you can add a user using adduser akkerm akkerm.

I had to process emails with attachements too. Soo I used Lucas' example java and extended it a little to be able to add an attachement also. Here is my code:


package sendmail;

import java.io.File;

import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;


public class EmailSender {
public EmailSender() {
}

public static void sendEmail(String to, String subject, String from,
String content, String attachment) throws Exception {
Properties props = new Properties();
props.setProperty("mail.transport.protocol", "smtp");
props.setProperty("mail.host", "localhost");
props.setProperty("mail.user", "akkerm");
props.setProperty("mail.password", "");

Session mailSession = Session.getDefaultInstance(props, null);
Transport transport = mailSession.getTransport();

MimeMessage message = new MimeMessage(mailSession);
message.addFrom(new Address[] { new InternetAddress("akkerm@localhost",
from) }); // the reply to email address and a logical descriptor of the sender of the email!

message.setSubject(subject);


message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Add content
MimeBodyPart mbp1 = new MimeBodyPart();
mbp1.setText(content);

MimeBodyPart mbp2 = new MimeBodyPart();
// attach the file to the message
FileDataSource fds = new FileDataSource(attachment);
mbp2.setDataHandler(new DataHandler(fds));
mbp2.setFileName(fds.getName());


// create the Multipart and its parts to it
Multipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
mp.addBodyPart(mbp2);

// add the Multipart to the message
message.setContent(mp);


transport.connect();
transport.sendMessage(message,
message.getRecipients(Message.RecipientType.TO));
transport.close();
}

public static void main(String[] args) throws Exception {
String content = "Some test content from akkerm.";
EmailSender.sendEmail("akkerm@localhost", "An interesting message",
"THE APP", content, "c:/temp/in/excelfile.xls");
}
}


To be able to fetch the attachment of the mail I did the following. First I added a variable, called Multipart:
  <variable name="MultiPart" element="ns1:multiPart">

Where ns1: is xmlns:ns1="http://services.oracle.com/bpel/mail", the name space of the added XMLSchema. When you receive the message, you get a content-element. But to be able to handle it as a multiple parts you have to copy it to a variable based on the multipart element from the XSD.
Then you can count the number of parts using the expression:
count(bpws:getVariableData('inputVariable','payload',
'/ns1:mailMessage/ns1:content/ns1:multiPart/ns1:bodyPart'))

You can copy that in a variable that's called PartsCount of type xsd:int for example. To be able to loop over the parts you need an index also:

<variable name="PartsCount" type="xsd:int"/>
<variable name="PartIndex" type="xsd:int"/>

Then you can use a while loop with loop condition like:
bpws:getVariableData('PartIndex')<=bpws:getVariableData('PartsCount')

Within the loop you can copy the body part to a variable content that is also based on the multipart-element:
   <variable name="Content" element="ns1:multiPart"/> 

using the expression:
bpws:getVariableData('MultiPart','/ns1:multiPart/ns1:content/ns1:multiPart/ns1:bodyPart[bpws:getVariableData("PartIndex")]'

You need to copy that to the /ns1:multiPart/ns1:bodyPart part of the Content variable.

The content of a binary attachment is base64-encoded. If you want to save that to a file there are several options. Some of them are listed here. The most simple way to do that is to use a file write adapter. Configure it using the adapter wizard, and choose an "Opaque" xsd.
To be able to determine with which filename the file is written, you can use an adapater-header variable. Create a variable with the message type based on the header-type of the adapter-wsdl:
<variable name="FileWriteHeader" messageType="ns5:OutboundHeader_msg"/>

In the Invoke of the FileWrite partnerlink, you can add it as a header variable to use.



I already wrote about mcf-properties and headervariables here. I found that in SoaSuite 10.1.3 you can direct the fileadapter also to write to another directory then specified in the adapter wizard. But aparently this does not work in 10.1.2. You can override the directory via the header variable, by adding the property to the wsdl. But it keeps writing to the directory specified at designtime. The filename however is used from the header variable, so you that you can change.

No comments :