How to read an email with the POI library?
CLEVR is an Expert Mendix Partner. Frequently we post technical and functional Mendix Insights on topics we think are relevant for the Mendix developer community. In this Mendix Insights blog we take a deep dive in how to read an email with the POI library. Contributed by one of our Mendix Conultants. Enjoy reading!
My name is Giel van Altena and working as a Mendix developer at CLEVR. I would like to share my knowledge how to program Java within your Mendix Apps. This Mendix Insights Blog is for Mendix developers who like Java programming and want to know how to use the POI library for reading emails. With the POI library you can get the sender, body and attachments from a msg file. I will explain how to add java libraries to your project, set-up your project, and write a java action in Eclipse.
Table of contents:
- Add jar files to your project
- Set-up your project
- Write the java acton
How to add jar files to your project
Apache POI is a java library for reading and writing files in the Microsoft format. Inside this java library is a class named MAPIMessage which you will need for reading emails from Outlook. Find and download the POI java library from the Apache website, for download see link. The binary distribution contains the zipped jar files. When you have downloaded the package with the jar files, you can pick the jar files that you need, and place them in the userlib folder of your Mendix project. Jar files in this location are automatically added to the classpath of your Eclipse project, which means you can import them in your java.
All jar files in the package
- poi-3.16.jar: Take This
- poi-examples-3.16.jar
- poi-excelant-3.16.jar
- poi-ooxml-3.16.jar
- poi-ooxml-schemas-3.16.jar
- poi-scratchpad-3.16.jar: Take this. This library contains the MAPIMessage class.
- commons-codec-1.10.jar
- commons-collections4-4.1.jar
- commons-logging-1.2.jar: You probably need this. My userlib folder already contained commons-logging-1.1.jar, so I didn’t need commons-logging-1.2.jar.
- junit-4.12.jar
- log4j-1.2.17.jar
- curvesapi-1.04.jar
- xmlbeans-2.6.0.jar
You may experience jar conflicts, when you try to run the application. This happens when a project contains two java classes with the same name. You probably have downloaded the Excel Importer from the appstore which adds another version of the POI jar file to your userlib. You can resolve this problem by removing the jar file with the older version. My project did run after I removed ‘poi-3.10-FINAL-20140208.jar’ (which is the older jar file) from my userlib folder. Always test your project before committing any changes. Please leave a comment if you know a better way of managing jar conflicts.
How to setup your project
1: You start by setting up your domain model. I have setted up my domain model as follows: An email entity with attributes for storing the email file and its content, such as the sender, recipients and html body. A document entity for storing any attachments. Both entities are subclasses of FileDocument.
2: Create a java action with three input parameters: An email object, a list with document objects and a string variable. The email file is passed to the java action as email object. The list of documents is needed to collect the email’s attachments, because for each attachment a document object is created and added to the list. The string variable is needed to tell the java action the full name of the document entity, which is in my case ‘MyFirstModule.Document’. My java action looks like this:
3: Create a page for uploading the email. You can choose the standard filemanager from Mendix or the dropzone widget from the Appstore for uploading the file.
4: Create a microflow that opens the first page. The microflow must create an empty email object and open the page with the file uploader.
5: Create a second page for showing the email after it has been uploaded and processed by the java action. Show the email’s attributes and attachments on the page.
6: Create a second microflow. This microflow takes the email object, creates a list of documents, creates a string variable (‘MyFirstModule.Document’) and calls the java action. The java action will read the email, copy its attributes to the email object, and add the attachments to the list of documents. After the java action is done, the microflow sets the association between the attachments and the email, closes the first page and opens the second page. It is possible to set the association between the attachments and the email inside the java action, but I like to do as much as possible in the microflow.
How to write the java action in Eclipse?
More information about using Eclipse to write java actions can be found here. Open Eclipse and go to the java file belonging to your java action, which in my case can be found in ‘myfirstmodule.actions’. In the java file, you can write your java code between the markers: // BEGIN USER CODE and // END USER CODE
1: Write some validation lines: Write the following lines to validate if the file has contents and if the file extension matches ‘.msg’. The file extension is checked with a regular expression. The function matches() returns false when the file extension doesn’t end with ‘.msg’. When one of the validations fails, the control has to return to the microflow that called the java action. I prefer to throw exceptions when something is wrong and handle these exceptions in my microflow, because the rest of the java throws exceptions, so you need to handle exceptions in the microflow anyway.
2: Extract the attributes: We are going to use functions from the MAPIMessage class (POI) to get the attributes and attachments from the email and functions from the Core class to store them. I am going to use Core.change() for storing the email’s attributes in the email object, Core.instantiate() for creating a FileDocument and Core.storeFileDocumentContent() for storing the email’s file content in the FileDocument. You actually can’t use Core.change() to store any dates, because Core.change() only accepts string values, so we are going to use the email’s proxy object and its function setMessageDate() to store the email’s date.
Write the following lines to copy the attributes from the email file to the email object. Create an inputstream to read the email file (line 72). Instantiate a MAPIMessage object to be able to call its functions to get the data from the email (line 73). Create a hashmap variable (line 77), take the email’s attributes and store them in local variables (lines 78-82). You need a custom java function to get the email address of the person who send the email (line 83) and this function is written below. Then copy the email’s date with setMessageDate() from the email’s proxy object (86), put the local variables in the hashmap (lines 89-103) and save the email’s attributes by using Core.change(). This function will throw an exception if an attribute name is spelled incorrectly, so keep in mind the spelling. Surround this whole block with a try/catch block.
3: Extract the sender’s email address: MAPIMessage has a lot functions, see the list of functions here, but no function to get the email address of the person who send the email, so we need to write a java function. Start with the java function between the second markers: // BEGIN EXTRA CODE and // END EXTRA CODE
The email is made of a number of chunks. One of these chunks contains the line ‘From: “Last name, First name (Company name)” <firstname.lastname@company.com>’. We can take the email address from this line by using a regular expression. My regular expression “(From:)\s?”?(.*)”?\s?<(.*)>$” captures the email address as a group, by placing round brackets around it, so we can extract it as a substring. This website here provides a useful tool for testing your regular expression.
Write down your regular expression and use double slashes before any special character (line 150). Next, take the lines from the email’s header using getHeaders() and store them in an array (line 151). Iterate through the array and see if any line matches the regular expression (lines 152-158). If it matches, take the third group from the matcher and return it (line 159).
4: Extract the attachments: An email file with attachments contains an attachment chunk for every attachment file. Continue programming in your try/catch block between the first markers. Take the attachment chunks from the file using getAttachmentFiles() and store them in an AttachmentChunks array (line 107). Iterate through the array and create a FileDocument object for each attachment using Core.instantiate(). You need to tell Core.instantite() the exact entity name ‘MyFirstModule.Document’. Create an inputstream with new ByteArrayInputSteam() for reading the file (line 114) and use Core.storeFileDocumentContent() for writing the file in the FileDocument (line 115). Then take the attachment name from the chunk and give the FileDocument the correct name using Core.change() (lines 118-121). You should end your java with the catch block.
Good job! I hope I have explained everything clearly and you have successfully written your java action. Please do not hesitate to leave a comment if you need any further information or have an enhancement on the functionality or code!