Portrait de Philippe PradosPrados.FRProgrammation, architecture & sécurité — depuis 1998

Javamail: Sérialiser dans des emails

Comment envoyer un objet Java dans un mail ? Ce document explique comment étendre le framework Javamail.

Par Philippe PRADOS - 1999
www.prados.fr

JavaMail™ est un framework permettant de manipuler une messagerie. Il est possible d'envoyer des messages et d'en recevoir. Les messages Internet utilisent un descriptif de donné permettant d'indiquer comment utiliser une partie d'un message. Un type MIME est associé au flux d'un message. Par exemple, le type text/plain permet d'indiquer que le flux suivant du message est un fichier texte.

Pour sauver des objets Java dans un message, il faut choisir un nouveau type MIME et indiquer au framework comment construire un objet à partir d'un flux de ce type. Déclarons le type MIME application/x-java_serialisation. Lors de la construction d'un message JavaMail™, il faudra l'utiliser.

body.setContent(obj,"application/x-java_serialization");

Lors de l'écriture ou de la lecture d'un message, le framework JavaMail™, épaulé par le JavaBeans™ Activation Framework, cherche une classe javax.activation.DataContentHandler associé au type. Il faut construire une classe dérivée.

/**
 * Handler for save or make object from a Mime type.
 *
 * <P>Add in mailcap file the line :
 * <PRE>
 * application/x-java_serialization; x-java-content-handler=JavaHandler
 * </PRE>
 **/
public class JavaHandler implements DataContentHandler
{
  public static final String MIMEType="application/x-java_serialization";
  private static final String userType_="Java object";
  private static final ActivationDataFlavor dataFlavor_=
    new ActivationDataFlavor(Object.class,MIMEType,userType_);
  /**
   * Returns an array of DataFlavor objects indicating the flavors the data
   * can be provided in. The array should be ordered according to
   * preference for providing the data.
   **/
  public DataFlavor[] getTransferDataFlavors()
  {
    return new DataFlavor[] { dataFlavor_ };
  }

  /**
   * Returns an object which represents the data to be transferred.
   * The class of the object returned is defined by the representation
   * class of the flavor.
   **/
  public Object getTransferData(DataFlavor dataflavor,
                                DataSource datasource)
    throws java.io.IOException
  {
    if(dataFlavor_.equals(dataflavor))
      return getContent(datasource);
    else
      return null;
  }

 /**
   * Return an object representing the data in its most preferred form.
   * Generally this will be the form described by the first DataFlavor
   * returned by the getTransferDataFlavors method.
   **/
  public Object getContent(DataSource ds)
    throws java.io.IOException
  {
    ObjectInputStream istream=null;
    try
    {
      istream=new ObjectInputStream(ds.getInputStream());
      return istream.readObject();
    }
    catch (ClassNotFoundException x)
    {
      throw new IOException("Class not found");
    }
    finally
    {
      if (istream!=null) istream.close();
    }
  }

  /**
   * Convert the object to a byte stream of the specified MIME type
   * and write it to the output stream.
   **/
  public void writeTo(Object obj,String mimeType,OutputStream os)
    throws IOException
  {
    ObjectOutputStream ostream=new ObjectOutputStream(os);
    ostream.writeObject(obj);
    ostream.flush();
  }
}

La méthode getContent() sera appelée lors de l'interprétation d'une section de type application/x-java_serialization. La méthode writeTo() sera appelée lors de l'addition d'une section java dans le message.

Il faut ensuite ajouter dans le fichier META-INF/mailcap une ligne indiquant qu'il est nécessaire d'utiliser cette classe.

text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain
multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed
message/*;; x-java-content-handler=com.sun.mail.handlers.message_rfc822
application/x-java_serialization;;
x-java-content-handler=JavaHandler

Cela permet d'enregistrer l'association entre le type MIME et la classe permettant de l'analyser. Il est nécessaire d'ajouter les lignes déjà présentes dans le fichier mailcap de l'archive mail.jar.

Ainsi, il est maintenant possible d'envoyer par messagerie des objets Java.

Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(email));
msg.setRecipient(Message.RecipientType.TO,new
InternetAddress(email));
msg.setSubject(subject);
msg.setSentDate(new Date());
// Send object
msg.setContent(aJavaObject,"application/x-java_serialization");
Transport.send(msg);

Les objets Java sont dépliés lors de la réception du message.

for (int i=1;i<=totalMessages;++i)
{
  MimeMessage msg=(MimeMessage)folder.getMessage(i);
  if (msg.getContentType().equalsIgnoreCase(
     "application/x-java_serialization"))
  {
    // Get java object from email
     JavaObject obj=(JavaObject)msg.getContent(); // Read java object
  }
}

Attention, le destinataire du message doit posséder les mêmes classes que l'émetteur. Il est possible d'ajouter dans le message les fichiers .class nécessaire à l'extraction des objets java. Un ClassLoader se chargera d'utiliser les fichiers présents dans le message pour désérialiser les objets.

Si on désire compresser l'instance java avant de la placer dans le message, il faut modifier légèrement les méthodes getContent() et writeTo().

public class JavaHandler implements DataContentHandler
{
  //...
  public Object getContent(DataSource ds)
    throws java.io.IOException
  {
    ObjectInputStream istream = null;
    try
    {
      GZIPInputStream zin=new GZIPInputStream(ds.getInputStream());
      istream = new ObjectInputStream(zin);
      return istream.readObject();
    }
    catch (ClassNotFoundException x)
    {
      throw new IOException("Class not found");
    }
    finally
    {
      if (istream != null)
        istream.close();
    }
  }

  public void writeTo(Object obj, String mimeType, OutputStream os)
   throws IOException
  {
    GZIPOutputStream zout=new GZIPOutputStream(os);
    ObjectOutputStream ostream = new ObjectOutputStream(zout);
    ostream.writeObject(obj);
    zout.finish();
    ostream.flush();
    zout.flush();
  }
}

Cet outil peut servir à rédiger un ORB (Object Request Broker) asynchrone. Une requête java est envoyée par messagerie à un serveur. Celui-ci regarde périodiquement sa messagerie, et exécute les requêtes présentes dans les messages. Il construit un nouveau message avec le code retour du traitement et l'envoie à l'expéditeur. Celui-ci attend la réponse en consultant sa propre messagerie.

Le dialogue n'étant pas synchronisé, le client ne sait pas quand le serveur lira sa messagerie. Si le processus du client peut être interrompu avant d'avoir reçu la réponse du serveur, le client peut mémoriser les requêtes en attentes pour les traiter lors d'une prochaine exécution.

Et voilà, avec quelques outils, on a mis en place une architecture d'acteur. Des serveurs sont éparpillés sur le net. Chacun possède une boîte aux lettres. Un client désire déléguer un traitement. Il construit un message e-mail avec une requête sérialisée et les archives nécessaires à l'exécution. Le message est envoyé à une ou plusieurs boîtes aux lettres. Périodiquement, les serveurs consultent leurs messageries. Ils peuvent alors découvrir la requête. Un ClassLoader est instancier pour pouvoir exécuter la requête en utilisant les classes présentes dans le message. Le traitement est exécuté. Il peut lui aussi déléguer des tâches à d'autres serveurs en envoyant des mails. Une fois le traitement terminé, le serveur retourne un message dans la boîte aux lettres du client pour lui informer du résultat. Régulièrement, le client consulte sa propre messagerie pour savoir si les acteurs qu'il a lancés ont terminé leurs travaux.