miércoles, 30 de marzo de 2011

Utilización de las librerias Java Mail y envío de SMS

1.- Introducción

En esta serie de artículos vamos a empezar a usar JavaMail. Aunque en la página de SUN ponga que es para J2EE, la verdad es que esta librería nos permite enviar y recibir correos desde cualquier aplicación java.

Veremos en primer lugar qué tenemos qué descargar, puesto que JavaMail no viene por defecto con Java y necesita, además, otra librería adicional, JAF (o JavaBeans Activation Framework).

Luego, de la forma más directa posible y un mínimo de explicaciones para entender lo que estamos haciendo, haremos tres ejemplos con JavaMail:

  • Leer un correo

  • Enviar un correo

  • Enviar un correo con datos adjuntos.

Para nuestros ejemplos usaré la cuenta de gmail, así que la configuración que veas es para dicha cuenta. Si tienes otro servidor de correo, tendrás que mirar cómo es la configuración con tu servidor y cambiar los parámetros adecuados en la configuración de JavaMail.



2.- ¿Qué necesitamos descargarnos para usar JavaMail?

Para usar JavaMail necesitamos descargarnos la librería de JavaMail de la página de SUN. Puedes bajártela aquí:

http://www.oracle.com/technetwork/java/index-138643.html

Acepta las condiciones, bájate el zip, desempaquétalo y añade los jar que vienen a tu proyecto.

JavaMail utiliza otra librería, JAF, que también debes descargarte:

http://www.oracle.com/technetwork/java/javase/tech/index-jsp-138795.html

El activation.jar que viene ahí también debes añadirlo a tu proyecto, igual que los anteriores.

Ya tienes todo lo que necesitas para usar JavaMail.



3.- Ejemplos con JavaMail

Vamos a ver tres ejemplos sencillos con JavaMail, de más sencillo a más complejo:



4.- Envío de un correo de texto

Para el ejemplo, usaremos una cuenta de gmail como servidor de correo.

La configuración de esta cuenta (de cualquier cuenta de gmail) para el envío de correos es

  • Requiere usuario y password. El usuario es la cuenta de correo, pondremos por ejemplo, ejemplo@gmail.com

  • Protocolo smtp

  • TLS si está disponible

  • Puerto 587, que no es el de defecto de smtp.

Clases y configuración de JavaMail para el envío de mensajes

Para este ejemplo sencillo, hay tres clases de JavaMail implicadas:

  • Session: De alguna forma representa la conexión con el servidor gmail de correo. Hay que obtenerla pasándole los parámetros de configuración para el servidor de correo -gmail en nuestro caso-.

  • Transport: Clase para el envío de mensajes. Se obtiene llamando al método getTransport() de la clase Session.

  • MimeMessage: El mensaje.

Creación y configuración de la clase Session de JavaMail

La clase Session representa nuestra conexión con el servidor de correo -gmail en nuestro caso-. Se obtiene con el método Session.getDefaultInstance() pasándole un parámetro:

  • Un Properties con las propiedades de la conexión.

Properties es una clase de java estándar java.util.Properties. Esta clase admite que guardemos en ella parejas "clave-valor", es decir, guardamos datos -de tipo String- dándoles un nombre a cada uno para luego poder recuperarlos. Esto se hace por medio del método setProperty(), de esta manera

Properties p = new Properties();
p.setProperty("un nombre", "un valor");
p.setProperty("PI", "3.1416");

Las Properties que necesita Session son específicas, es decir, tienen que tener unos nombres concretos y unos posibles valores concretos. En la API una lista de algunas de ellas y también en la API tienes una lista más completa.

Veremos aquí sólo las estrictamente necesarias para conectarnos con gmail y poder enviar mensajes. El código sería este

Properties props = new Properties();

// Nombre del host de correo, es smtp.gmail.com
props.setProperty("mail.smtp.host", "smtp.gmail.com");

// TLS si está disponible
props.setProperty("mail.smtp.starttls.enable", "true");

