Suponemos que ya te has bajado JavaMail y JAF. Ahora que sabemos como enviar un correo sencillo con JavaMail, vamos a ver cómo leer un correo.
Leer un correo tonto de texto con JavaMail es bastante sencillo, pero si nuestro programa hace eso a piñón fijo, no podremos leer cualquier correo que nos llegue.
La estructura real de un correo, con adjuntos y posibilidad de visualizarlo en texto plano o html es algo compleja, pero debemos tratarla para poder leer cualquier correo.
En este tutorial haremos un ejemplo de lectura de correo con JavaMail, teniendo en cuenta todo esto, pero sin complicarnos demasiado la vida. Lo justo para ver cómo leer correos con JavaMail y empezar a entender la estructura de estos correos complejos.
El comprender estos correos complejos nos ayudará, más adelante, a construir y enviar con JavaMail correos que van más allá de un simple texto.
En el ejemplo, usaré una cuenta de gmail para conectarme con ella y obtener los correos del buzón inbox (carpeta de entrada). Si usas otro servidor de correo, deberás cambiar los parámetros de configuración de acuerdo a tu servidor.
La configuración de gmail para leer los correos es la siguiente
Para leer los correos necesitamos básicamente las siguientes clases de JavaMail:
Para obtener la sesión con gmail necesitamos tener una instancia de la clase Session con la configuración adecuada. Esta instancia se obtiene llamando al método Session.getDefaultInstance(). El parámetro para este método es una clase java.util.Properties de java estándar.
Como mencionamos anteriormente, Properties es una clase java que nos permite guardar datos de tipo texto asignándoles un nombre. Sería algo parecido a esto
Properties p = new Properties();
p.setProperty("un nombre", "un valor");
p.setProperty("PI", "3.1416");
Para obtener una instancia correctamente configurada de Session, en la clase Properties que le pasemos deben estar fijadas determinadas parejas nombre-valor. Tienes una lista de todas las posibles propiedades que JavaMail tiene en cuenta en la API. Vamos a ver las concretas para la conexión con pop.gmail.com
Properties prop = new Properties();
// Deshabilitamos TLS
prop.setProperty("mail.pop3.starttls.enable", "false");
// Hay que usar SSL
prop.setProperty("mail.pop3.socketFactory.class","javax.net.ssl.SSLSocketFactory"
);
prop.setProperty("mail.pop3.socketFactory.fallback", "false");
// Puerto 995 para conectarse.
prop.setProperty("mail.pop3.port","995");
prop.setProperty("mail.pop3.socketFactory.port", "995");
Con mail.pop3.starttls.enable a false deshabilitamos TLS.
Para usar SSL debemos decirle a JMail como obtener un socket SSL, para ellos fijamos mail.pop3.socketFactory.class al nombre de clase que nos proporciona java estándar para obtener sockets SSL, es decir, javax.net.ssl.SSLSocketFactory.
Lo de mail.pop3.socketFactory.fallback es opcional. Si no lo fijamos, si falla el socket SSL JavaMail intentará con un socket normal. Puesto que es inútil -gmail sólo admite SSL-, ponemos esta propiedad a false para indicarle a JavaMail que no lo intente con sockets normales.
Falta indicar el puerto 995 tanto para JavaMail como para la clase que crea sockets SSL, por ello fijamos con este valor dos propiedades distintas: mail.pop3.port y mail.pop3.socketFactory.port
Con todo esto inicializado, ya podemos obtener nuestra instancia de Session
Session sesion = Session.getInstance(prop);
sesion.setDebug(true);
Lo de setDebug(true) es sólo para obtener más información en la pantalla de lo que se está haciendo. Cuando nuestro programa funcione correctamente, podemos quitar esta línea.
Una vez que tenemos nuestro Session, tenemos que obtener la carpeta de correo entrante de nuestro servidor de correo. Para ello ponemos las siguientes líneas de código
Store store = sesion.getStore("pop3");
store.connect("pop.gmail.com","ejemplo@gmail.com","la
password ");
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
A partir de Session obtenemos el almacen de correos Store con getStore(), indicando el protocolo "pop3" con nuestro servidor de correo.
Con el Store establecemos la conexión con el servidor de correo, indicando nombre de host -pop.gmail.com-, usuario (la dirección de correo) y password. Para ello usamos el método connect().
Finalmente, al Store le pedimos la carpeta Inbox con getFolder("INBOX"). Este Folder hay que abrirlo, en principio para sólo lectura.
Antes de nada, que quede claro que según hayas puesto en gmail, puede que sólo obtengas los mensajes nuevos. Si vas siguiendo el ejemplo con código, envíate un mensaje a esa cuenta antes de ejecutar el programa, para asegurarte que tienes un mensaje nuevo sin leer. En mis pruebas no he podido bajarme correos que me he enviado desde mi mismo PC y con mi cliente de correo (Thunderbird), así que he tenido que entrar con el navagador en gmail y enviarme desde ahí correos a mí mismo.
Para obtener los mensajes, símplemente hay que llamar a getMessages() de la clase Folder. Esto nos devolverá un array de Message, con los mensajes nuevos.
Message [] mensajes = folder.getMessages();
Como primera medida sencilla y para ver algo, sacaremos por pantalla los campos From y Subject -asunto- del mensaje. El contenido del mensaje es más complejo y lo vemos en el siguiente apartado.
Los campos From y Subject se obtienen con los métodos getFrom() y getSubject().
El getFrom() nos devuelve un array de Address, así que debemos ir leyendolos de uno en uno. El código para escribir esta información de cada mensaje puede ser como el siguiente
for (int i=0;i<mensajes.length;i++)
{
System.out.println("From:"+mensajes[i].getFrom()[0].toString());
System.out.println("Subject:"+mensajes[i].getSubject());
}
Bueno, aquí comienza el lio. Un mensje puede ser muy tonto -un simple texto- o muy complicado -un texto en formato plano y html para que elijas cual quieres ver y con ficheros adjuntos variados.
Lo primero es ver de qué tipo es. La clase Message -en realidad la interface Part que esta clase implementa- tiene el método isMimeType() para poder identificar de qué tipo es el mensaje. La primera comprobación que podemos hacer es ver si es de tipo text o multipart. El primer caso nos indica que es un texto sencillo, el segundo que es un mensaje compuesto. Podríamos comprobar también si una image o cualquier otro tipo MIME que se nos ocurra, pero lo normal es que recibamos un text o un mutipart. El código sería así
for (int i=0;i<mensajes.length;i++)
{
if (mensajes[i].isMimeType("text/*")
// mensaje
de texto simple
if (mensajes[i].isMimeType("multipart/*"))
// mensaje
compuesto
}
Como ves, hemos usado comodines -el asterisco- para que nos valga cualquier tipo de texto -text/plain, text/html, etc- y cualquier tipo de multipart -multipart/alternative, multipart/parallel, etc-
Si el MIME type es text/*, podemos sacar su contenido. Obviamente deberíamos ver exactamente que tipo de texto -text/plain, text/html, text/rtf, etc- para presentarlo en condiciones, pero es mucho para este tutorial. Vamos a suponer que es text/plain y podemos sacarlo y visualizarlo tal cual.
El código es muy tonto
System.out.println(mensajes[i].getContent());
Si el mensaje es de tipo text, el método getContent() nos devolverá directamente el String con el texto del mensaje. Si es text/plain, se podrá escribir directamente en pantalla o donde queramos.
Para otros tipos, como text/html o text/rtf, necesitas algo más elaborado, como un JEditorPane, para mostrar el contenido correctamente.
Si el MIME type del mensaje es multipart, debemos ir extrayendo cada una de las partes que componen el mensaje compuesto. Con cada una de ellas debemos hacer un tratamiento similar, es decir, ver si es algún tipo simple -text, image, etc- o si a su vez vuelve a estar compuesto -multipart- y repetir el proceso hasta obtener las partes simples.
Si el mensaje es de tipo multipart, el método getContent() nos devolverá una clase MultiPart. Esta clase tiene métodos para saber cuántos cachos componen el mensaje y obtener cada uno de ellos. El código par esto podría ser así
if (mensajes[i].isMimeType("multipart/*"))
{
// Obtenemos el contenido,
que es de tipo MultiPart.
Multipart multi;
multi = (Multipart)mensajes[i].getContent();
// Extraemos cada una
de las partes.
for (int j=0;j<multi.getCount();j++)
{
Part unaParte = multi.getBodyPart(j);
// Volvemos
a analizar cada parte de la MultiParte
if (unaParte.isMimeType (....))
...
}
}
Es decir, con getContent() del mensaje obtenemos un MultiPart.
Con getCount() de este MultiPart obtenemos de cuántas partes esta compuesto el mensaje -por ejemplo, podría ser un mensaje de texto con dos imágenes adjuntas, es decir, tres partes-.
Obtenemos cada parte con getBodyPart(). Cada una de estas partes hay que volver a analizarla.
Esto, claramente, nos invita a un método recursivo para analizar cada parte, de forma que si la parte es simple, se muestra el contenido y si es compuesta, se extraen las subpartes y se vuelve a llamar al mismo método. En el código de ejemplo del final está hecho así.
La idea ya debería estar clara. Vamos a ver como tratar algo que no sea text ni multipart. Como ejemplo, una imagen adjunta.
Para comprobar si una parte es una imagen, nuevamente usamos el método isMimeType().
if (unaParte.isMimeType("image/*"))
{
...
}
Si no es un text ni un multipart, lo más probable es que getContent() nos devuelva un InputStream del que podemos leer los bytes que componen esa parte. En nuestro caso, los bytes de la imagen. En cualquier caso, tenemos el método getInputStream() que seguro que nos lo devuelve como un InputStream.
A partir de este InputStream es fácil leer los bytes y guardarlos, por ejemplo, en un fichero de imagen con un FileOutputStream. Tal cual leemos bytes del InputStream, los vamos escribiendo en el FileOutputStream. El método getFileName(), para el caso de imagen, posiblemente nos devuelva el nombre del fichero que tenía la imagen cuando nos la enviaron, con lo que ya tenemos que nombre darle al fichero. Aquí tienes un trozo de código para guardar la imagen en un fichero (ojo, lo mete en el raíz de la unidad D:\, cámbialo si no te gusta).
if (unaParte.isMimeType("image/*"))
{
FileOutputStream fichero = new FileOutputStream("d:/"+unaParte.getFileName());
InputStream imagen = bodyPart.getInputStream();
byte [] bytes = new byte[1000];
int leidos=0;
while ((leidos=imagen.read(bytes))>0)
{
fichero.write(bytes,0,leidos);
}
}
De todas formas, a mí me gusta más ver la foto, así que aquí va el código para mostrarla en un JFrame.
if (unaParte.isMimeType("image/*"))
{
JFrame v = new JFrame();
ImageIcon icono = new ImageIcon(ImageIO.read(unaParte.getInputStream()));
JLabel l = new JLabel(icono);
v.getContentPane().add(l);
v.pack();
v.setVisible(true);
}
Como puedes ver, se comprueba si es de tipo image/*. En caso afirmativo, la clase ImageIO de java nos ayuda a construir una Image a partir de un InputStream y esta podemos meterla en un ImageIcon, que se puede meter a su vez dentro de un JLabel que podemos pintar en una ventana JFrame ... ¡¡uff!!
En RecibirMail.java tienes un programa completo capaz de leer los correos de gmail. Escribe en pantalla las partes de texto y salva en fichero en d:\ las imagenes, además de visualizarlas en un JFrame.
Ahora que sabemos más o menos de qué se compone un mensaje, vamos a tratar de enviar un correo con una foto adjunta usando JavaMail.