Wednesday, August 18, 2010

OSGi, JavaMail, and the mailcap issue

When developing some of the components for our application, I have been seeing some issues with ClassLoaders when creating them as OSGi bundles.

One main case that had me curious for a while was using JavaMail inside an OSGi bundle, and having to send a multipart mail.

The issue was - JavaMail relies on JAF (the activation framework), which houses a file (mailcap) in it's META-INF directory. So, if these (the javamail and jaf) are stored in separate bundles, then javamail cannot access the configuration file to determine which MIME types it can handle, and thus throwing an UnsupportedDataTypeException:

javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative; 

An UnsupportedDataTypeException usually occurs because JAF cannot find the DataContentHandler (DCH) for a given MIME type by reading the mailcap.

Glassfish 3 actually bundles these together in one bundle (modules/mail.jar), but I was still having the issue described above.

So I went down the path trying to figure out what in the world I could do to get past this. You can't really export resources like you do packages in the manifest, so importing into my bnd file didn't work, and even trying to manually force new mailcaps (which seemed to work elsewhere) didn't work:

MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed; x-java-fallback-entry=true");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);

This is basically just pushing through exactly what is in the mailcap file directly. But - this didn't work either. Odd...

I then went as far as create a new instance of the specific handler that is being used, and testing the support for that DataFlavor:

DataContentHandler dhmm = new com.sun.mail.handlers.multipart_mixed();
DataFlavor[] dtf = dhmm.getTransferDataFlavors();
for (DataFlavor tmpdf : dtf) {
 log.debug("   isSupported? " + tmpdf.getMimeType() + ":" + message.getDataHandler().isDataFlavorSupported(tmpdf));
}

And it shows it is supported: isSupported? multipart/mixed:true

Yet - when sending the message, same Exception. Ugh...

Finally, Sahoo (from the Glassfish team) gave me a suggestion of manipulating the ClassLoaders when I needed to to make the calls, saving the current ClassLoader so it can be put back into place.

In our bundle, we create the session and send the message in two different methods, so this had to be implemented twice, but finally - it worked!

// There is an issue in the OSGi framework preventing the MailCap
// from loading correctly. When getting the session here,
// temporarily set the ClassLoader to the loader inside the bundle
// that houses javax.mail. Reset at the end.
ClassLoader tcl = Thread.currentThread().getContextClassLoader();

try {
    // Set the ClassLoader to the javax.mail bundle loader.
    Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader());

    ...
} finally {
    // Reset the ClassLoader where it should be.
    Thread.currentThread().setContextClassLoader(tcl);
}

This is now working fine. I was a bit leery about mucking with the ClassLoaders in here - which was an issue with using JRuby code inside OSGi bundles as well, but this seems to be OK in that we are temporarily changing and immediately changing back.

3 comments:

  1. Thanks Robert, I've been fiddling around with just about every combination of activation/javamail and mailcap files - then I found your post. Absolute lifesaver.

    ReplyDelete
  2. Doh! Didn't even notice a comment here! :) Glad it helped. I have so much stuff like this that I want to get it on here so I can have a reference as well.

    ReplyDelete
  3. Thanks Much Robert, It worked for me as well!!!

    ReplyDelete