// Puerto de gmail para envio de correos
props.setProperty("mail.smtp.port","587");

// Nombre del usuario
props.setProperty("mail.smtp.user", "ejemplo@gmail.com");

// Si requiere o no usuario y password para conectarse.
props.setProperty("mail.smtp.auth", "true");

Con esto estamos en disposición de obtener nuestra instancia de Session.

Session session = Session.getDefaultInstance(props);
session.setDebug(true);

Hemos puesto setDebug(true) para obtener más información por pantalla de lo que está sucediendo. Una vez que el programa nos funcione, podemos quitar esa línea sin problemas.

Ya tenemos nuestro objeto Session.

Construir el mensaje para enviar con JavaMail

Vamos ahora a construir un mensaje simple de texto. Para ellos instanciamos la clase MimeMessage y le ponemos varios datos.

En el constructor debemos pasarle el objeto Session que obtuvimos anteriormente

MimeMessage message = new MimeMessage(session);

Ponemos luego el FROM y el TO del mensaje, es decir, quién lo envía y a quién va dirigido. Para meter estos datos usaremos los métodos setFrom() y addRecipient() de MimeMesage. Los parámetros que admiten estos métodos son instancias de javax.mail.internet.InternetAddress. En el constructor de esta clase InternetAddress símplemente ponemos la dirección de correo -ver código más abajo-.

Para los destinatarios usaremos el método addRecipient() de MimeMessage. Esta método admite dos parámetros, una constante para indicar el tipo de destinatario y otro que es el InternetAdress mencionado. El tipo de destinatario puede ser

  • Message.RecipientType.TO Destinatario principal del mensaje

  • Message.RecipientType.CC Destinatario al que se envía copia del mensaje

  • Message.RecipientType.BCC Destinatario al que se envía copia, pero sin que los demás destinatarios puedan verlo.

En nuestro caso, sólo pondremos el TO y la dirección de correo.

// Quien envia el correo
message.setFrom(new InternetAddress("ejemplo@gmail.com"));

// A quien va dirigido
message.addRecipient(Message.RecipientType.TO, new InternetAddress("destinatario@dominio.com"));

Ahora sólo queda llenar el asunto -subject que dirían los ingleses- del mensaje y el texto. Como tenemos métodos para ello, setSubject() y setText(), no hay ningún problema.

message.setSubject("Hola");
message.setText("Mensajito con Java Mail" +
"de los buenos." +
"poque si");

Ya tenemos construido el mensaje.

Un pequeño detalle nada más. Este mensaje es de texto y el formato que se está usando es texto "plano", es decir, texto normalito, tal cual, sin más. Hay muchas formas de enviar texto. Se puede enviar, por ejemplo, en formato html, con lo que podríamos poner negritas, cursivas, etc.

Para enviar texto con otros formatos, debemos usar el mismo método setText(), pero con más parámetros. En concreto, podemos usar la versión de setText() que admite tres parámetros

  • El texto, en formato html -o el que decidamos-

  • El juego de caracteres a utilizar. Por ejemplo, puede ser "ISO-8859-1", que corresponde a nuestro juego de caracteres.

  • El formato a utilizar. Para un html sería "html". No se puede poner cualquier cosa, hay que utilizar subtipos estandar de los tipos MIME (Multipurpose Internet Mail Extension). Al utilizar setText(), la primera parte de este tipo se presupone "text". El subtipo "hmtl" es el que ponemos aquí.

El siguiente código crearía el mismo mensaje, pero con texto html y algún adorno de negrita y cursiva

message.setText(
"Mensajito con Java Mail
" + "de los buenos." + "poque si",
"ISO-8859-1",
"html");

Enviamos el mensaje

Para enviar el mensaje usamos la clase Transport, que se obtiene de Session. El método getTransport() requiere un parámetro String con el nombre del protocolo a usar. Como el de gmail es smtp, pues ponemos smtp.

Transport t = session.getTransport("smtp");

