Use JavaMail 1.5 with XPages
I have an XPage application that needs to read emails from GMail over IMAP (and SSL) and store them in an nsf database.
For this I need JavaMail 1.5.
After reading some posts, I came to the conclusion that I need to create my own OSGI plugin. Following John Dalsgaard's blog http://www.dalsgaard-data.eu/blog/wrap-an-existing-jar-file-into-a-plug-in/ I was able to wrap the JavaMail 1.5 jar file in OSGI-Plugin. The plugin exports all JavaMail packages so I can use them in my XPage application.
There is some Java code in my XPage that is trying to establish a connection to GMail. But the connection was always disconnected, so I enabled the debug option for javamail. Debugging showed that my Java code is still using javamail 1.3 (the one provided by the domino server).
So I moved my Java code to OSGI-Plugin and exported the package, so I can still use it in my XPage. Enabling debugging for javamail showed correct version 1.5. But now I am getting an exception javax.mail.NoSuchProviderException
no matter if I use imap
or imaps
how protocol.
What am I missing? Why can't I use javamail 1.5 jar file in osgi plugin?
source to share
The javax.mail code is not really OSGi friendly. It uses the Thread ClassLoader context to find implementations.
You can cheat it like this:
- install both javax.mail and com.sun.mail.imap into OSGi container. Version 1.5.2 contains OSGi manifest headers, so it should work
- When you call the javax.mail api function that loads the Provider, set the thread context classloader to a classloader that most likely knows the imap classes.
eg:.
Thread thread = Thread.currentThread();
ClassLoader previousCl = thread.getContextClassLoader();
thread.setContextClassLoader(IMAPStore.class.getClassLoader());
try {
session.getStore("imaps");
} finally {
thread.setContextClassLoader(previousCl);
}
Note that I have not tested this, but the solution should look similar. You may need to add a few javax.mail commands to the try block (get session for example).
Less ugly solution [/ strong>
Less ugly solution - if you skip using the javax.mail API and use the imap classes directly.
eg:.
Session session = Session.getDefaultInstance(...);
Store imapStore = new IMAPStore(session, url);
Very often, when SPI or a similar solution is used in Java EE, the same functionality can be achieved by skipping the factory mechanism (which uses a streaming thread loader and other tricks) and creating the implementation classes directly.
Update
The command states that there is an exception:
com.sun.mail.handlers.multipart_mixed incompatible with javax.activation.DataContentHandler
The reason is that the javax.activation. * available twice in the OSGi container. It comes from the JDK and it also comes from one of the packages. The problem is similar here and the reason also comes from duplicate packages.
However, the real reason is to use the Thread Context ClassLoader again. If you look in the source where the exception is thrown, you can see that the thread context classloader is used to load the class. This is a problem as I think TCC is the system classloader and javax.mail is connected to the javax.activation package.
I can imagine the following parameters right now:
- Remove the javax.activation package. This may solve the problem, as in this case each kit will be connected to the system package. However, you can still get ClassNotFoundExceptions later ... Dealing with obscene classloader practice :).
- Remove javax.activation from system packages. In this case, everyone will connect to the kit. However, the same problem can happen that the javax.activation (or TCC) package cannot see the class that needs to be loaded.
- Find a point in the stacktrace where you can change the TCC and install a ClassLoader that sees all the required classes. This is again a common trick for monogolithic technologies (as in the original example, but now to call this function).
- Find a technology that has features that are more OSGi friendly. If you can find it, post it here :).
source to share
I found this jar in maven center:
http://search.maven.org/#artifactdetails|javax.mail|javax.mail-api|1.5.2|jar
It seems to be a valid OSGi package. Maybe this helps?
source to share
Do you really need Java 1.5.2 mail? I do not think so. You need Google's advanced IMAP classes and the code I wrote in January . In a nutshell:
/** ========================================================================= *
* Copyright (C) 2014 Stephan H. Wissel *
* *
* @author Stephan H. Wissel <stephan@wissel.net> *
* *
* @version 0.1 *
* ========================================================================== *
* *
* Licensed 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 com.notessensei.gimap;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.FetchProfile;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import lotus.domino.Database;
import lotus.domino.Document;
import lotus.domino.Item;
import lotus.domino.NotesException;
import lotus.domino.NotesFactory;
import lotus.domino.NotesThread;
import com.sun.mail.gimap.GmailFolder;
import com.sun.mail.gimap.GmailMessage;
import com.sun.mail.gimap.GmailSSLStore;
public class GMail2Notes {
public static int MAX_TEST_COUNT = 50;
public static void main(String[] args) throws NotesException, MessagingException {
if (args.length < 3) {
System.out.println("GMail2Notes username@gmail.com password nsfFileName");
System.exit(1);
}
System.out.println("Starting for " + args[0] + " to " + args[2]);
NotesThread.sinitThread();
lotus.domino.Session s = NotesFactory.createSession();
Database db = s.getDatabase("", args[2]);
GMail2Notes g2n = new GMail2Notes(args[0], args[1]);
g2n.addExcludedLabel("Spam");
g2n.addExcludedLabel("Trash");
int resultCount = g2n.importMessages(s, db, "All Mail");
System.out.print("Messages imported:");
System.out.println(resultCount);
NotesThread.stermThread();
System.out.println("Done");
}
private final String userName;
private final String passWord;
private GmailSSLStore store = null;
// set it to false for complete import
private boolean isTestMode = true;
// Labels we don not want to import
private final List<String> excludedLabels = new ArrayList<String>();
public GMail2Notes(String usr, String pwd) {
this.userName = usr;
this.passWord = pwd;
}
/**
* Add a folder name to the list we are not interested in
*
* @param label
*/
public void addExcludedLabel(String label) {
this.excludedLabels.add(label);
}
public List<String> getSystemFolderNames() throws MessagingException {
List<String> result = new ArrayList<String>();
GmailSSLStore store = this.getStore();
Folder[] folders = store.getFolder("[Gmail]").list();
for (Folder f : folders) {
result.add(f.getName());
}
return result;
}
public int importMessages(lotus.domino.Session s, Database db, String systemFolderName) throws MessagingException {
int result = 0;
// The object to move message to
Mime2Doc md = new Mime2Doc();
// Getting gMail ready
GmailFolder f = this.getSystemFolder(systemFolderName);
f.open(Folder.READ_ONLY);
Message[] messages = f.getMessages();
FetchProfile profile = new FetchProfile();
profile.add(GmailFolder.FetchProfileItem.CONTENT_INFO);
profile.add(GmailFolder.FetchProfileItem.LABELS);
profile.add(GmailFolder.FetchProfileItem.MSGID);
profile.add(GmailFolder.FetchProfileItem.SIZE);
f.fetch(messages, profile);
int count = 0;
for (Message message : messages) {
result += this.importOneMessage(s, db, md, message);
// For testing we don't run through all of them
count++;
if (this.isTestMode && count >= MAX_TEST_COUNT) {
break;
}
}
if (f.isOpen()) {
f.close(false);
}
this.cleanup();
System.out.println("Done");
return result;
}
/**
* We need a delivered date so documents don't show up in SEND
*
* @param doc
* @param sender
*/
private void adjustDeliveredDate(Document doc, Address sender) {
String senderName = sender.toString();
if (!senderName.equalsIgnoreCase((this.userName))) {
try {
Item PostedDate = doc.getFirstItem("PostedDate");
doc.copyItem(PostedDate, "DeliveredDate");
doc.save();
PostedDate.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
}
}
/**
* Strips leading \ from messages
*
* @param rawLabel
* @return label Stripped from Backslash
*/
private String cleanLabel(String rawLabel) {
return (rawLabel.startsWith("\\") ? rawLabel.substring(1) : rawLabel).trim();
}
private void cleanup() {
if (this.store != null && this.store.isConnected()) {
try {
this.store.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}
this.store = null;
}
private GmailSSLStore getStore() throws MessagingException {
if (this.store != null) {
return this.store;
}
Properties props = System.getProperties();
props.setProperty("mail.imaps.connectiontimeout", "5000");
props.setProperty("mail.imaps.host", "imap.gmail.com");
props.setProperty("mail.imaps.partialfetch", "false");
props.setProperty("mail.imaps.port", "993");
props.setProperty("mail.imaps.timeout", "5000");
props.setProperty("mail.mime.base64.ignoreerrors", "true");
props.setProperty("mail.store.protocol", "gimaps");
Session session = Session.getDefaultInstance(props, null);
this.store = (GmailSSLStore) session.getStore("gimaps");
this.store.connect(this.userName, this.passWord);
// Ready for connection
return this.store;
}
/**
* can be: All Mail, Drafts, Important, Sent Mail, Spam, Starred, Trash
**/
private GmailFolder getSystemFolder(String folderName) throws MessagingException {
GmailSSLStore store = this.getStore();
Folder folder = store.getFolder("[Gmail]").getFolder(folderName);
return (GmailFolder) folder;
}
private int importOneMessage(lotus.domino.Session s, Database db, Mime2Doc md, Message message) {
int result = 0;
try {
GmailMessage g = (GmailMessage) message;
Address sender = g.getSender();
String[] labels = g.getLabels();
System.out.print(g.getMessageID());
if (labels != null) {
System.out.print(", ");
System.out.print(Arrays.toString(labels));
}
if (this.processThisMessage(labels)) {
result = 1;
Document doc = db.createDocument();
InputStream in = g.getMimeStream();
md.importMail(s, in, doc);
this.moveDocToFolders(doc, labels);
this.adjustDeliveredDate(doc, sender);
System.out.println(" - processed");
} else {
System.out.println(" - skipped");
}
} catch (Exception e) {
// TODO: record the message for follow-up
e.printStackTrace();
}
return result;
}
/**
* Moves doc to folders as needed
*
* @param doc
* @param labels
*/
private void moveDocToFolders(Document doc, String[] labels) {
if (labels != null) {
for (String label : labels) {
this.movetoMatchingFolder(doc, label);
}
}
}
private void movetoMatchingFolder(Document doc, String folderCandidate) {
// TODO handle the SENT folder, Draft folder
if (folderCandidate != null && !folderCandidate.trim().equals("")) {
try {
String realFolder = this.cleanLabel(folderCandidate);
if (realFolder.equalsIgnoreCase("inbox")) {
doc.putInFolder("($Inbox)");
} else if (realFolder.equalsIgnoreCase("drafts")) {
// TODO handle Drafts
} else if (realFolder.equalsIgnoreCase("sent")) {
// TODO handle SENT
} else {
doc.putInFolder(realFolder, true);
}
} catch (NotesException e) {
e.printStackTrace();
}
}
}
private boolean processThisMessage(String[] messageLabels) {
boolean result = true;
// If the message has no labels we do process it
if (messageLabels != null && messageLabels.length < 1) {
for (String rawLabel : messageLabels) {
String cleanLabel = this.cleanLabel(rawLabel);
if (this.excludedLabels.contains(cleanLabel)) {
result = false;
break;
}
}
}
return result;
}
}
Let us know if this worked for you
source to share