Ahora debemos establecer la conexión, dando el nombre de usuario y password.

t.connect("ejemplo@gmail.com","la password");

y ahora simplemente enviamos el mensaje

t.sendMessage(message,message.getAllRecipients());

El método de enviar mensaje es sendMessage() y NO sólo send(). El método send() es estático y no tiene en cuenta la conexión, ni el usuario ni la password. Es una forma rápida de envio de mensaje para un servidor smtp que no requiera autentificación. Como no es el caso, debemos usar sendMessage().

El método sendMessage(), además del mensaje, necesita la lista de receptores del mensaje, es decir, el array de direcciones a los que va el mensaje. La forma rápida de obtener este array es llamar al método getAllRecipients() de la clase MimeMessage, justo como lo hemos hecho en el código.

Con esto se envía el mensaje. Ahora solo queda cerrar la conexión

t.close();

Ejemplo completo:

package tucuenta.ejemplos.mail;

import java.util.Properties;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class EnviarMail
{
/**
* main de prueba
* @param args Se ignoran.
*/
public static void main(String[] args)
{
try
{
// Propiedades de la conexión
Properties props = new Properties();
props.setProperty("mail.smtp.host", "smtp.gmail.com");
props.setProperty("mail.smtp.starttls.enable", "true");
props.setProperty("mail.smtp.port", "587");
props.setProperty("mail.smtp.user", "tucuenta@gmail.com");
props.setProperty("mail.smtp.auth", "true");

// Preparamos la sesion
Session session = Session.getDefaultInstance(props);

// Construimos el mensaje
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("yo@yo.com"));
message.addRecipient(
Message.RecipientType.TO,
new InternetAddress("tucuenta@gmail.com"));
message.setSubject("Hola");
message.setText(
"Mensajito con Java Mail" + "de los buenos.");

// Lo enviamos.
Transport t = session.getTransport("smtp");
t.connect("tucuenta@gmail.com", "la clave");
t.sendMessage(message, message.getAllRecipients());

// Cierre.
t.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}





5.- Recepcion de un correo con JavaMail

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.

Servidor de gmail

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

  • Tener POP habilitado. Entra en tu cuenta de gmail y arriba a la derecha pincha "configuración". En la nueva pantalla pincha "Reenvio y correo POP". Asegúrate de que pone "POP habilitado" o marca la opción "Habilitar POP para todos los mensajes que se reciban a partir de ahora". Si no lo haces, no podrás acceder desde JavaMail a tus mensajes.

  • Servidor de correo al que conectarse pop.gmail.com

  • Puerto de conexión 995

  • Conexión SSL

  • Deshabilitar TLS

  • Usuario -la dirección de correo, usaré ejemplo@gmail.com- y password válidos de gmail.

Clases implicadas en la lectura de un correo

Para leer los correos necesitamos básicamente las siguientes clases de JavaMail:

  • Session, que representa de alguna forma la conexión con gmail.

  • Store y Folder, que representan el almacén del servidor con los ocrreos y la carpeta dentro de ese almacén donde el servidor guarda los correos que queremos leer.

  • Message, los mensajes que leeremos.

Establecer la Session

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.

Obtener el Store y el Folder de Inbox

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.

Obtener los mensajes

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

Ver el contenido del mensaje

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// 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-

Sacar el contenido de texto

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.

Obtener las partes de un multiparte.

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
// 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í.

Obtener una imagen adjunta.

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.

Ejemplo:


package tucuenta.ejemplos.mail;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import javax.imageio.ImageIO;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;


public class RecibirMail
{
/**
* main de la clase.
* @param args Se ignoran
*/
public static void main(String[] args)
{
// Se obtiene la Session
Properties prop = new Properties();
prop.setProperty("mail.pop3.starttls.enable", "false");
prop.setProperty(
"mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
prop.setProperty("mail.pop3.socketFactory.fallback", "false");
prop.setProperty("mail.pop3.port", "995");
prop.setProperty("mail.pop3.socketFactory.port", "995");
Session sesion = Session.getInstance(prop);
// sesion.setDebug(true);

try
{
// Se obtiene el Store y el Folder, para poder leer el
// correo.
Store store = sesion.getStore("pop3");
store.connect(
"pop.gmail.com", "tucuenta@gmail.com", "la password");
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);

// Se obtienen los mensajes.
Message[] mensajes = folder.getMessages();

// Se escribe from y subject de cada mensaje
for (int i = 0; i < mensajes.length; i++)
{
System.out.println(
"From:" + mensajes[i].getFrom()[0].toString());
System.out.println("Subject:" + mensajes[i].getSubject());

// Se visualiza, si se sabe como, el contenido de cada mensaje
analizaParteDeMensaje(mensajes[i]);
}

folder.close(false);
store.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}

/**
* Metodo recursivo.
* Si la parte que se pasa es compuesta, se extrae cada una de las subpartes y
* el metodo se llama a si mismo con cada una de ellas.
* Si la parte es un text, se escribe en pantalla.
* Si la parte es una image, se guarda en un fichero y se visualiza en un JFrame.
* En cualquier otro caso, simplemente se escribe el tipo recibido, pero se
* ignora el mensaje.
*
* @param unaParte Parte del mensaje a analizar.
*/
private static void analizaParteDeMensaje(Part unaParte)
{
try
{
// Si es multipart, se analiza cada una de sus partes recursivamente.
if (unaParte.isMimeType("multipart/*"))
{
Multipart multi;
multi = (Multipart) unaParte.getContent();

for (int j = 0; j < multi.getCount(); j++)
{
analizaParteDeMensaje(multi.getBodyPart(j));
}
}
else
{
// Si es texto, se escribe el texto.
if (unaParte.isMimeType("text/*"))
{
System.out.println("Texto " + unaParte.getContentType());
System.out.println(unaParte.getContent());
System.out.println("---------------------------------");
}
else
{
// Si es imagen, se guarda en fichero y se visualiza en JFrame
if (unaParte.isMimeType("image/*"))
{
System.out.println(
"Imagen " + unaParte.getContentType());
System.out.println("Fichero=" + unaParte.getFileName());
System.out.println("---------------------------------");

salvaImagenEnFichero(unaParte);
visualizaImagenEnJFrame(unaParte);
}
else
{
// Si no es ninguna de las anteriores, se escribe en pantalla
// el tipo.
System.out.println(
"Recibido " + unaParte.getContentType());
System.out.println("---------------------------------");
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}

/**
* Presupone que unaParte es una foto adjunta a un correo.
* Recoge la imagen y la visualiza en un JFrame
*
* @param unaParte Parte de un correo correspondiente a una imagen.
*
* @throws IOException
* @throws MessagingException
*/
private static void visualizaImagenEnJFrame(Part unaParte)
throws IOException, MessagingException
{
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);
}

/**
* Supone que unaParte corresponde a una imagen de un fichero y que
* getFileName() esta relleno.
* Salva la imagen en d:\getFileName().
*
* @param unaParte Parte de un correo correspondiente a una imagen.
*
* @throws FileNotFoundException
* @throws MessagingException
* @throws IOException
*/
private static void salvaImagenEnFichero(Part unaParte)
throws FileNotFoundException, MessagingException, IOException
{
FileOutputStream fichero = new FileOutputStream(
"d:/" + unaParte.getFileName());
InputStream imagen = unaParte.getInputStream();
byte[] bytes = new byte[1000];
int leidos = 0;

while ((leidos = imagen.read(bytes)) > 0)
{
fichero.write(bytes, 0, leidos);
}
}
}

5.- Envío de un correo con datos adjuntos con JavaMail.

Habíamos visto que un correo compuesto tiene más o menos una estructura de árbol. Cada nodo del árbol tiene como datos una clase MultiPart. Las hojas del árbol son las que contienen la información del correo, es decir, el texto, la imagen, etc.

Vamos a ello.

Clases implicadas

Las clases que necesitamos para enviar un correo con adjuntos con JavaMail son

  • Session y Transport para la conexión con gmail y envio del mensaje.

  • MimeMessage, MimeMultiPart y MimeBodyPart Para construir el mensaje.

Obtención de Session

Como mencionamos en el envio de un correo sencillo, necesitamos obtener una instancia de Session y para ello necesitamos previamente rellenar una variable Properties.

Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.setProperty("mail.smtp.starttls.enable", "true");
props.setProperty("mail.smtp.port","587");
props.setProperty("mail.smtp.user", "
tucuenta@gmail.com");
props.setProperty("mail.smtp.auth", "true");

Session session = Session.getDefaultInstance(props, null);
session.setDebug(true);

Los detalles de estos valores que hemos rellenado puedes verlos en el enlace anterior.

Con esto ya tenemos instanciada la clase Session. Vamos ahora a construir el mensaje

Construir un correo de texto con un adjunto

Lo primero vamos a construir las dos partes del mensaje. El texto y la imagen. Para ello, instanciamos dos clases MimeBodyPart y las rellenamos con los datos.

Empezamos con la de texto, que es más sencilla

BodyPart texto = new MimeBodyPart();
texto.setText("Texto del mensaje");

Ya está. Al usar el método setText() sin parámetros, ya se está configurando todo para que sea un texto plano -text/plain en MIME type-.

El adjunto con la imagen también es sencillo. Supongamos que tenemos la imagen en un fichero D:\futbol.gif -por supuesto, de una extensión conocida por java-

También debemos crear un MimeBodyPart, pero esta vez llamamos al método setContent(). A este método hay que pasarle un DataHandler, que es una clase capaz de manejar los datos que vayan en esta parte. Para instanciar ese DataHandler debemos darle una fuente de datos. La fuente, puesto que tenemos la imagen en fichero, puede ser una clase FileDataSource, capaz de leer un fichero con datos y saber de qué tipo es (imagen, audio, etc). En resumen, el código sería este

BodyPart adjunto = new MimeBodyPart();
adjunto.setDataHandler(new DataHandler(new FileDataSource("d:/futbol.gif")));
adjunto.setFileName("futbol.gif");

La llamada a setFileName() es opcional. Haciéndola le daremos al receptor del correo posibilidad de saber el nombre del fichero de imagen. Si no lo ponemos, no pasa nada.

Juntar el texto y la imagen adjunta

Ahora debemos juntar estas dos partes en una única parte compuesta.

Para ello, instanciamos una clase MimeMultiPart y le añadimos ambos cachos. La clase MimeMultiPart no es más que una parte más de un correo, pero que está a su vez compuesta de otras partes -el texto y la imagen en nuestro caso-. Podemos ir añadiendo aquí todas las imagenes y otros ficheros que queramos.

MimeMultipart multiParte = new MimeMultipart();

multiParte.addBodyPart(texto);
multiParte.addBodyPart(adjunto);

Ya está. Ahora sólo nos falta el mensaje en sí, algo a lo que podamos dar un destinatario de correo, un asunto -subject-, etc.

Construir el mensaje de correo

El mensaje a construir es simplemente una instancia de MimeMessage. Al instanciarlo debemos pasarle el objeto Session que obtuvimos anteriormente. También debemos rellenar los campos de destinatario y demás. Y finalmente también debemos rellenar el contenido, metiendo nuestro MimeMultipart anterior.

Los detalles de los campos subject, to y from puedes verlos en el envío de un mensaje sencillo con JavaMail.

El código quedaría así

MimeMessage message = new MimeMessage(session);

// Se rellena el From
message.setFrom(new InternetAddress("yo@yo.com"));

// Se rellenan los destinatarios
message.addRecipient(Message.RecipientType.TO, new InternetAddress("tucuenta@gmail.com"));

// Se rellena el subject
message.setSubject("Hola");

// Se mete el texto y la foto adjunta.
message.setContent(multiParte);

Ya tenemos compuesto el total del correo. Ahora sólo queda enviarlo

Enviar el correo

Ya lo vimos en el ejemplo sencillo de envio de correo con JavaMail. Sólo tenemos que hacer lo siguiente.

Transport t = session.getTransport("smtp");
t.connect("
tucuenta@gmail.com","la password");
t.sendMessage(message,message.getAllRecipients());
t.close();

Simplemente obtenemos una instancia de Transport, que es la clase encargada del envio del correo.

Realizamos la conexión dando usuario -correo de gmail de la cuenta que estemos usando- y la clave.

Enviamos el mensaje con sendMessage(). Ojo, no uses el método send() a secas, porque ese no tiene en cuenta los parámetros de conexión.

Ejemplo completo:

package tucuenta.ejemplos.mail;

import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class EnviarMailComplejo
{
public static void main(String[] args)
{
try
{
// se obtiene el objeto Session. La configuración es para
// una cuenta de gmail.
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.setProperty("mail.smtp.starttls.enable", "true");
props.setProperty("mail.smtp.port", "587");
props.setProperty("mail.smtp.user", "tucuenta@gmail.com");
props.setProperty("mail.smtp.auth", "true");

Session session = Session.getDefaultInstance(props, null);
// session.setDebug(true);

// Se compone la parte del texto
BodyPart texto = new MimeBodyPart();
texto.setText("Texto del mensaje");

// Se compone el adjunto con la imagen
BodyPart adjunto = new MimeBodyPart();
adjunto.setDataHandler(
new DataHandler(new FileDataSource("d:/futbol.gif")));
adjunto.setFileName("futbol.gif");

// Una MultiParte para agrupar texto e imagen.
MimeMultipart multiParte = new MimeMultipart();
multiParte.addBodyPart(texto);
multiParte.addBodyPart(adjunto);

// Se compone el correo, dando to, from, subject y el
// contenido.
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress("yo@yo.com"));
message.addRecipient(
Message.RecipientType.TO,
new InternetAddress("tucuentag@gmail.com"));
message.setSubject("Hola");
message.setContent(multiParte);

// Se envia el correo.
Transport t = session.getTransport("smtp");
t.connect("tucuenta@gmail.com", "la clave");
t.sendMessage(message, message.getAllRecipients());
t.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}



Enviar SMS desde una aplicación JAVA


En esta entrada os mostraremos cómo enviar sms desde una aplicación Java. Seguro que esta funcionalidad os la ha pedido algún cliente que quería integrar directamente en vuestra aplicación la posibilidad de enviar un sms en alguna situación como comunicarse con sus propios clientes, enviar una alerta en determinadas ocasiones, etc…

Lo primero es encontrar un proveedor que permita el envío de sms mediante una conexión a su servidor. Puede que existan aún aquellas páginas donde era gratuito enviar un sms. Sin embargo la fiabilidad de entrega de estos proveedores, unido a que sólo permiten enviar a través de una página web y no desde un programa java y que suelen estár asociados al envío de publicidad no hacen de esta solución una solución aceptable.

Un proveedor de sms sirve de intermediario entre nuestra aplicación Java y los operadores de telefonía móvil. La misión del programa Java será por tanto conectarse de algún modo al proveedor elegido (HTTP, SMTP…) y pasarle los datos del sms en algún formato (con los propios parámetros de HTTP, con un xml…).

Después de buscar entre varios proveedores, nos hemos quedado con la pasarela de envío de sms de Altiria. El envío de sms se realiza con este proveedor mediante una petición POST a su servidor con los datos del sms que se quiere enviar como parámetros de la propia petición, siendo los más importantes el contenido del sms y los destinatarios. Se puede encontrar el documento de especificaciones de la conexión también en su web (Documento de especificaciones).

Queremos resaltar que el para que el ejemplo que ponemos a continuación funcione y envíe un sms, es necesario ponerse en contacto con Altiria y contratar un bono de envío de sms que permita la conexión a su pasarela de envío. El ejemplo sirve para ilustrar lo fácil que sería el envío del sms una vez contratado el bono.

Ya tenemos el proveedor y el mecanismo de envío (HTTP). Ya sólo queda implementar esta conexión HTTP en nuestro programa Java. Para ello necesitamos un cliente HTTP para realizar la conexión. Hemos elegido la librería HTTP de Jakarta, (HttpClient) que debe estar presente en el classpath de nuestra aplicación. El siguiente código realiza la conexión enviando los parámetros del sms.

Ejemplo:

//Se inicia el objeto HTTP

HttpClient client = new HttpClient();

client.setStrictMode(true);

//Se fija el tiempo máximo de espera de la respuesta del servidor

client.setTimeout(60000);

//Se fija el tiempo máximo de espera para conectar con el servidor

client.setConnectionTimeout(5000);

PostMethod post = null;

//Se fija la URL sobre la que enviar la petición POST

//Como ejemplo se supone www.pasarela_push_altiria.com/post/sms

post = new PostMethod("http://www.pasarela_push_altiria.com/post/sms");

//Se fija la codificación de caracteres en la cabecera de la petición

post.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");

//Se crea la lista de parámetros a enviar en la petición POST

NameValuePair[] parametersList = new NameValuePair[3];

parametersList[0] = new NameValuePair("dest", "34600111222");

parametersList[1] = new NameValuePair("dest", "34600111333");

parametersList[2] = new NameValuePair("msg", "Mensaje de prueba");

//Se rellena el cuerpo de la petición POST con los parámetros

post.setRequestBody(parametersList);

int httpstatus = 0;

String response = null;

try {

//Se envía la petición

httpstatus = client.executeMethod(post);

//Se consigue la respuesta

response = post.getResponseBodyAsString();

}

catch (Exception e) {

//Habrá que prever la captura de excepciones

return;

}

finally {

//En cualquier caso se cierra la conexión

post.releaseConnection();

}

//Habrá que prever posibles errores en la respuesta del servidor

if (httpStatus!=200){

return;

}

else {

//Se procesa la respuesta capturada en la cadena ‘‘response’’



Este código se traduce en el envío de un sms con el texto “Mensaje de Prueba” a los móviles 34600111222 y 34600111333.

6 comentarios:

  1. Hola buenas tardes ..
    Se puede enviar un mensaje de texto a celulares pero sin el uso de Altiria?

    ResponderEliminar
  2. Si se puede enviar un SMS, sin plataformas SMS de empresas. Pero tendrías que conectar tu movil a la PC y abrir un puerto serial que conecta al movil y por ultimo utilizar los comandos AT que reconoce el movil, estos comandos son como un lenguaje de modems, osea que podrás dar un toke a otro movil, enviar sms, etc.

    ResponderEliminar
  3. Hola,

    Qué librerias java existen para manejar la conexión y el envio de SMS a través de un telefono móvil??

    ResponderEliminar
  4. que librerias debo usar en el caso de la clase sms, pongo algunas y me salen errores, pues nose corresponde bien los constructores.

    ResponderEliminar
  5. Hola Jesús, para aclarar tus dudas sobre la clase sms, contacta desde la página API SMS de Altiria: te enviarán la documentación completa de su gateway sms y la API SMS actualizada con una cuenta de prueba para usarla con Java, PHP o cualquier otro lenguaje y si tienes alguna duda te la aclara su soporte técnico.

    ResponderEliminar
  6. buenas tardes, tendras idea de porque el codigo funciona perfectamente con la red de mi casa pero no con la del trabajo?
    En el trabajo desactive firewall y antivirus y aun no funciona.
    Uso telnet smtp.gmail.com 465 y me comenta que no es posible establecer conexion. Que puedo hacer para acceder a ese puero su ya lo abri en el firewall tambien y nada?

    ResponderEliminar