PFC “Gestor de listas de distribución mediante Struts, JavaMail y
Transcripción
PFC “Gestor de listas de distribución mediante Struts, JavaMail y
PFC “Gestor de listas de distribución mediante Struts, JavaMail y MySQL” David Cuberes, [email protected] ETSE-ETIG, septiembre 2004. Tutor: Dr. Sergio Gómez. 1. OBJETIVOS 6 2. ESPECIFICACIONES 8 2.0 INTRODUCCIÓN 2.1 DESCRIPCIÓN FUNCIONAL PÁGINA DE INICIO LISTADO DE MENSAJES DE UNA LISTA DE DISTRIBUCIÓN LISTADO DE ARCHIVOS DE UNA LISTA DE DISTRIBUCIÓN MENSAJES CREACIÓN DE LISTAS CONSULTA DE LOS DATOS DE LA LISTA: MODIFICACIÓN DE LOS DATOS DE LA LISTA: EXPULSIÓN / READMISIÓN USUARIOS ALTA DE USUARIOS MODIFICACIÓN USUARIOS: REGISTRO EN LISTAS LOGIN SESIÓN ACCESO APLICACIÓN 2.2 ASIGNACIÓN HARDWARE DEL SERVIDOR SOFWARE DEL SERVIDOR HARDWARE Y SOFTWARE CLIENTE 2.3 DIAGRAMA DE CASOS DE USO 2.4 DIAGRAMA DE CLASES DE LOS CASOS DE USO CDU 1. BAJAR REGULARMENTE CORREO CDU 2. REENVÍO MENSAJES CDU 3. INICIAR SESIÓN CDU 4. CONSULTA DATOS LISTA CDU 5. ACCESO MENSAJES DE LISTAS PÚBLICAS CDU 6. ACCESO ARCHIVOS DE LISTAS PÚBLICAS CDU 7. ALTA USUARIO CDU 8. IDENTIFICARSE CDU 9. CREAR LISTA CDU 10. CONSULTA DATOS USUARIO CDU 11. MODIFICAR DATOS USUARIO CDU 12. REGISTRARSE A LISTA CDU 13. CONSULTA LISTAS REGISTRADO CDU 14. DESREGISTRARSE DE LISTA CDU 15. RECIBIR O NO MAILS LISTA POR CORREO CDU 16. ACCESO MENSAJES DE LISTA PRIVADAS CDU 17. ACCESO ARCHIVOS DE LISTAS PRIVADAS CDU 18. ENVIAR MENSAJE DESDE WEB CDU 19. ENVIAR MENSAJE POR CORREO CDU 20. CONSULTAR LISTAS CREADAS CDU 21. MODIFICAR DATOS LISTA CDU 22. EXPULSAR USUARIO LISTA 2 8 8 8 8 9 9 10 11 11 11 12 12 13 13 13 13 14 14 14 15 15 15 17 19 19 20 20 21 22 23 23 24 25 26 26 27 27 28 28 28 29 29 29 30 30 CDU 23. READMITIR USUARIO LISTA 2.5 RELACIÓN DE CLASES CLASES FRONTERA CLASES DE ACCIÓN CLASES DE CONTROL CLASES DE ENTIDAD BEANS DE FORMULARIO 31 31 31 32 32 32 32 3. DISEÑO 33 3.1 RELACIÓN COMPLETA DE LAS CLASES DEL PROYECTO 3.2 INTERFAZ GRÁFICA 3.3 STRUTS 3.4 LÓGICA DE NEGOCIO DIAGRAMA ESTÁTICO DE CLASES ATTACHMENT.JAVA COLASINCRONIZADA.JAVA COMUNASMTP.JAVA COMUNPOPREPLIES.JAVA FILALISTADO.JAVA GESTIONACCESO.JAVA GESTIONBANEADOS.JAVA GESTIONLISTAS.JAVA GESTIONUSUARIOS.JAVA LISTADISTRIBUCION.JAVA LISTADO.JAVA LISTADOATTACHMENTS.JAVA LISTADOMAILS.JAVA MYAPPLICATION.JAVA MYLINKEDLIST.JAVA MYLOGIN.JAVA MYMESSAGE.JAVA MYSESSION.JAVA NUEVOMAIL.JAVA PATTERNSPOOL.JAVA PATTERNSREPLY.JAVA THREADPOP.JAVA THREADREPLIES.JAVA THREADSMTP.JAVA 3.5 BASE DE DATOS DIAGRAMA TIPOS DE DATOS PRESENTES EN LAS TABLAS TABLAS SCRIPT DE LA BASE DE DATOS 33 35 38 40 41 42 42 42 43 43 43 44 44 45 46 47 49 49 49 50 50 50 51 52 52 53 53 55 56 57 57 57 58 63 4. DESARROLLO 65 CDU 1. BAJAR REGULARMENTE CORREO CDU 2. REENVÍO MENSAJES CDU 3. INICIAR SESIÓN CDU 4. CONSULTA DATOS LISTA 65 67 68 69 3 CDU 5. ACCESO MENSAJES LISTA PÚBLICA CDU 6. ACCESO ARCHIVOS DE LISTAS PÚBLICAS CDU 7. ALTA USUARIO CDU 8. IDENTIFICARSE CDU 9. CREAR LISTA CDU 10. CONSULTA DATOS USUARIO CDU 11. MODIFICAR DATOS USUARIO CDU 12. REGISTRARSE EN LISTA CDU 13. CONSULTA LISTAS REGISTRADO CDU 14. DESREGISTRARSE EN LISTA CDU 15. RECIBIR O NO MAILS POR CORREO CDU 16. ACCESO MENSAJES LISTA PRIVADA CDU 17. ACCESO ARCHIVOS LISTAS PRIVADA CDU 18. ENVIAR MENSAJE POR WEB CDU 19. ENVIAR MENSAJE POR CORREO CDU 20. CONSULTAR LISTAS CREADAS CDU 21. MODIFICAR DATOS LISTA CDU 22. EXPULSAR USUARIO DE LISTA CDU 23. READMITIR USUARIO A LISTA 71 74 74 75 76 77 78 79 79 80 80 80 80 80 81 82 82 83 84 5. EVALUACIÓN PRUEBAS 85 CDU 1. BAJAR REGULARMENTE CORREO CDU 2. REENVÍO MENSAJES CDU 3. INICIAR SESIÓN CDU 4. CONSULTA DATOS LISTA CDU 5. ACCESO MENSAJES DE LISTAS PÚBLICAS CDU 6. ACCESO ARCHIVOS DE LISTAS PÚBLICAS CDU 7. ALTA USUARIO CDU 8. IDENTIFICARSE CDU 9. CREAR LISTA CDU 10. CONSULTA DATOS USUARIO CDU 11. MODIFICAR DATOS USUARIO CDU 12. REGISTRARSE A LISTA CDU 13. CONSULTA LISTAS REGISTRADO CDU 14. DESREGISTRARSE DE LISTA CDU 15. RECIBIR O NO MAILS LISTA POR CORREO CDU 16. ACCESO MENSAJES DE LISTA PRIVADAS CDU 17. ACCESO ARCHIVOS DE LISTAS PRIVADAS CDU 18. ENVIAR MENSAJE DESDE WEB CDU 19. ENVIAR MENSAJE POR CORREO CDU 20. CONSULTAR LISTAS CREADAS CDU 21. MODIFICAR DATOS LISTA CDU 22. EXPULSAR USUARIO LISTA CDU 23. READMITIR USUARIO LISTA 85 86 86 87 87 89 89 90 90 91 91 91 92 92 92 92 93 93 93 93 93 94 94 6. CONCLUSIONES 95 7. RECURSOS UTILITZADOS 96 BIBLIOGRAFÍA 96 4 PÁGINAS WEB SOFTWARE HARDWARE 96 99 100 8. MANUALES 101 8.1 INSTALACIÓN 8.2 MANUAL DE USUARIO LOGIN ALTA USUARIO MODIFICACIÓN USUARIO DATOS PERSONALES REGISTRO EN LISTAS CREACIÓN DE LISTAS EXPULSIÓN, READMISIÓN DE USUARIOS LISTA DE MENSAJES MENSAJE LISTADO ARCHIVOS 101 103 104 105 106 107 108 112 115 119 122 9. IMPLEMENTACIÓN EN SOPORTE FÍSICO 123 5 1. OBJETIVOS Desarrollo de una aplicación WEB que gestione listas de distribución asociadas a cuentas de correo POP mediante Struts, JavaMail y una base de datos MySQL. Con arreglo a las características esenciales que la mayoría de las listas suelen poseer, se identifican tres ejes a desarrollar: • • • listas: o creación. Existen sitios web con un número de listas invariable únicamente modificable por el administrador, y otros en los que a los usuarios registrados se les permite crear sus propias listas. Nosotros nos decantaremos por esta segunda posibilidad. o mostrar sus propiedades: fecha de creación, moderador, número de mensajes, tema a discutir, ... o tener conocimiento de los usuarios registrados en la misma. o diferenciar entre listas públicas, de acceso universal, y listas privadas, restringidas a usuarios dados de alta en ella. o un moderador encargado de llevar el control, con potestad de impedir la entrada a visitantes no gratos. usuarios: o alta, consulta y modificación de los datos personales de un usuario registrado. o decidir las listas a las que uno desea formar parte. o posibilidad de crear una nueva lista. mensajes: o visualización atendiendo a diversos criterios de búsqueda y de filtrado de un listado de mensajes, así como la relación entre mensajes pertenecientes a un mismo thread. o muchas listas no aceptan el envío de archivos. En nuestro caso, sí los aceptamos, y también un listado es recuperable según varias reglas, como en el anterior punto. o mecanismo automatizado de recepción de mensajes llegados por correo y reenvío a quienes lo soliciten. En el universo del software libre ya disponemos de un número razonable de implementaciones completas con las que montar listas o foros de discusión, en especial con PHP, y que cuentan con gran aceptación por parte de internautas y webmasters. Cabe señalar que no he encontrado ninguna solución en la que se empleara JSP o Struts. De todas formas, no ha sido mi ánimo cubrir ningún “nicho” en el desarrollo de software, ni mejorar algo ya existente o aportar una solución innovadora. Encontrar un proyecto con el cual pudiera comprender el funcionamiento de una aplicación web, aplicando tecnologías muy demandadas como JSP o Struts ha sido mi principal motivación. 6 De hecho la elaboración del proyecto ha implicado un conocimiento de: • correo electrónico: envío y recepción, attachments, tipos MIME, multipart, JavaMail. • HTML para la creación de la interfaz gráfica, y de CSS para mantener un estilo coherente. • JavaScript: verificación de datos de formulario del lado del cliente y aspecto dinámico de las páginas. • Contenedores de servlets, en especial Tomcat. • Bases de datos: JDBC, SQL, concurrencias, y connection pooling. • Struts, modelo-vista-controlador. • JSP. • expresiones regulares con Java y JavaScript para validaciones o búsqueda de patrones en cadenas. • threads: sincronismos, wait/notify. • beans. • etcétera. Este proyecto se inició hasta estar bien avanzado empleando el Modelo 1 de JSP. Decidí desestimar los scriptlets dentro del JSP a favor de la claridad y separación en capas que ofrece Struts. A medida que crece una aplicación web que se vale de ASP, JSP, o PHP puede llegar a hacerse un tanto confusa e ingobernable. 7 2. ESPECIFICACIONES 2.0 INTRODUCCIÓN Detallaremos los requisitos a cumplir en respuesta a qué debe hacer, en qué consiste el proyecto, y cuáles son a priori los recursos necesarios de hardware y software. Es una fase de recogida y análisis de información centrada en definir el problema, cuyas observaciones constituirán los cimientos del posterior diseño, el cual es el encargado de encontrar la solución. 2.1 DESCRIPCIÓN FUNCIONAL Página de inicio Entrada en la cual: • aparezca el estado del usuario: invitado o registrado. Los invitados únicamente pueden leer las listas públicas. Los registrados disponen de una serie de ventajas que iremos viendo en las siguientes funciones. • se muestre una fila por cada lista de distribución existente en el sitio web. Deben aparecer para cada una, el número de mensajes , de usuarios, la fecha del último mensaje o bien su hora si ha sido recibido durante el mismo día, y si es una lista pública o privada. En la segunda, el acceso está limitado a los usuarios que se hayan dado de alta en ella. En cada fila unos enlaces dirigirán al listado de mensajes o a la página de información de la lista. • haya enlaces a páginas de registro de usuarios, de identificación para usuarios ya registrados y de consulta del perfil de un usuario logineado. Listado de mensajes de una lista de distribución Por cada mensaje expondremos: • indicador de si el mensaje incluye un adjunto. • remitente. • asunto, con un enlace para leer el mensaje. • hora o fecha, según si la llegada se produce el día de la consulta o no. • tamaño en bytes. Los mensajes se ordenan por defecto del último mensaje recibido al primero, mostrándose en páginas de n filas. Otras opciones pasan por la visualización del total o de un conjunto de mensajes conforme a: • listado normal o relacionado, en la cual conocer mensajes vinculados en una conversación común, en niveles con arreglo a su orden de precedencia. 8 • orden por remitente, asunto, fecha o tamaño, ya sea en ascendente o descendente. • filtrado de mensajes por: o un rango de fechas o de tamaños. o palabra contenida en el remitente o en el asunto. Existirá la posibilidad de redactar nuevos mensajes para los usuarios que además de registrados pertenezcan a la lista consultada. Listado de archivos de una lista de distribución De apariencia similar al anterior, devolverá el total de archivos que han sido adjuntados en los mensajes, con el siguiente aspecto por fila: • nombre del archivo, con un enlace para la descarga desde la misma página. • tamaño en bytes. • hora o fecha, según si la llegada se produce el día de la consulta o no. • remitente del mensaje que adjuntaba el archivo, con enlace para lectura del mensaje. Los criteris de búsqueda o filtrado posibles: • orden por nombre del fichero, remitente, tamaño o fecha en ascedente o descendente. • búsqueda de mensajes: o por un rango de fechas o tamaños o por palabra contenida en el nombre del fichero o en el remitente Mensajes Si estamos leyendo un mensaje de la lista debe incorporar la información siguiente: • identificador numérico del mensaje único en toda la aplicación, servirá para hacer una referencia rápida al mismo. • remitente. • asunto. • fecha y hora de creación. • tamaño. • una línea por adjunto que incluya: o nombre. 9 o tamaño en bytes. o botón de descarga. • texto del mensaje. • enlaces para la navegación entre mensajes, de respuesta del mensaje si estamos registrados en la lista de distribución, o de vuelta a la página desde donde enlazamos antes (listado de mensajes, listado de archivos...). La navegación mantendrá los mensajes en el mismo orden en que aparecían en el listado, y a la vuelta al listado nos posicionará en la página del último mensaje visualizado. • de pertenecer el mensaje a un hilo de conversación, se mostrará al final de su página, todos los mensajes con los cuales está relacionado, permitiendo el acceso directo a cualquiera de ellos, la navegación entre todos los mensajes que forman el hilo uno por uno o la visualización de todos los mensajes en una sola página web. Si estamos redactando un mensaje nuevo o respondiendo a uno, mostrará: • remitente, el nick con el cual nos hemos identificado, introducido automáticamente. • destinatario (nombre de la lista más su dirección de correo electrónico), introducido automáticamente. • campo para introducir path del adjunto, si se desea incluir uno. • área de texto donde redactar el mensaje. Si el mensaje responde a otro anterior, éste tendrá cada línea precedida por un “>”. • botón de envío y link para volver sin enviar el mensaje comenzado. No se permitirá adjuntar archivos que superen un tamaño máximo. Creación de listas Con anterioridad a la página de entrada de datos para una nueva lista debe haberse verificado que existe la posibilidad de crearla. En nuestro proyecto cada lista irá asociada una dirección de correo a la que se liga en el momento de su creación. Podemos tener contratadas con nuestro proveedor de servicios de Internet un número determinado de direcciones de correo electrónico y llegar a agotarse todas. En tal caso, avisaremos de la imposibilidad de crear nuevas listas. Entradas: • nombre lista que la identifique ante los usuarios. • identificador creado automáticamente que lo identifique internamente en la aplicación. • descripción breve que pueda mostrarse en un listado. • comentario de una mayor extensión al campo anterior, a mostrar en la página de datos de la lista. 10 • nick del fundador de la lista1. • fecha de creación. • email asociado a la lista, que será empleado tanto por la aplicación a la hora de bajar los mensajes de correo como por los usuarios que deseen participar en la lista por ese medio. • tipo lista si pública o privada. De estas entradas, la fecha, el e-mail y el fundador son asignadas automáticamente y no se pueden cambiar. Las entradas “nombre lista” y “descripción breve” deben cumplimentarse o bien cancelar el proceso. Consulta de los datos de la lista: Muestra todos los datos que se introdujeron a la hora de crear la lista. Esta consulta está permitida para cualquier usuario, esté o no registrado en la lista o siquiera en la web. Son datos necesariamente públicos, porque han de permitir al no registrado en dicha lista conocer las propiedades de la misma y así decidir si darse de alta en ella o no. También se muestran una lista de los usuarios registrados en esa lista, pues una condición de las listas es conocer quiénes son los que reciben tus mensajes y poderte dirigir directamente a ellos. Modificación de los datos de la lista: El fundador dispone de un permiso extendido respecto al resto de usuarios en lo que concierne a las listas que haya creado él. Al entrar en la consulta de los datos, podrá también modificar lo que considere oportuno con excepción de: • idLista, identificador de la lista, que permanece oculto y se emplea para relaciones entre tablas. • nick del fundador, y fecha de creación: una lista no tiene más que un fundador y una fecha y no tiene sentido cambiarlos. • e-mail de la lista: si en el futuro se cambia de proveedor, es el administrador de la aplicación quien ha de encargarse de cambiar las direcciones de correo y no el fundador. En cuanto a la lista de usuarios registrados, el fundador en su condición inherente de moderador tiene control sobre quien accede a las listas. Expulsión / readmisión usuarios El creador de una lista es libre de impedir la entrada a quien no respete las normas de comportamiento y educación exigibles en cualquier lugar. 1 En el proyecto hemos simplificado los roles de gestión de listas: el creador también será el moderador. 11 De llegar a ese extremo, deberá cumplimentar en el formulario de expulsión el motivo que le lleva a tomar tal decisión, el cual se le expondrá al afectado junto con la fecha en que se hizo efectiva la expulsión. En el futuro el usuario puede ser readmitido, siempre bajo criterio del mismo fundador. Cabe indicar que el ámbito de expulsión es la lista misma. Por lo tanto, un usuario sin acceso a una lista puede seguir entrando en otras en las que esté aceptado. Alta de usuarios Entradas: • nombre • primer apellido • segundo apellido • e-mail • login • password • otros campos: que nos pueda interesar en nuestra web a efectos estadísticos o comerciales y que no tendremos en cuenta en este proyecto. De todos los campos, son imprescindibles el login, el password y el e-mail. Sin cumplimentarlos no se dará de alta el usuario. El login es necesario porque es como será conocido tanto por usuarios como por las relaciones que realice el ordenador a nivel interno. El e-mail al margen que en una lista de correo serviría para notificaciones o publicidad, es necesario para que pueda recibir las listas por correo, si lo pide. Modificación usuarios: Es posible modificar cualquier campo, salvo el de login que lo identifica para siempre2. Si modifica el correo electrónico, la lista de mensajes le será enviado a la nueva dirección desde el mismo momento del cambio. Dentro de este estado, se pueden modificar también otros dos aspectos al margen de los datos personales como son: 2 • gestión listas donde estoy registrado. • gestión listas de las que soy fundador. al igual que sucede en cualquier sitio web, donde el login de acceso no se suele poder cambiar. 12 Registro en listas Los usuarios escogerán las listas a las que desean darse de alta de entre todas las posibles, decidiendo a su vez si desean recibir los mensajes de la lista por correo electrónico. Esta opción puede activarse o desactivarse cuando se desee, al igual que el darse de baja de una lista. Login Entradas: • login. • password. La tarea es identificar al usuario. Desde la página de login podrá también darse de alta el usuario que no esté registrado en la web. Un usuario no identificado permanece con el estado “invitado”, lo cual representa que sólo pueden leerse las listas públicas. El usuario sólo necesita loginearse una vez, y ya queda identificado para todas las ocasiones en que se requiera conocer la identidad del visitante, como podría ser el acceso a cada lista. Por esta razón, el “login” y el “acceso” están separados. Sesión Mantendrá el login y el email del usuario registrado desde el momento en que se loginee y hasta que caduque su sesión. En el primer momento de acceso a la página de inicio estos dos atributos estarán a nulo. Acceso Controlará quién entra a cada lista según los permisos del usuario y la característica de la misma. Casos posibles: • • lista pública: o lectura de mensajes para todos los usuarios. o escritura de mensajes para los registrados en la lista (salvo expulsados). o si un usuario está expulsado se le mostrará una página con la fecha y el motivo de la expulsión, se le informará que no puede escribir mensajes en la web ni enviar o recibir por correo, aunque sí puede entrar como invitado3. lista privada: o lectura y escritura de mensajes sólo para usuarios registrados en la lista (salvo expulsados) 3 Nadie puede evitar que un usuario expulsado de una lista pública entre como invitado (sin loginearse) y lea los mensajes, pero al menos no interferirá en el desarrollo de la lista. 13 o un usuario no registrado encontrará una página donde se le informará de la necesidad de registrarse si quiere entrar en la lista. o un usuario expulsado encontrará también una página con fecha y motivo de la expulsión y tampoco podrá acceder ni vía web ni vía correo electrónico. Los correos recibidos en el buzón de entrada serán desestimados si el usuario no pertenece a la lista o bien ha sido expulsado de ella. En estos casos, el remitente recibirá un correo informándole del motivo po el que su mensaje no ha sido aceptado. Aplicación Componente encargado del arranque del sistema y el mantenimiento en memoria de las estructuras necesarias. Deberá iniciar las listas de distribución que se encarguen por sí mismas de recoger periódicamente los mensajes depositados en sus cuentas de correo y de reenviar a todos los usuarios registrados de la lista que así lo soliciten. 2.2 ASIGNACIÓN Descripción de componentes hardware y software. Hardware del servidor 4 Precisaremos de un ordenador con capacidad de albergar una base de datos, un servidor web con capacidad de procesamiento de JSP y conexión a red de banda ancha. Para una carga media de listas/mensajes/usuarios será necesario que la RAM sea alta, cuanta más mejor, por los siguientes motivos: • se mantendrán en memoria estructuras para no acceder constantemente a la base de datos. • en las ocasiones en que deba recuperarse registros de la base de datos una memoria mayor a la habitual nos permitiría aumentar el número posible de conexiones a la base de datos y su caché, reduciendo el tiempo de espera 5. • Las aplicaciones java requieren cargar en memoria la Máquina Virtual de Java, que consume muchos recursos. Si el sistema operativo es Windows XP, ya de por sí muy exigente, se estima que los requisitos mínimos serían los de un ordenador de 1 GHz con 512 MB. estos supuestos son únicamente si la aplicación fuera a explotarse realmente. En nuestro caso, los requisitos son mucho menores, habida cuenta del número de usuarios y listas que probaremos y que se especificarán en el apartado de la evaluación de las pruebas. 4 5 no se ha estudiado el tuning de la base de datos en este proyecto. 14 La arquitectura de hardware es indiferente. La aplicación será multiplataforma, por estar escritos en Java tanto el contenedor de servlets como el proyecto. MySQL también se encuentra disponible en la mayoría de las arquitecturas existentes. Sofware del servidor El sistema operativo puede ser cualquiera de los existentes en el mercado. Por los mismos motivos expuestos al mencionar la arquitectura. El servidor requiere Jakarta-Tomcat u otro contenedor de servlets compatible con las mismas especificaciones, un servidor de bases de datos MySQL, y el JSDK de Java. No basta con el JRE, puesto que Tomcat requiere llamar al JSDK para compilar los JSP. Hardware y software cliente Un navegador cualquiera sin importar ni la plataforma ni el sistema operativo. Si la antigüedad del ordenador y del software no es superior a los 5 años debería funcionar sin ningún problema. Incluso un ordenador más antiguo que pueda utilizar una versión reciente de un navegador tampoco hallaría problemas, ya que el ordenador cliente sólo se emplea para mostrar los resultados. Navegadores obsoletos podrían tener inconvenientes a la hora de interpretar etiquetas HTML, CSS o javascript. El acceso a Internet puede ser la de un módem de 33kbps, ya que se emplearán páginas con contenidos sencillos, nada pesadas. 2.3 DIAGRAMA DE CASOS DE USO Síntesis de las tareas realizables por cada actor, tras la lectura de la descripción funcional. Cada tarea recibirá el nombre de “caso de uso”, de ahora en adelante referidos en ocasiones como “CDU”, y serán tenidos en cuenta a lo largo de todo el documento como se ve fácilmente en el índice. 15 BAJAR REGULARMENTE EL CORREO APLICACIÓN REENVÍO MENSAJES INICIAR SESIÓN CONSULTAR DATOS LISTA ACCEDER MENSAJES DE LISTAS PÚBLICAS INVITADO ACCEDER LISTA ARCHIVOS DE LISTAS PÚBLICAS DARSE DE ALTA COMO USUARIO IDENTIFICARSE CREAR LISTA REGISTRADO CONSULTAR DATOS USUARIO MODIFICAR DATOS USUARIO REGISTRARSE EN LISTA Nota: los actores que vienen a continuación “heredan” los casos de uso del actor REGISTRADO, puesto que estar registrado en lista o ser creador de una implica estar previamente registrado en la web. 16 CONSULTA LISTAS ESTOY REGISTRADO DESREGISTRARSE EN LISTA RECIBIR O NO MAILS LISTA POR CORREO ACCESO MENSAJES LISTA PRIVADA REG ISTRADO EN LISTA ACCESO ARCHIVOS LISTA PRIVADA ENVIAR MENSAJE DESDE WEB ENVIAR MENSAJE POR CORREO CONULTAR LISTAS CREADAS MODIFICAR DATOS LISTA CREADA CREADOR LISTA EXPULSAR USUARIO DE LISTA READMITIR USUARIO DE LISTA 2.4 DIAGRAMA DE CLASES DE LOS CASOS DE USO Corresponde ahora concretar los CDU, y examinar las clases que forman parte en cada una de ellos sin interesarnos de momento ni en su intercambio de datos ni cuál es su orden de creación. Estas clases, llamadas de análisis, son de 3 tipos: frontera, entidad y control. Las primeras son la interfaz gráfica, las “pantallas” en las que hombre cumplimenta formularios, y recibe outputs de la máquina. Las clases entidad representan datos persistentes, objetos de negocio que son creados, actualizados o del que recuperamos información mediante las clases de control. En un esquema clásico una clase de control hace de intermediario entre entidad y frontera. Haremos, sin embargo, una subdivisión dentro de las clases de control: las encargadas de la gestión de las clases de entidad que las acompañarán en la capa de negocio, y unas que designaremos “de 17 acción”, cuyo cometido será el intercambio entre la capa presentación y la capa de negocio6 permitiéndonos separar ambas. El motivo perseguido es que la lógica de presentación no tenga acceso directo a la lógica de negocio ni viceversa7. Por lo tanto, se advertirá que toda clase-acción llamará a una clase de control, recibirá los resultados y los enviará a la pantalla. Por eso veremos en los diagramas de clases de casos de uso que de las clases acción suele haber tanto flechas de ida como de vuelta. Aunque este gráfico no corresponda al apartado de especificaciones se considera oportuno incluirlo para entender los diagramas de clases siguientes. CAPA PRESENTACIÓN INTRODUCCIÓN DATOS CAPA NEGOCIO 1: datos CLASE ACCIÓN 2: crea 4: resultado CLASE GESTIÓN 3: 5.1: PÁGINA ÉXITO CLASE ENTIDAD 5.2: PÁGINA ERROR Es una simplificación, pero a cualquier clase frontera le antecede una clase acción. Éste puede invocar a una o varias clases de gestión. Cada clase de gestión puede acceder a varias clases de entidad. A la vuelta, según los datos, la clase acción nos dirige a una clase frontera u otra. Normalmente, será una única página la que devolverá la clase-acción. Nada impide tampoco que la clase frontera que invoca a la clase acción y la clase frontera que recibe de ésta sean la misma. Otro tipo de clase empleado, serán los “beans de formulario”, partícipes del intercambio de información entre las páginas que incluyen formularios y las clases-acción. Pueden considerarse “clases-entidad”, pues representan la entidad “formulario” pero no forman parte de la lógica de negocio. En UML la regla es: a cada caso de uso le corresponde un único diagrama de clases de análisis. Se observará, sin embargo, que en algunas ocasiones varios casos de uso tienen un mismo diagrama, por ejemplo los casos del tipo consultar/modificar. No obstante, no fusionaremos explicaciones en un mismo diagrama, pese a compartirlo. Así mantenemos el mismo orden que en el diagrama de casos de uso, además que a lo largo de varios capítulos (diseño, desarrollo, evaluación de pruebas) los casos de uso volverán a ser mencionados, y conviene una explicación unívoca de cada uno. Por último en los CDU 1 y 2 al ser tareas sin interfaz gráfica no encontraremos ni clases de frontera ni clases-acción. La existencia de la capa de negocio en este proyecto es virtual, ya que los objetos deben su existencia a los objetos Struts que los crean, y no se encuentran dentro de un contenedor de EJB’s. De todas formas, con Struts mantenemos una separación entre presentación y lógica. 7 Hay un motivo más: en Struts se emplean estas clases Action. 6 18 CDU 1. Bajar regularmente correo aplicación gestión pop pop mensajes lista • Actor: aplicación. • Precondición: proceso que arranca cada cierto lapso de tiempo. • Postcondición: mensajes nuevos guardados en la base de datos. • Pasos: o o o o o • para cada lista de distribución la aplicación se autentica en su cuenta de correo. descargamos los mensajes. los procesamos. los guardamos en la base de datos se enlaza con el CDU2 para que los reenvíe. Notas: entidad “pop” es externo y aparece en el diagrama para hacerlo más claro. Por lo tanto, no pertenece al proyecto y no se considerará clase de entidad del mismo. CDU 2. Reenvío mensajes aplicación gestión smtp smtp • Actor: aplicación. • Precondición: proceso que arranca llamado por CDU 1 ó CDU 18. • Postcondición: mensajes reenviados a los usuarios que lo pidieron. • Pasos: para cada mensaje descargado por CDU 1 se observa usuarios registrados a la lista que desean recibirlos y se les reenvía. Similar con CDU 18. • Nota: la clase “smtp” representa un servidor smtp externo, que incorporamos en el diagrama para hacerlo más claro. Por lo tanto, no pertenece al proyecto y no se considerará clase de entidad del mismo. 19 CDU 3. Iniciar sesión introduce en navegador direccion web Acción inicio Actor pantalla Inicio sesion • Actor: invitado. • Precondición: acceder a página de inicio. • Postcondición: o se crea un objeto sesión. o página inicio devuelta. • Pasos: o invitado entra en la página de inicio de la web o “acción inicio” crea objeto “sesión” cuyos atributos login y password estarán a nulo indicando que es un usuario invitado o se redirige a página de inicio. • Notas: accesos posteriores a la página inicial por parte del mismo usuario no crean nuevos objetos sesión. CDU 4. Consulta datos lista crea pantalla Inicio datos lista Acción lista pantalla listas estoy registrado gestión listas representa bean form. lista pantalla datos lista lista pantalla listas fundadas usuario • Actor: cualquiera. • Precondiciones: o lista distribución a consultar existente. o indicar desde qué pantalla accedemos para poder volver. • Postcondición: página de datos lista devuelta. • Pasos: o accedemos desde cualquiera de las pantallas que se observan. o ”acción lista” crea objeto “gestión listas” que consulta en la lista y devuelve la información a objeto acción. Éste cumplimenta el bean formulario que es pasado a la pantalla. o en pantalla datos lista recibimos datos consulta. 20 • Notas: las precondiciones se cumplen siempre si accedemos desde alguna de esas páginas. CDU 5. Acceso mensajes de listas públicas crea gestión acceso acceso / no acceso pantalla Inicio acción acceso pantalla listas estoy registrado login acción listado mensajes tipo lista, baneado? registrado? sesión lista baneados pantalla acceso pantalla listas denegado fundadas usuario pantalla listado mensajes gestión listado mensajes bean form. listado mensajes acción mensaje pantalla mensaje bean form. mensaje acción extiende solicita mensaje mensajes lista datos mensaje pantalla extiende • Actor: cualquiera. • Precondiciones: o acceder desde las pantallas señaladas. o seguir los pasos. • Postcondiciones: o acceso a los mensajes. o página de acceso denegado, en la que se avisa que los no registrados en lista o registrados en alta pero expulsados sólo podrán leer los mensajes. • Pasos: o accedemos desde cualquiera de las pantallas que se observan. o “acción acceso” crea “gestión acceso” el cual: • consulta la sesión para saber si el usuario está logineado o no (login nulo= invitado). • si logineado, se comprueba que no esté expulsado de la lista. • lee de lista que su tipo es “lista pública”. o se ejecutará “acción listado mensajes” si todo está correcto, en caso contrario, recibimos página con los motivos por los cuales sólo podemos acceder en modo lectura. o “acción listado mensajes” llama a “gestión listado mensajes” que lee los mensajes de la lista a devolver en la página de listado de mensajes, según los parámetros recibidos por el formulario. o se puede cambiar los valores de los controles del formulario las veces que se considere necesarias, o dejarlo tal cual está. En cada cambio, se modificará la información en el bean de formulario de listado mensajes que será leído por “gestión listado mensajes”. 21 o o o • en la página elegimos el mensaje que deseamos leer. una vez leído, podemos leer otros volviendo a la lista de mensajes o navegando entre ellos. Siempre que leemos un mensaje, se ejecuta la “acción mensaje” que a su vez llama a “gestión listado mensajes”. desde la página de mensaje, si éste forma parte de un hilo de discusión, mediante la “página extiende” veremos todos los mensajes relacionados. Notas: objeto “Lista” contiene las propiedades de la lista, es la “cabecera” en memoria, en contraposición con “mensajes lista”, que por su potencial número se recupera de disco. CDU 6. Acceso archivos de listas públicas pantalla listado mensajes acción listado archivos archivos lista pantalla listado archivos bean form. listado arch. gestión listado archivos pantalla mensaje descarga archivo archivo (descargado) archivo (disco duro servidor) • Actor: cualquiera. • Precondiciones: o acceder desde las pantallas señaladas. o seguir los pasos. • Postcondiciones: archivo descargado. • Pasos: o accedemos desde cualquiera de las pantallas que se observan: pantalla listado archivos vía pantalla listado mensajes (1) ó pantalla mensaje(2). (1) se devuelve un listado de todos los archivos existentes en una lista, se elige el que se desea descargar: • “acción listado archivos” llama a “gestión listado archivos” que lee los archivos de la lista a devolver en la página de listado de archivos, según los parámetros recibidos por el formulario. • se puede cambiar los valores de los controles del formulario las veces que se considere necesarias, o dejarlo tal cual está. En cada cambio, se modificará la información en el bean de formulario de listado archivos que será leído por “gestión listado archivos”. (2) adjunto de un mensaje. 22 CDU 7. Alta usuario pantalla inicio acción login pantalla login pantalla datos usuario acción usuario bean form. usuario gestión usuarios crea login, email pantalla login ya existe representa usuario sesión • Actor: invitado. • Precondiciones: o acceder desde la pantalla de inicio. o no haberse logineado y no existir como ese usuario. • Postcondiciones: o usuario nuevo creado. o login automático. • Pasos: o un usuario que no existe no puede identificarse. Por ello desde la pantalla de login, permitiremos crear nuevo usuario. o “acción usuario” recibe un formulario cumplimentado por la página de datos del usuario. o “gestión usuarios” comprueba que no exista un usuario con el mismo login: • • • no existe: crea un objeto usuario y actualiza el objeto sesión con el login y email. Entonces “acción usuario” muestra “pantalla datos usuario” en modo de modificación. existe: “acción usuario” recibe el aviso del objeto de gestión y redirige a la página “pantalla login ya existe”, donde se informa de la incidencia y se ofrece volver a probar el alta con un login distinto o bien abandonar. Notas: con los campos login, password y email en blanco o con el campo email con un texto que no tenga formato de correo electrónico no se aceptará crear un nuevo usuario. CDU 8. Identificarse crea pantalla Inicio acción login pantalla login bean form. login acción check login pantalla login aceptado 23 login/no login pantalla login no aceptado usuarios gestión login login, password sesión • Actor: invitado8. • Precondiciones: o acceder desde la pantalla de inicio. o no haberse logineado y no existir como ese usuario. Postcondiciones: logineado, sesión actualizada • • Pasos: o introducimos login y password. o “acción check login” crea un objeto “gestión login” el cual busca en todos los usuarios uno que tenga dichos login y password, recibidos a través del bean de formulario. • • si existe: actualizamos el objeto sesión con login y password, retornamos a “acción check login” que nos devolverá la “pantalla de login aceptado”. si no existe: “acción check login” devolverá “pantalla login no aceptado”. CDU 9. Crear lista agregamos a pantalla listas fundadas usuario pantalla no se pueden crear mas listas acción crear lista bean form. lista lista gestión listas listas fundadas usuario mails disponibles sesión pantalla datos lista acción lista pantalla no se pudo crear lista • Actor: registrado. • Precondiciones: acceder desde la pantalla indicada. • Postcondiciones: nueva lista creada o bien aviso si no fue posible. • Pasos: o clicamos en el enlace “nueva lista” de la “pantalla listas fundadas usuario”. En la práctica, tanto en el caso de uso anterior “alta usuario” como en “identificarse” permitiré que el actor sea cualquiera, debido a que restaría fluidez en la defensa del proyecto iniciar y detener la aplicación cada vez que nos logineamos o si debemos crear algunos usuarios. 8 De seguir estos dos casos de uso al pie de la letra, desde la página de inicio el enlace a la página “login” no debería aparecer si ya estamos logineados, con lo cual sería imposible volver a identificarse o crear un usuario nuevo. Recordemos que las pruebas se realizan sobre una máquina y sesión. Por lo tanto, podremos crear varios usuarios y loginearnos como varios usuarios desde una misma sesión. 24 o llamado por “acción crear lista” el objeto “gestión listas” comprueba si hay el recursos disponible para crear una nueva lista: una cuenta de correo que ligaremos a la misma. • • o • existe: entramos en “pantalla datos lista” para rellenar el formulario. no existe: “pantalla no se pueden crear más listas” cumplimentado el formulario, lo devolvemos de nuevo a “gestión listas”: • éxito: tenemos una lista nueva • fracaso: “pantalla no se pudo crear lista” donde se ofrece volver a intentarlo ó abandonar el proceso. Notas: para que el formulario sea aceptado los campos “nombre lista” y “descripción lista” no pueden estar en blanco. CDU 10. Consulta datos usuario pantalla Inicio acción usuario bean form. usuario gestión representa usuarios pantalla listas estoy registrado pantalla listas fundadas usuario sesión pantalla datos usuario usuario • Actor: registrado. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: ninguna. • Pasos: o clicando en el enlace de cualquiera de esas páginas accederemos a la “pantalla datos usuario”. o “gestión usuarios” lee el login de “sesión” para saber qué usuario debe recuperar. o la información recibida por “acción usuario” se trasvasa en el bean de formulario para que sea leída por la pantalla. 25 CDU 11. Modificar datos usuario pantalla Inicio bean form. usuario pantalla listas estoy registrado registros usuario representa gestión usuarios email pantalla listas fundadas usuario pantalla datos usuario acción usuario sesión usuario • Actor: registrado. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: usuario y sesión modificados. • Pasos: o clicando en el enlace de cualquiera de esas páginas accederemos a la “pantalla datos usuario”. o efectuamos las modificaciones que consideremos oportuno. o “gestión usuarios” modifica el usuario y si es necesario cambia el email en “sesión” y en todos los “registros usuario” para que las listas conozcan la nueva dirección de correo. • Notas: o no se permite modificar el login. o en este CDU, el bean de formulario sirve tanto para mostrar en la pantalla el estado inicial y los cambios producidos, como para ser leído por la acción. CDU 12. Registrarse a lista representa pantalla datos usuario pantalla listas fundadas usuario pantalla listas donde estoy registrado acción listas estoy registrado gestión usuarios lista sesión • Actor: registrado. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: registrado a lista. 26 registros usuario • Pasos: o clicando en el enlace de cualquiera de esas páginas accederemos a la “pantalla listas donde estoy registrado”, que mostrará tanto las listas donde estamos registrados como las listas a las que podemos registrarnos. o “sesión” permite saber qué usuario accede a la página. o nos registramos en la lista con un clic. o seguimos en la misma pantalla, donde se muestra la actualización. CDU 13. Consulta listas registrado pantalla listas donde estoy registrado pantalla usuario representa acción listas estoy registrado gestión usuarios sesión lista registros usuario • Actor: registrado en lista. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: ninguna. • Pasos: o clicando en el enlace de cualquiera de esas páginas accederemos a la “pantalla listas donde estoy registrado”, que mostrará tanto las listas donde estamos registrados como las listas a las que podemos registrarnos. o “sesión” permite saber qué usuario accede a la página. CDU 14. Desregistrarse de lista Mismo diagrama que CDU 12. • Actor: registrado en lista. • Precondiciones: idem CDU 12. • Postcondiciones: desregistrado de lista. • Pasos: ídem CDU 12 pero desregistrándonos. 27 CDU 15. Recibir o no mails lista por correo Mismo diagrama que CDU 12. • Actor: registrado en lista. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: recibiremos o no mensajes de la lista por correo electrónico. • Pasos: o clicando en el enlace de cualquiera de esas páginas accederemos a la “pantalla listas donde estoy registrado”, que mostrará tanto las listas donde estamos registrados como las listas a las que podemos registrarnos. o “sesión” permite saber qué usuario accede a la página. o podemos marcar con un clic si deseamos recibir mensajes o no, de las listas en las que estemos registrados. o después de la operación seguimos en la misma página. CDU 16. Acceso mensajes de lista privadas Mismo diagrama que CDU 5. • Actor: registrado en lista. • Precondiciones: o acceder desde las pantallas señaladas. o seguir los pasos. • Postcondiciones: o acceso a los mensajes. o página de acceso denegado, en la que se avisa que no puede leerse ni escribir mensajes sin estar registrado en la lista o bien si estando registrado en ella hemos sido expulsados. • Pasos: los mismos que en el CDU 5. CDU 17. Acceso archivos de listas privadas Mismo diagrama que CDU 6. • Actor: registrado en lista. • Notas: idem CDU 6. 28 CDU 18. Enviar mensaje desde web pantalla listado mensajes acción nuevo mensaje pantalla nuevo mensaje bean form. mensaje pantalla mensaje sesión pantalla mensaje no enviado gestión nuevo mensaje pantalla mensaje enviado mensaje mensajes lista gestión smtp smtp • Actor: registrado en lista. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: mensaje guardado en la base de datos de la lista. • Pasos: o se puede crear un nuevo mensaje desde “pantalla listado mensajes” o responder a uno existente en “pantalla mensaje”. o se envía a “gestión smtp” que ejecutará el CDU 2 “reenvío mensaje”. o una vez creado el mensaje recibiremos una notificación si el mensaje se ha podido guardar o no. • Notas: o el campo de “asunto” y de “texto” no pueden estar en blanco. o la clase “smtp” representa un servidor smtp externo, que incorporamos en el diagrama para hacerlo más claro. Por lo tanto, no pertenece al proyecto y no se considerará clase de entidad del mismo. CDU 19. Enviar mensaje por correo Es una tarea externa a la aplicación. El usuario envía un mensaje con su cliente de correo que es ajeno a nuestro sistema. Luego, del mensaje recibido en la cuenta de correo se ocupará el actor “aplicación” en el caso de uso “bajar regularmente el correo”. CDU 20. Consultar listas creadas gestión usuarios pantalla usuario pantalla listas estoy registrado acción listas fundadas usuario pantalla listas fundadas usuario 29 sesión listas fundadas usuario • Actor: fundador lista. • Precondiciones: acceder desde cualquiera de las pantallas indicadas. • Postcondiciones: ninguna. • Pasos: un enlace nos lleva a “pantalla listas fundadas usuario”. • Notas: si el usuario no ha creado ninguna lista, aparecerá un aviso escrito en la misma pantalla de listas fundadas usuario. No se considera un error. CDU 21. Modificar datos lista Mismo diagrama que CDU 4. • Actor: fundador lista. • Notas: idem CDU 4. La diferencia entre CDU 4 y CDU 21 : o o o CDU 4: el usuario no creó la lista por lo que puede consultar los datos de la lista pero no modificarlos. CDU 21: el fundador puede modificar datos. los objetos y pantallas visitadas en ambos casos son los mismos. CDU 22. Expulsar usuario lista pantalla datos lista acción usuarios registrados en mi lista pantalla usuarios registrados en bean form. lista usuarios reg. en mi lista acción expulsar pantalla usuario expulsado bean form. expulsar gestión baneados gestión listas usuarios registros usuarios listas sesión baneados • Actor: fundador lista. • Precondiciones: acceder desde la pantalla indicada. • Postcondiciones: usuario expulsado. • Pasos (desde “pantalla usuarios registrado en lista” en adelante): o se clica sobre el usuario a expulsar de entre la lista de usuarios registrados. o “acción expulsar” devuelve un formulario en blanco donde indicar motivo de la expulsión, en “pantalla usuario expulsado”. o al reenviar el formulario a la acción, se crea “gestión baneados” que ejecuta la expulsión. 30 o • volvemos a la pantalla desde donde se expulsó. Notas: o la expulsión es propiamente desde la “pantalla usuarios registrados en lista”. En el diagrama aparece los objetos requeridos para crear esa pantalla, y desde dónde es llamada. En el CDU siguiente, no mostraremos estos preliminares. o en “pantalla usuarios registrados en lista” están presentes los usuarios expulsados y los no expulsados. CDU 23. Readmitir usuario lista pantalla usuarios registrados en lista acción expulsar sesión gestión baneados baneados • Actor: fundador lista. • Precondiciones: acceder desde la pantalla indicada. • Postcondiciones: usuario readmitido. • Pasos: o se clica sobre el usuario a readmitir. o “gestión baneados” rehabilita al usuario. o seguimos en la misma pantalla. • Notas: en “pantalla usuarios registrados en lista” están presentes los usuarios expulsados y los no expulsados. 2.5 RELACIÓN DE CLASES Con la suma de información de todos los apartados anteriores, estamos en disposición de perfilar las clases básicas que encontraremos en el proyecto. La información concentrada aquí servirá de punto de partida para el apartado de diseño. CLASES FRONTERA Corresponden a las futuras pantallas y en consecuencia forman parte de la interfaz de usuario. p_inicio, p_datos lista, p_listado mensajes, p_listado archivos, p_datos usuario, p_login, p_login ya existe, p_login aceptado, p_login no aceptado, p_listas fundadas usuario, p_nueva lista, p_no se pueden crear más listas, p_no se pudo crear lista, p_listas estoy registrado, p_usuarios registrados en lista, p_usuario expulsado, p_acceso denegado, p_mensaje, p_extiende, p_nuevo mensaje, p_mensaje enviado ,p_mensaje no enviado. 31 CLASES DE ACCIÓN Preceden a las páginas, porque invocan objetos de lógica de negocio y en función de resultados envían a una u otra página (ejemplo: login correcto, login incorrecto). a_inicio, a_crea lista, a_lista, a_acceso, a_mensaje, a_listado archivos, a_listado mensajes, a_extiende, a_usuario, a_login, a_check login, a_nuevo mensaje, a_ listas fundadas usuario, a_listas estoy registrado, a_usuarios registrados lista, a_expulsar. CLASES DE CONTROL Las clases de control son los intermediarios entre las clases frontera y las clases entidad. Suministrarán información en una u otra dirección según el caso. g_POP, g_SMTP, g_listas, g_acceso, g_listado mensajes, g_usuarios, g_login, g_nuevo mensaje, g_baneados. g_listado archivos, descarga archivo, CLASES DE ENTIDAD aplicación, usuario (invitado, registrado, registrado en lista, fundador lista), sesión, lista, mensajes lista, archivos lista, usuarios, baneados, mails disponibles, registros usuario, listas fundadas usuario, registros usuarios_listas. BEANS DE FORMULARIO Empaquetado de información que viaja entre los jsp y las clases-acción. Principalmente sus atributos son los valores de los controles de un formulario html. bf_lista, bf_listadoMails, bf_listadoArchivos, bf_usuarios registrados en mi lista. bf_mensaje, bf_usuario, bf_login, 32 bf_expulsar, 3. DISEÑO 3.1 RELACIÓN COMPLETA DE LAS CLASES DEL PROYECTO Enumeramos el total de clases creadas en el proyecto. En su mayoría a partir de las clases de especificaciones. En los subapartados posteriores se razonarán las decisiones que han llevado a prescindir de ciertas clases o a crear nuevas. A los nombres de las clases se les antecede el prefijo “My” cuando puede haber algún equívoco respecto a clases de otros paquetes. Clases-frontera [capa presentación]: p_inicio p_login p_login ya existe p_login aceptado p_login no aceptado p_acceso denegado p_listado mensajes p_listado archivos p_extiende p_mensaje p_mensaje enviado p_mensaje no enviado p_nuevo mensaje p_no se pueden crear más listas p_no se pudo crear lista p_datos usuario p_listas estoy registrado p_listas fundadas usuario p_nueva lista p_datos lista p_usuarios registrados en lista p_usuario expulsado ------------------------------------------------------------------------------------- index.jsp login.jsp loginYaExiste.jsp bienvenido.jsp login_error.jsp acceso.jsp listadoMails.jsp listadoAttachments.jsp extiende.jsp detalle.jsp mensajeEnviado.jsp mensajeNoEnviado.jsp nuevoMail.jsp nuevasListasNo.jsp nuevaListaError.jsp usuario.jsp usuario2.jsp usuario3.jsp lista.jsp lista.jsp lista2.jsp banear.jsp indice.jsp indiceDetalle.jsp mail.jsp Clases-acción [Struts]: a_inicio a_acceso a_login a_check login a_listado mensajes a_listado archivos a_mensaje a_extiende a_nuevo mensaje IndexJspAction.java AccesoAction.java EditLoginAction.java CheckLoginAction.java ListadoMailsAction.java ListadoAttachmentsAction.java DetalleAction.java ExtiendeAction.java NuevoMailAction.java 33 a_usuario a_listas estoy registrado UsuarioAction.java Usuario2Action.java a_ listas fundadas usuario a_crea lista a_lista a_usuarios registrados lista a_expulsar Usuario3Action.java CreaListaAction.java ListaAction.java Lista2Action.java BanearAction.java Beans de formulario [Struts]: bf_login LoginForm.java bf_expulsar bf_mensaje bf_lista bf_usuarios reg. en mi lista bf_listadoMails bf_listadoArchivos bf_mensaje bf_usuario BanearForm.java DetalleForm.java ListaForm.java Lista2Form.java ListadoMailsForm.java ListadoMailsForm.java NuevoMailForm.java UsuarioForm.java Clases-control [capa negocio]: g_POP g_SMTP g_listas g_listado mensajes g_listado archivos g_login g_usuarios g_baneados g_acceso ThreadPop.java ThreadSmtp.java GestionListas.java ListadoMails.java ListadoAttachments.java MyLogin.java GestionUsuarios.java GestionBaneados.java GestionAcceso.java g_nuevoMensaje descarga archivo --------------------------------------------------------- NuevoMail.java 9 FileDownload.jsp Listado.java PatternsPool.java --------------------------------------------------------- PatternsReply.java ThreadReplies.java Clases-entidad [capa negocio]: lista distribucion sesión mensaje aplicación archivo ---------------------------------------------------------------------------- 9 ListaDistribucion.java MySession.java MyMessage.java MyApplication.java Attachment.java ColaSincronizada.java ComunASmtp.java ComunPopReplies.java FilaListado.java MiLinkedList.java Realizaremos la descarga a través de un JSP, que obviamente no es una clase de control. 34 3.2 INTERFAZ GRÁFICA En un web-site de listas de distribución el visitante, quizás con la intención de leer varios mensajes, demanda un servicio ágil. Ello implica que el peso de las páginas debe ser mínimo, sin efectos gráficos o animaciones superfluas que ralenticen el proceso. Al mismo tiempo, la navegación entre las páginas debe ser intuitiva, o por lo menos de fácil aprendizaje, y respaldado en todo momento por una “ayuda”. En la navegación, se ha tenido en cuenta que en toda página se encuentre un enlace a la página de inicio o la anterior, por lo que desde cualquier lugar la página de inicio es accesible en un click o a lo sumo dos. Y siempre hay una ayuda on-line disponible. Enlaces que dirijan a páginas relacionadas entre sí son agrupados en un menú de pestañas (o tabs). Se han aplicado hojas de estilos CSS en los enlaces, aspecto visitado y activo, en las tablas, en los encabezados y pies de los listados y en los tabs de ciertas páginas. Un futuro cambio de estilo comportaría alterar un solo archivo. Empleo de JavaScript para cambiar el aspecto dinámico de los controles de los formularios, activándolos o desactivándolos en función de las acciones del usuario y para verificar celdas no vacías o formatos correctos como por ejemplo no introducir texto si se espera un número o una fecha, avisando al usuario del error. Con el uso del marco de trabajo Struts, conseguimos una eficaz separación entre la presentación, reservada para los JSP, y los objetos de negocio, que representan el motor de la aplicación. Las páginas incluirán etiquetas Struts, con un formato muy parecido al de las etiquetas HTML destinados a recuperar datos recibidos directamente de una clase-acción o de forma indirecta a través de un bean de formulario. Las ventajas son muchas: • • • • • • liberamos los JSP de molestos scriptlets, porciones de código, que dificultan la lectura y el mantenimiento. un cambio en la presentación no implica forzosamente un cambio en la lógica del negocio y viceversa. en proyectos grandes, pueden formarse dos equipos: uno de diseñadores de páginas web, y otro de programadores, sin que el trabajo de unos interfiera en el de los otros. el borrado accidental de parte de un JSP no echa al traste un proyecto, sólo afecta a ese JSP. es más fácil ampliar y rediseñar la aplicación web. a través del archivo struts-config.xml es más fácil de seguir el esquema de la web. Según este modelo, la única lógica aceptable en un JSP es de presentación, etiquetas que muestran un elemento HTML ( un encabezado, un enlace ... ) en función de si se han recuperado o no datos. Las páginas de error son simples, e incluso la misma página JSP que lanza el error podría mostrar el mensaje. De nuevo , es mucho más útil que se ocupe Struts ya que evitamos introducir lógica en la página de la que se puede ocupar la clase Action, redirigiéndonos a una u otra página de error. Internamente, la gestión de “altas, consultas y modificaciones” sobre un mismo elemento se llevan a cabo por un único JSP, ya que los objetos visuales que se muestran son los mismos. Así un rediseño implicaría modificar una sola página y no tres. Han sido tomadas unas medidas elementales de seguridad destinadas a evitar que un intruso entre en páginas ajenas reescribiendo la dirección de una página junto con parámetros en la barra de URL del navegador. Es imposible saltar el control de acceso, o ejercer de fundador de una lista sin serlo, 35 entre otras tareas, porque aunque algunos datos se pasen por queryString o post no son suficientes sin la información contenida en los objetos de negocio. Diagrama de navegación entre los JSP: login.jsp bienvenido.jsp loginYaExiste.jsp login_error.jsp usuario.jsp lista.jsp nuevaListaError.jsp index.jsp usuario3.jsp lista2.jsp usuario2.jsp acceso.jsp nuevasListasNo.jsp banear.jsp listadoMails.jsp nuevoMail.jsp mensajeEnviado.jsp mensjeNoEnviado.jsp listadoAttachments.jsp detalle.jsp extiende.jsp fileDownload.jsp vuelven a detalle.jsp, o listadoMails.jsp o listadoAttachments.jsp. Respecto a los JSP que deducimos gracias a las clases-frontera, existen tres nuevas páginas JSP: • indice.jsp o muestra los enlaces anterior, siguiente y tantos enlaces como páginas se divida el resultado de listadoMails.jsp. o incluida en listadoMails.jsp dos ocasiones, antes y después del listado de mensajes. 36 • • mail.jsp o cuerpo de un mensaje. o incluido una vez en detalle.jsp y en extiende.jsp, que actúa de contenedor de varios “mail.jsp” indiceDetalle.jsp o enlaces para navegar entre mensajes, llamado antes y después de mostrar un mensaje. Estas páginas auxiliares evitan repetir código innecesariamente, facilitando así el mantenimiento y la legibilidad. En cuanto al aspecto final de la interfaz, véase las capturas de pantalla en el manual de usuario. 37 3.3 STRUTS El uso de Struts tiene como precio la multitud de clases de acción y beans de formulario adicionales que han de ser implementadas. Suele haber una correspondencia entre los nombres de los jsp, y el de las acciones que los preceden. No coinciden en número, dado que una clase-acción en ocasiones redirige a más de una página en función de los resultados. Del mismo modo que las altas, consultas y modificaciones sobre una misma entidad se realizan desde una sola página JSP, la clase de acción también es única para las tres operaciones. Los beans de formulario se encuentran detrás de los formularios HTML. La información entre los dos es bidireccional: de la página al bean y del bean a la página, según la necesidad. Tanto ListadoMailsAction como ListadoAttachmentsAction emplean el mismo bean puesto que los dos formularios HTML de sus páginas listadoMails.jsp y listadoAttachments.jsp son casi idénticos. ListaForm y Lista2Form agrupan información para sus respectivas páginas, lista.jsp y lista2.jsp, pese a que éstas no incluyen formulario. En este caso, representan una manera compacta de enviar a las páginas información heterogenea en un solo objeto request en lugar de en varios. En un esquema clásico de Struts el tratamiento de un formulario HTML se desdobla en dos Action: edit_Action y save_Action. El primero dirige a la página JSP y el segundo crea un formulario para recoger los datos a procesar de la página. Así no se crea un bean formulario en edit_Action que no se vaya a utilizar. En el CDU “identificarse” se ha procedido de esta forma, editLoginAction.java y checkLoginAction.java. con dos Actions llamados Sin embargo, en el resto de los CDU optamos por un único action que lleve a cabo todo el proceso. Es cierto que se crea dos veces el bean de formulario. Pero también es cierto que la primera vez que se crea, no se encuentra “en blanco” (salvo en los campos que debe editar el usuario, por supuesto) sino que incorpora información a mostrar en la página. Dentro de Struts, merece especial atención el connection_pooling. Accesos repetidos de un modo convencional a una base de datos por parte de varios usuarios exigirían abrir y cerrar multitud de conexiones. Una conexión a una base de datos consume mucho tiempo, como mínimo alrededor de un segundo, si la base está en local. El pool de conexiones se ideó para reducir estos tiempos de espera. Consiste en un objeto encargado de mantener en memoria una colección de conexiones en lugar de ir creándolas y destruyéndolas. Por lo tanto el consumo de segundos que supone cada conexión se da tan solo en el arranque de la aplicación, ya que en el resto de ocasiones devolvemos una referencia, la cual es instántanea. Cuando un objeto ya no requiere de la conexión, la “devuelve” al pool, permitiendo que el mismo objeto u otro puedan volverla a utilizar más adelante. Struts nos proporciona este connection-pool. Nosotros únicamente debemos especificar cuál es la fuente de datos y rellenar algunos parámetros como el nombre de usuario, el password, el máximo de conexiones simultáneas, el máximo de conexiones en memoria, etcétera. A continuación, mostramos una entrada en el archivo struts-config.xml: 38 <data-source type="org.apache.commons.dbcp.BasicDataSource"> <set-property value="MySQL" property="description" /> <set-propertyvalue="com.mysql.jdbc.Driver" property="driverClassName" /> <set-propertyvalue="jdbc:mysql://localhost/pfcweb_sistema" property="url" /> <set-property value="root" property="username" /> <set-property value="" property="password" /> <set-property value="100" property="maxCount" /> <!-- max conexiones en Pool --> <set-property value="5" property="minCount" /> <!-- min conex. --> <set-property value="75" property="maxActive" /> <!-- max conex. simultaneas --> <set-property value="5000" property="maxWait" /> <set-property value="true" property="defaultAutoCommit" /> <!-- auto commit está por defecto a false --> <set-property value="false" property="defaultReadOnly" /> </data-source> Los propiedades especificadas de las conexiones son a modo de ejemplo. Según el número de visitas a la web y la potencia del ordenador se deberían aumentar o disminuir. Todos los objetos de negocio que requieran de un acceso a una base de datos lo harán con las conexiones suministradas por los Action. Garantizamos así un uso rápido y eficiente de las conexiones, y un control centralizado de sus propiedades. Este es un motivo más por el que MyApplication es creado desde IndexJspAction y no de forma independiente. Hubiera podido escogerse la inicialización a través de web.xml, creando una entrada de este tipo: <servlet> <servlet-name>miApp</servlet-name> <servlet-class>pfc_web_struts.MyApplication</servlet-class> <load-on-startup>3</load-on-startup> </servlet> Después hubiera podido llamarlo desde cualquier Action con: request.getSession().getServletContext().getAttribute("miApp"); No obstante, al hallarnos fuera de Struts, no tendríamos ocasión de acceder al pool de conexiones. Debería abrir conexiones por su propia cuenta para todas las cases creadas por él. Los objetos de negocio, en su mayoría de ámbito “página”, disponen de un método setConexion() con el cual recibir la conexión, si deben acceder a la base de datos. MyApplication en lugar de obtener una conexión, obtiene un objeto DataSource, con el cual demandar las conexiones que necesite para los objetos que crea. Struts también nos ofrece una clase llamada FileForm que nos permite subir archivos desde el ordenador local donde el usuario crea el mensaje al servidor donde se encuentra nuestra aplicación. 39 3.4 LÓGICA DE NEGOCIO De entre las clases-entidad, se ha desestimado su paso a clases del proyecto a todas aquellas que al no emplearse de forma continuada o debido a su extenso tamaño se recuperan directamente de la base de datos sin mantenerlas en memoria: • usuario (invitado, registrado, registrado en lista, fundador lista) la diferencia entre los diversos tipos de usuario, se reduce tan sólo a un atributo. Por lo cual trataríamos con una clase usuario. Pero a nivel interno sólo empleamos de forma habitual su login y e-mail, que guardamos en un objeto sesión. Por ello no creamos una clase usuario, ni la mantenemos entera en memoria. Para el acceso a los datos de un usuario ya tenemos el usuarioForm. Entre los objetos del negocio no se trabaja con clases usuario, aunque podría crearse en el futuro si fuera necesario. • mensajes lista: es inviable mantener en memoria un objeto por cada lista que contenga la totalidad o parte de los mensajes de una lista. • archivos lista: idem anterior. • baneados: mantendremos el login de los usuarios baneados dentro de cada lista. • mails disponibles, registros usuario, listas fundadas usuario, registros usuarios_listas: se emplean ocasionalmente. Salvo la instancia de MyApplication cuya vida alcanza a la de la aplicación y las de MySession que se mantienen mientras sus respectivos usuarios esté conectados a la web, y al margen de los objetos creados por ellos que mantienen la misma duración, el resto de objetos de esta capa son de ámbito request, esto es, se crean en el momento que una clase Action las solicita para atender una petición de una JSP y acto seguido se “destruyen”, o mejor dicho, quedan a disposición del Garbage Collector. 40 DIAGRAMA ESTÁTICO DE CLASES En el diagrama no establecemos la totalidad de dependencias con clases externas por no restar legibilidad al gráfico. 1 MyApplication java.lang 1 javax.mail Thread JAVAMAIL n 1 NuevoMail 1 1 ListaDistribucion ThreadPop guarda GestionBaneados 1 crea ThreadReplies ThreadSmtp 1 MyMesssage crea ColaSincronizada GestionUsuarios 1 1 0..n GestionAcceso PatternsPool Attachment 1 PatternsReply 1 GestionListas 1 Listado ListadoMails FilaListado ListadoAttachments 1 1 1 MyLogin n 1 MySession java.util MyLinkedList LinkedList 41 ComunPopReplies ComunASmtp INTERNET Paso a detallar las clases participantes por orden alfabético, junto con sus métodos principales y una síntesis de su funcionalidad: Attachment.java Representa las propiedades mínimas necesitadas por un archivo: nombre, directorio y tamaño. También es el encargado de guardarlo en disco dentro de la carpeta de la lista, si es uploaded por un usuario. En caso de haber en el directorio por defecto, especificado en el registro PathAttachments de la tabla sistema otro archivo de idéntico nombre, se crea una subcarpeta. Métodos: • getters para obtener las propiedades del archivo. • public boolean guardaEnDisco(String is) nombreLista,java.io.InputStream ColaSincronizada.java Implementación de una cola con los métodos indispensables. Participan en ella un productor que deja objetos y un consumidor que los recoge. Evita que a un mismo tiempo ambos tomen el mismo objeto. También bloquea al consumidor cuando no hay más objetos disponibles. Métodos: • • • synchronized void add (Object o), encola. synchronized Object get (), desencola. int size (), tamaño de la cola. ComunASmtp.java En esencia es una cola donde ThreadPop y NuevoMail depositen los mensajes para que ThreadSmtp los reenvíe en cuanto pueda. Métodos: • • ColaSincronizada getCola(). Session getSession (), empleado por el marco de trabajo JavaMail: cuando creamos un mensaje para ser enviado desde ThreadPop o NuevoMail una de las propiedades solicitadas es la sesión, que son las propiedades de sistema y el tipo de servidor smtp utilizado, y que toman de este método. 42 ComunPopReplies.java Se ha reducido a una cola donde ThreadPop envía mensajes a ThreadReplies. Métodos: • ColaSincronizada getCola(). Tanto ComunASmtp como ComunPopReplies son actualmente simples contenedores de una cola. No obstante, como representan la intermediación entre dos o más clases, y ya en desarrollos pasados tuvieron más elementos quizás en el futuro podrían repetirse, lo que desaconseja desde las clases usuarias de ComunASmtp y ComunPopReplies crear una ColaSincronizada directamente. FilaListado.java Encargado de crear las filas que aparecen en los listados de mensajes o archivos. Métodos: • • las propiedades se pasan por el constructor. contiene varios getters para acceder independientemente a las distintas propiedades de la fila: si un mensaje tiene adjunto, el nombre del remitente, el tamaño, la fecha, etcétera. GestionAcceso.java Control de usuarios sobre el acceso a listados. Comprueba si está registrado en la lista, de ser ésta privada, o si está baneado, ofrece la fecha y el motivo de expulsión. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public void setListaDistribucion(ListaDistribucion ld), lista distribución sobre la que deseamos comprobar si el usuario tiene acceso. • public boolean estaRegistradoEnLista(String login) • public boolean estaBaneado(String login) • public String getMotivo(), motivo por el cual fue expulsado. • public String getFecha(), fecha de la expulsión. Internamente crea un GestionBaneados para la respuesta de los tres últimos métodos, que son mostrados en la página de acceso, de ser rechazado. 43 GestionBaneados.java Expulsiones y readmisiones de usuarios. Un usuario expulsado no es desregistrado, ya que si es nuevamente readmitido tendría que volver a introducir los datos de alta. Además, podría interesar guardar sus datos con varios fines estadísticos y comerciales. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public void setListaDistribucion(ListaDistribucion ld) • public boolean altaBaneado(String miLogin, String login, String email, GregorianCalendar cl, String motivo), expulsión. • public boolean bajaBaneado(String miLogin, String login, String email), readmisión. • public boolean modifBaneado(String miLogin, String login, String motivo) • public String getFecha() • public String getMotivo() Desde la página web no es posible la opción de alta o baja de expulsiones para un no fundador, porque los enlaces que lo permiten se ocultan. Aun así, un usuario bien informado con aviesas intenciones podría introducir en la barra de url del navegador: http://localhost:8080/cuberes/banear.do?login=nuria&idLista=2&accion=alta&return=inde x.do Y expulsar al usuario. Por ello, a ciertos métodos restringidos les pasamos un parámetro adicional, “miLogin”, correspondiente al login guardado en MySession cuando el usuario se identifica, y al que no tiene acceso. Los métodos contrastarán el login con el del fundador de la lista, que obtenemos de ListaDistribucion, antes de realizar alguna operación como alta o baja. En bajaBaneado se requiere el email del expulsado con el fin que ListaDistribucion lo retire de la lista de direcciones de las que acepta recibir mensajes. GestionListas.java Creamos con ella nuevas listas o modificamos existentes. Ofrece los usuarios registrados y los expulsados de una lista. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. 44 • public ListaDistribucion getListaDistribucion (int idLista), devuelve una referencia con la que podemos consultar las propiedades de la lista. • public int altaLista(String nombre, String descrip, String comentario, String fundador, GregorianCalendar fecha, boolean esListaPublica) o consulta en la tabla emailsDisponibles donde se encuentra toda la información sobre una cuenta de correo libre que asociaremos a la nueva lista. o agregamos un nuevo registro en tabla Listas con los datos pasados por parámetro y otro en tabla registros donde el fundador consta como primer usuario de la lista, y puede así pasar el control de acceso y enviar mensajes. o si todo correcto, se guarda la transacción y se crea un objeto ListaDistribucion que añadimos al hash de MyApplication. • public Collection getUsuariosExpulsados(int idLista) • public Collection getUsuariosRegistrados(int idLista) • public void modifLista(int id, String nombre, String descrip, String comentario, boolean esListaPublica) • public boolean puedenCrearseMasListas(), en caso de no encontrar una cuenta de correo libre, devuelve false. GestionUsuarios.java Altas, consultas y modificaciones de usuarios, así como conocer las listas en las que se está o no registrado, y las listas de las que se es fundador. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public void setMySession(MySession bs), requerido antes de emplear ningún método: si realizamos una alta debemos actualizar el MySession (login, email), si modificamos el email personal también. En los demás métodos, se emplea para recuperar el login, en lugar de recibirlo por parámetro . • public setLogin(String login), permite consultar los métodos siguientes para un usuario que no sea el de la sesión. Por ejemplo un administrador podría consultar las listas creadas de un usuario. • public boolean altaUsuario(String nombre, String apellido1, String apellido2, String email, String password) • public void altaEnLista (int idLista) • public void bajaDeLista (int idLista), desregistrarse de una lista. • public String [] getDatosUsuario() 45 • public Collection getListasCreadas(), listas de las que soy fundador. • public Collection getListasNoApuntado() • public Collection getListasUsuario() • public boolean modifUsuario(String nombre, String apellido1, String apellido2, String email, String password) • public void recibirCorreo(int idLista, String opcion), si se desea recibir o no correo de una lista a la que estamos registrados. ListaDistribucion.java Mantenemos información relativa a una lista de distribución que conviene encontrar en memoria porque es solicitada de manera continua. Sin ir más lejos, desde la misma página de inicio, aparte de otras páginas. No se incluyen el conjunto total o parcial de mensajes o archivos porque su número será muy alto. Sí, en cambio, el nombre de la lista, las direcciones de correo electrónico de los usuarios que desean enviar o recibir correo externamente a la página, la fecha del último mensaje , la descripción de la lista, el fundador, entre otros. Una vez creado, lanza ThreadPop, ComunPopReplies y ThreadReplies. Estos objetos permanecen por todo el tiempo de vida de la aplicación en memoria junto a ListaDistribucion, ya que su uso continuo desaconseja crearlos y destruirlos en tiempo de ejecución. Cada lista de distribución tiene sus propios threads, es más eficiente y lógico que si los dos threads fueran compartidos por todas las listas. Así cada thread descarga los mensajes de su cuenta y concurrentemente varios threads pueden estar bajando al mismo tiempo mensajes. Si no, una lista no actualizaría sus mensajes hasta que no acabara el thread de atender la anterior lista, con lo que se desaprovecharía el ancho de banda de la red. Si nos preocupa el espacio en memoria, recordemos que diversas instancias de un thread no significa cargar “entero” ese objeto ese número de veces, sino que el código se carga una vez, y cada hilo mantiene su estado. Métodos: • constructor recibe la conexión y las propiedades de la lista antes comentadas. • getters para obtener las propiedades anteriores. • setters para actualizar las propiedades anteriores, y tambien el número de usuarios y de mensajes. • public void addEmailUsuario(String login,String email), agregamos una dirección de correo a la cual permitiremos enviar o recibir correo. • public boolean estaEnEmailsUsuario(String email) • public Address [] getAdressesDestinatarios(String from), devuelve en un vector de Address (JavaMail) los emails de todos los receptores de la lista menos la del remitente. 46 • synchronized public boolean guardaEnBD(MyMessage msg), guardamos el mensaje en la base de datos. Es un método sincronizado porque puede ser llamado por varios usuarios simultaneamente cuando crean mensajes, incluido el ThreadPop. Centralizamos pues, la grabación de mensajes, en lugar de hacerlo por separado desde ThreadPop y NuevoMail. Listado.java Listado es una clase abstracta, base de ListadoMails y ListadoAttachments para la creación de listados conforme a diversos criterios de ordenación y filtrado. Desde ambas clases se pueden recuperar mensajes, por lo que todos los métodos comunes han sido concentrados aquí. También muestra los mensajes, que pueden ser consultados desde un listado u otro. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public int getAnteriorMail(), getSiguienteMail(), para navegar entre mensajes, nos devuelve el mail contiguo al que estamos posicionados actualmente, ó –1 , si no existe. • setters para establecer diversas consultas: por fechas, por un campo, en un determinado orden, el número de página a visualizar, etcétera. • getters para recuperar los valores antes establecidos. • public MyMessage [] getTotalMailsHilo( int id), aquél cuyo id pasamos por parámetro. • public FilaListado[] print(), devuelve una lista con los valores que responden a la consulta realizada tras aplicar los diversos setters. Este método debe ser redefinido en las subclases. • public void setHilo(boolean on), indicamos si el listado que recorremos internamente pertenece a un hilo de discusión. El parámetro “on” se pone a true, si desde dentro de un mensaje mostramos el listado de mensajes relacionados. • protected String cadenaSQLWhere(), crea el Where del sql, según los setters. • protected String cadenaSQL(int pag, int lim, String where), crea la sentencia completa sql, con la cadena where creada con el método anterior. Además se le indica el número de página a listar, y lim se emplea para recibir la página completa o bien el primer o último elemento de la página. La opción de página completa se emplea con print() mientras que las otros dos son de uso interno, para conocer el mensaje anterior a uno que sea primero de una lista, o el siguiente al que sea último. mensajes relacionados con Este método debe ser redefinido en las subclases. • protected FilaListado creaFila(ResultSet GregorianCalendar hoy), llamado desde print(). 47 rs, Connection conn, • public MyMessage getMail(int id), obtención de un mensaje. El compilador no me permite crear los métodos print() y cadenaSQL() como abstractos, y como private no serían visibles por los descendientes. Los declaro protected, aunque a efectos de visibilidad una clase protegida es visible para todas las clases de un mismo paquete. Las clases herederas implementan los criterios mínimos que puedan diferenciar el acceso al listado de mails y al listado de attachments. Internamente Listado conserva un vector con los id de los mensajes del listado que se muestre por pantalla. No todos los mensajes, sino los n registros que forman la paginación. Supongamos que no existiera el vector. Un usuario cuando entra en la página de listado de mensajes lo hace para consultar mensajes nuevos. Suponemos que un usuario por término medio leerá varios mensajes de una lista por sesión. Para cada mensaje leído, es necesario conocer el mensaje que le antecede y el que le sigue, ya que para navegar con los enlaces “anterior” y “siguiente” que se muestran en la página del mensaje se requieren estos valores. Sin vector, una solución plausible sería que para cada mensaje consultáramos la base de datos pidiendo el listado de los n registros de la página, recorriéramos el ResultSet, deteniéndonos en el id buscado, con la precaución de guardar en cada iteración el id anterior. Entonces al llegar tendríamos id anterior, id y moviéndonos una posición el id posterior. Bien. Ello obliga a solicitar un listado de nuevo para cada mensaje: • primer mensaje leído: tengo id anterior, id mensaje, id posterior. • segundo mensaje leído: id anterior es el id del primer mensaje, pero debo volver a solicitar un listado de n registros a la base de, ya que el listado no tiene por qué coincidir con la posición de los registros en la tabla. Deberemos volver a recorrerlo para encontra el id del segundo mensaje y así el id posterior. • igual para el resto de mensajes. Además de solicitar un conjunto de registros innecesario en cada ocasión, la consulta debería ser con los parámetros solicitados por el usuario. Gracias al vector, actualizado sólo cuando el usuario entra en el listado de mensajes o de archivos o cuando cambia de página para leer otros n registros, mediante un id conocemos el id de los mensajes contiguos. Y no necesitamos ir reconsultando a la base de datos más que para solicitar el mensaje actual. Mantenemos un cursor que apunte al id del mail actual, por lo que tampoco recorremos en busca del id del mensaje, salvo en la primera vez. La diferencia es recibir un único registro de un mensaje mediante una consulta SQL sencilla tipo SELECT * FROM mensajes WHERE id=x o bien recibir 30 ó 40 que responderán a una consulta que implicará un WHERE de varios parámetros y ordenado. En su conjunto, necesitaría más tiempo. Prevalece asimismo la idea de leer en memoria antes que en disco duro, teniendo en cuenta que serán muchos los usuarios que se encuentren consultando el correo en la aplicación. Para que el vector persista en memoria a lo largo de la visita por varios listados paginados o mensajes, la clase ha de tener un ámbito de sesión, cosa que hacemos guardando el Listado (sea de mensajes , sea de archivos) en el MySession. 48 ListadoAttachments.java Descendiente de Listado para listados de archivos. • public FilaListado[] print(), redefinición del método de Listado. Mostramos listado de archivos. • protected String cadenaSQL(int pag, int lim, String where), redefinición del método de Listado. ListadoMails.java Descendiente de Listado para listados de mensajes. Métodos: característica propia de los listados de • public boolean isListaRelacionada(), mensajes, por lo que se define en esta clase. • public void setListaRelacionada(boolean var) • public FilaListado[] print(), redefinición del método de Listado. Mostramos listado de mensajes. • protected String cadenaSQL(int pag, int lim, String where), redefinición del método de Listado. MyApplication.java Inicializa la aplicación y mantiene en memoria los objetos que son necesarios constantemente desde el arranque del sistema. Al iniciarse: • el constructor recibe el DataSource de Struts. • lee los valores de inicialización generales a todo el proyecto de la tabla de sistema que se pasarán por parámetro a los MySession, o a los objetos ListaDistribución: el número de registros por página que se mostrará en los listados, el tamaño máximo autorizado de un adjunto, el directorio donde se guardan los adjuntos, y el número de segundos que el ThreadPop está dormido tras bajar el correo. • crea un ThreadSmtp y un ComunASmtp. • recupera de la base de datos las propiedades del total de listas de distribución existentes. Para cada lista se crea un objeto ListaDistribucion que se guarda junto a las demás en un hash accesible por el idLista. Métodos: • getters para la obtención de los valores de la tabla sistema. 49 getComunASmtp(), acceso a la cola de mensajes salientes hacia • public ComunASmtp ThreadSmtp. • public int getNumListas(), total de listas existentes. • public Collection • public ListaDistribucion getLista(int idLista) , obtención de una lista en particular. • public void addLista(int id,ListaDistribucion ld), añade una nueva lista al hash. • synchronized public Connection creaConexion(), recupera conexiones del DataSource para los objetos creados por MyApplication que lo necesiten. getListasExistentes() , contenido del hash. MyLinkedList.java Wrapper necesario para acceder al tamaño de la lista enlazada desde Struts. LinkedList dispone de la propiedad “size()”. Los tags Struts que leen propiedades sólo reconocen métodos que comiencen por “get”. Método: • public int getSize() MyLogin.java Identificación de usuarios. • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public String log_in(String login, String passw) MyMessage.java JavaMail cuenta con una clase Message, que es abstracta. Crear una clase descendiente de ella representa definir métodos que no me interesan. La clase MimeMessage no es apropiada para trabajar con la base de datos, pues muchos propiedades que utilizo no existen y si representa a un mensaje POP3 entrante entonces es de sólo lectura. Heredar de MimeMessage representaría crear una clase cargada de extras que no necesito. En definitiva, opto por definir mi propia clase de mensaje, javaMail.Message para enviar o recibir de Internet. 50 para trabajar internamente y Métodos: • setters y getters para acceso a las propiedades habituales de un mensaje. • public LinkedList getAttachments(), lista de adjuntos del mensaje. • void addAttachment(Attachment a), añadimos un adjunto. • public String getMessage_ID(), identificador en Internet de un mensaje. • boolean esHTML(), si el correo entrante es HTML. • boolean esReplyPte(), los mensajes entrantes que responden a otro y que deberá ser tratado por ThreadReplies. • String getReferences(), devuelve Message_ID del mensaje al que replica, o nulo. • void setReferences(String s) • void setMessage_ID(String s) MySession.java Ofrece los objetos de listado, el login y email de una sesion/usuario y los mantiene en memoria, pues requieren de una vida más allá de los ámbitos page o request, al tener que mantenerse a lo largo de varias páginas. Tanto ListadoMails como ListadoAttachments responden a unos parámetros que el usuario ha introducido a través de un formulario y devuelven un listado. Ya hemos hablado de la conveniencia del vector de id de mensajes. Otro argumento a favor de mantener en sesión estas clases es que deseamos que el estado de la consulta, reflejado en los controles del mismo formulario, se mantenga tal como lo dejó el usuario hasta que él decida cambiar de nuevo las condiciones de búsqueda. Una posibilidad de mantener intacto el estado sería pasar las propiedades por queryString o por form entre las páginas JSP. De todas formas, el carro de atributos tendría que moverse no sólo por las páginas de listados, sino también por todas las que a través de ellas se puede enlazar, como las de nuevo mensaje, lectura de mensaje, la de extender mensaje, y las que se pudieran crear nuevas con el tiempo, de modo que al volver a la página la encontráramos igual. Este carro iría agrandándose con los atributos propios de cada págia visitada, lo cual sería cada vez más confuso. La clase MySession nace con la intención de agrupar todos los objetos sesión, en lugar de dejarlos por separado. Métodos: • public void setConexion(Connection conn), recibe la conexión del Action que lo ha creado. • public ListadoAttachments getListadoAttachments(), si existe, devuelve un objeto ListadoAttachments, si no existe, lo crea. • public ListadoMails getListadoMails(), idem anterior. 51 setters y getters para login y email. • Los objetos ListadoMails o ListadoAttachments se crean cuando se necesitan, o sea, si entramos en el listado de mensajes o el de archivos respectivamente. Una vez creados se mantienen hasta que el usuario cierre o pierda la conexión. NuevoMail.java Encargado de guardar un mensaje redactado desde la web de la aplicación, y de completarlo con todos los datos necesarios para que pueda ser reenviado a los usuarios registrados que deseen recibir correo. • public boolean enviaMail(String login, String emailUser, String subject,Attachment attach, String content, String message_ID) , internamente crea un mensaje de texto plano de una sola parte, o dos si incluye un adjunto. Llama a ListadoDistribucion para que guarde el mensaje y para conocer los que han de ser los destinatarios y lo enviamos a la cola de ThreadSmtp y de ComunPopReplies. • public String textoBR (String content), reemplazamos los saltos de carro del textarea del formulario por etiquetas <BR> para que se visualice correctamente en la web. • public String textoReply (String contentMailReplyado), si nuestro mensaje es respuesta de otro, introducimos signos “>” que precedan cada línea del texto del mensaje original. PatternsPool.java Contiene expresiones regulares explotadas principalmente por el ThreadPop. En cada mensaje entrante se aplica una serie de patrones de búsqueda con el fin de reconocer algunas de sus propiedades, y de reemplazar algunos caracteres que no pueden guardarse tal cual en la base de datos. Ya que los emplearemos continuamente, nos conviene compilarlos y tenerlos en memoria, en lugar de crearlos y destruirlos, pese a que son muy simples. Por ello reunimos todos los patrones compilados en PatternsPool. contenido o el asunto del mensaje. La mayoría actúan sobre el Métodos: • String comillasDobles(String str, boolean eliminar), trata comillas dobles para que puedan ser entendidas por MySQL. Parámetro eliminar a true si deseamos eliminarlas. • boolean esMIMEmultipart(String str), reconocer si un mensaje es multiparte. • boolean esMIMEtextPlain(String str) , si es un mensaje de texto simple. • boolean esMIMEtext(String str) • String retornosCarro(String str), cambia retornos de carro por etiquetas “<BR>” 52 PatternsReply.java Similar a PatternsPool, pero éste es empleado sólo por ThreadPop, mientras que encontraremos instancias de PatternsReply en ThreadPop, ThreadReplies y NuevoMail. Métodos: • public boolean esReply(String str), revisa el comienzo del asunto del mensaje en busca de un “Re”. • public int numeroReply(), para Re[n], devuelve n. Si no, devuelve cero. • public String quitaPrimerRe(String str), extrae el primer “Re” que encuentra en la cadena de entrada. ThreadPop.java Responsable de bajar los mensajes de correo electrónico de la cuenta periódicamente, procesarlos, grabarlos en disco y enviarlos a ThreadReplies, para que busque posibles relaciones con otros mensajes y a ThreadSmtp para el reenvío a usuarios. Métodos: • public ThreadPop (ListaDistribucion listaD, ComunPopReplies comun, ComunASmtp comunS, String pathBase, int maxSizeAttach, int segsDuermePop), constructor. o recibe referencias a las colas de ThreadSmtp y ThreadReplies, el directorio donde situar los adjuntos, el máximo tamaño aceptable para estos, y los segundos que permanecerá dormido antes de volver a consultar la cuenta. o hace las asignaciones correspondientes de los parámetros con las variables del objeto. o crea un PatternsTool y un PatternsReply. • public void run(), método que debe implementar cualquier heredero de la clase Thread. En un bucle continuo seguimos estos pasos: o creación de los objetos de JavaMail y conexión a correo. o para cada mensaje entrante: comprobamos que el remitente puede enviar mensajes a la lista (si está registrado y no expulsado). Si no, el mensaje se drena y se pasa al siguiente, enviando antes un correo de aviso al remitente. creación de objeto MyMessage. trasvase de MimeMessage a MyMessage. tratamos el mensaje para que pueda ser grabado en la base de datos. encolamos en ComunPopReplies. creación de nuevo al que trasvasamos información del MimeMessage y ponemos en campo BCC los destinatarios. encolamos en ComunSmtp. se guarda en la base de datos, por el método de ListadoDistribucion también usado en NuevoMail. si todo ha sido correcto, se marca el mensaje entrante como borrable de modo que cuando se cierre la carpeta de JavaMail elimine los mensajes que ya tenemos guardados. En caso contrario, de ocurrir alguna incidencia, podremos recuperar el mensaje la próxima vez que el thread descargue de la cuenta. 53 o o bajados todos los mensajes se pone a dormir los segundos establecidos en el parámetro segsDuermePop, antes de volver a iniciar el proceso de nuevo. si el thread no consigue conectar con la cuenta de correo (cuenta incorrecta, fallo en la red, ...), duerme durante los segundos indicados por segsDuermePop y vuelve a intentarlo. Pasados 5 intentos, el thread descansa durante una hora. • private Message creaMessageSMTP(Message m, Session session), creamos mensaje que ThreadSmtp reenviará. No puede ser el mismo mensaje que hemos bajado por correo, ya que los mensajes entrantes son de sólo lectura. El Message_ID de este nuevo mensaje será el que guardemos en el campo del mismo nombre del registro de la base de datos, porque los receptores del mensaje que deseen contestarlo harán referencia a este identificador y no al del mensaje entrante. • private void llenaMyMessage(Message m, MyMessage m2), trasvase de información de la clase de JavaMail a la clase del proyecto. En su interior llama a los métodos que vienen a continuación. • private boolean mensajeAceptado(Message m), comprueba si el remitente puede enviar mensajes a la lista. • private void trataCaracteresEspeciales (MyMessage mensaje para que pueda ser guardado en la base de datos. • private void trataMultiPart(Part m, MyMessage m2, int nivel, boolean [] plain), método recursivo que trata las partes de un mensaje. m2), preparamos Los mensajes de correo constan de una o varias partes. Cada parte a su vez puede incluir otras subpartes. Un mensaje simple que no incluya más que texto forma una sola parte. Si el mensaje es en HTML, por regla general, tiene al menos dos partes10: una con el cuerpo del mensaje original y otra con el mismo contenido en texto sin formato para que pueda ser leído por cualquier cliente de correo que no reconozca HTML. Si el mensaje anterior incluye en el interior del contenido además del texto imágenes o cualquier otro objeto de extensión MIME , cada uno de ellos conformará una parte. Cada archivo adjuntado es una parte también, y si este archivo es un mensaje, mantendrá las mismas partes que el mensaje original. • private void trataAttachment (MimeBodyPart part, MyMessage msg), recibe la parte del mensaje que se ha descubierto que es un archivo, y la pasamos a nuestra clase Attachment que se encarga de grabarla en el disco. • private void trataReferences(Message m,MyMessage m2), comprobamos si es un mensaje respuesta de otro y lo marcamos para que se haga cargo posteriormente ThreadReplies. En la cabecera de un mensaje que es contestación de otro se encuentra uno de estos dos campos: References o In_Reply_To. Nadie puede evitar que exista algún caso aislado de mensajes que no incluyen ninguno de los dos, y no obstante sean una respuesta. Esto sucede cuando en lugar de responder un mensaje pulsando el botón adecuado, se crea un mensaje 10 No siempre es así, Yahoo, por poner un ejemplo, envía partes con sólo HTML, si al crear una nueva cuenta POP3 especificamos que podemos recibir mensajes de este tipo. 54 nuevo al que luego añade un asunto encabezado por un “Re: .. (mensaje al que quiero responder)”. Estos campos contienen entradas mensaje único para todo Internet. con un formato de Message_ID, un identificador de In_Reply_To sólo contiene un registro. En References, en cambio, es posible encontrar varias entradas, ya que se suele utilizar en hilos de conversación. El listado de referencias no es fiable, habiendo ocasiones en los que mensajes pertenecientes a una misma discusión, ordenados por tiempo, algunos incluyen la lista completa de referencias y otros una parcial de varios o un solo elemento. Con tal de no equivocarnos nos interesaremos pues en la última entrada. ThreadReplies.java El propósito de este thread es descargar de trabajo al ThreadPop. Mientras que el segundo se dedica de manera exclusiva a bajar correo, el primero puede ir examinando relaciones entre mensajes y actualizando la tabla de relaciones. En la actualidad las relaciones se limitan a encontrar hilos de conversación. Sin embargo, podrían indexarse también mensajes por varios criterios, o realizar algún tipo de búsquedas, algo de lo que no podría encargarse el ThreadPop porque supondría una pérdida de tiempo. El desdoble del procesado entre ambos threads, tiene pues sentido en este momento y aun más con vistas a la escalabilidad. ThreadReplies permanece a la espera de recibir en ComunPopReplies algún mensaje a examinar procedente de ThreadPop, permaneciendo activo mientras encuentre mensajes en la cola. Métodos: • public ThreadReplies(ComunPopReplies comun, Connection conn, int idLista), precompila las consultas SQL utilizadas para consultar las relaciones. Crea un objeto PatternsReply. • public void run(), método que debe implementar cualquier heredero de la clase Thread. En un bucle continuo, vamos extrayendo mensajes comprobamos su relación y los insertamos correctamente en la tabla. Los mensajes de los cuales no hayamos podido encontrar el mensaje referido no se registrarán en la tabla relación, sino que añadiremos su id en la tabla huérfanos. Internamente accede a un hash donde se mantienen transitoriamente los mensajes de los cuales no se haya encontrado un padre. Esto sucederá varias veces a lo largo del uso de la aplicación y no es en absoluto anómalo. En efecto, la descarga de mensajes del buzón de correo ni es ordenada ni hay modo de ordenarla y es fácil de observar en la base de datos como mensajes más antiguos pueden tener identificadores recientes, razón por la que los listados se ordenan por fecha. En el hash pues irán los hijos que llegaron antes que los padres, y los hijos de estos, con una estructura del tipo clave es igual a Message_ID del mensaje referenciado y valor de la casilla hash una lista enlazada de todos los mensajes que tengan el mismo mensaje referenciado. Será al llegar el padre, que se extraerán el resto de mensajes del hilo, para lo cual deberemos leer recursivamente el hash. El último mensaje leído siempre antes de bloquearse a la espera de la siguiente sesión es un mensaje nulo que introduce expresamente el ThreadPop al acabar la descarga del buzón. Una vez ThreadReplies, recibe esta señal sabe que han sido bajados todos los mensajes y que puede 55 examinar el hash con la conciencia que si queda algún mensaje colgado y no ha aparecido su padre ya no aparecerá jamás. Por lo tanto recorre el hash, y archiva en huerfanos los mensajes que quedaron pendientes. content, String palabra), veces que • private int buscaEnContent(String aparece la palabra en el contenido. • private int estudiaSubjects(MyMessage msg), compara el subject del posible reply con los subjects de mails en la base de datos a ver si encaja con alguno. Devuelve id del que deduce que podría ser el mensaje al cual contesta ó 0, de no encontrar ninguno. • private int IDdelReplied(String references), devuelve id del mensaje referenciado o -1 si no lo encuentra. • private boolean guardaEnBD(int reply, int refiereA), guarda mensaje en la tabla de relaciones. • private boolean guardaEnBD (MyMessage msg), no son reply. igual al anterior, para mensajes que ThreadSmtp.java Reenvía los mensajes creados o recibidos desde la web. Al contrario que ThreadPop o ThreadReplies, en los que cada lista de distribución tienen los suyos, éste es único para toda la aplicación. Métodos: • public ThreadSmtp(ComunASmtp comun, boolean autenticacion, String username,String password, String smtphost) , constructor. Recibe referencia a la cola por la que recibe los mensajes a enviar y los parámetros de conexión. En autenticacion se indica si el servidor de correo saliente requiere de login y password. Si está a false los valores de dichas variables no se tienen en cuenta. • public void run(), método que implementa al ser descendiente de la clase Thread. Bucle continuo: al recibir un mensaje, se debloquea conecta al servidor de Smtp y lo envía. Sigue entonces conectado enviando mensajes mientras encuentre la cola llena. Cuando esté próximo a bloquearse (observa si número de mensajes es uno o más) envía el último mensaje y cierra la sesión con el servidor Smtp. 56 3.5 BASE DE DATOS Diagrama listas id, nombre, email, host, login, password, comentario, registro, fecha, founder, descripcion, attachments mails int(11) varchar(75) varchar(75) varchar(75) varchar(25) varchar(75) text enum("si","no") date varchar(75) varchar(100) id , xfrom, email, n subject, content, idLista message_ID, time_, size, idLista, 1 id int(11) varchar(75) varchar(75) varchar(75) text varchar(75) datetime int(11) int(11) 1 id 1..n idLista id xfrom id idLista 0..n idLista baneados nombre, varchar(25) valorInt, int(11) valorStr, varchar(75) 1 relacion id id, int(11) ordinalThread, smallint(6) nivel, smallint(6) timeOrigen, datetime 0..1 login, varchar(25) idLista, int(11) recibeCorreo, enum("si","no") 0..n login, varchar(25) idLista, int(11) login fecha, date n motivo, text sistema 1 n registros int (11) int(11) varchar(5) varchar(75) int(11) 1 1 1 idMail, idAttach, 0..n path, nombre, idMail tamanyo, login huerfanos id, int(11) 1 login users 1 login, login nombre, 1 apellido1, apellido2, email, password, mailsdisponibles id, email, host, login, password, varchar(25) varchar(75) varchar(75) varchar(75) varchar(75) varchar(25) int(11) varchar(75) varchar(75) varchar(75) varchar(75) Tipos de datos presentes en las tablas • date : fecha. • enum (“si”, “no”) : campo enumerativo que recuperamos desde JDBC como si fuera una cadena. Equivale a un booleano, pues en la versión de MySQL empleada no existe este tipo de campo. Sus desarrolladores prevén, empero, incorporarlo en el futuro. • int(11) : el tamaño de entero establecido por defecto en MySQL para los campos clave numéricos. • smallint(6): cuando no se alcanzarán valores altos. • text: en campos que desconocemos el tamaño total, pero que suponemos puede ser extenso. • datetime: campo de tipo “timestamp”, esto es, fecha más hora. Mientras que de algunos registros nos es suficiente con saber su día de creación, y por ello empleamos un campo “date”, en el caso de los mails nos interesa una precisión mayor. 57 Un campo datetime resulta más cómodo y eficaz en una tabla que el mantener dos campos uno tipo date y otro tipo time. Es más rápido al indexar , buscar, o bien incluso al recorrer un ResultSet para comparar fechas. • varchar(n): cadena de longitud variable de tamaño máximo n. Tablas attachments Archivos adjuntos. En el campo “path” se indica la subcarpeta donde físicamente se halla el archivo en el servidor en el caso que ya exista en la carpeta raíz uno de idéntico nombre. baneados Expulsiones realizadas. Como el número ha de ser muy bajo, de incorporar estos campos a la tabla “users” estarían en más del 95% de los casos vacíos, razón por la cual se mantienen en una tabla aparte. listas Cabeceras de las listas de distribución. Propiedades generales y valores necesarios para conectar con la cuenta de correo electrónico asociada. • id: identificador de la lista usado internamente por el proyecto. • nombre: nombre de la misma. • email, host, login, password: relativos a la cuenta de correo. • descripción: breve descripción de la lista. • comentario: descripción tan extensa como se quiera. • fecha: día, mes y año de creación de la lista. • founder: fundador. • registro: si requiere estar registrado para acceder a la lista. mails Mensajes de las listas. • id: identificador de mensaje. • xfrom: remitente. La equis evita que MySQL se confunda en las sentencias con la palabra reservada, si bien aceptaría la forma `from`, entre acentos abiertos. 58 • email: dirección de correo del remitente11. • subject, content, fecha, tiempo: propiedades típicas de un mensaje. • message_ID: identificador único de un mensaje en Internet. Debe guardarse porque futuros mensajes que sean respuesta harán referencia a él. No obstante, se prefiere “id” como clave primaria porque un índice numérico es más eficiente en inserciones y consultas que uno alfanumérico. • idLista: identificador de lista a la que pertenece. • time_ : momento de envío del mensaje en nuestra hora, +2.00 respecto hora universal. Aparte de información para el usuario, nos permite ordenar los mensajes, ya que id, es útil para identificar un mensaje en sí, pero no para ordenar ya los mensajes obtenidos del buzón de entrada llegan parcialmente desordenados. mails disponibles Conjunto de direcciones de correo libres, preparadas para asignar a listas de correo nuevas. Cuando se crea una, se elimina el registro tomado y la información pasa a la lista. Otra posibilidad sería crear un campo idLista en esta tabla, de manera que en las direcciones libres estaría a NULL y en los otros casos se encontraría vinculado con la tabla “listas”. relación Relaciones entre mensajes. • • • • id: identificador del mensaje. timeOrigen: identificamos conjunto de mensajes de un mismo hilo de conversación. ordinalThread : posición dentro del hilo de un mensaje. nivel: posición ocupada en la jerarquía de hilo. Una primera elección en la que se pensó para conservar las dependencias entre mensajes constaba de dos campos, uno con el id del mensaje y otro con el id del mensaje al que referenciaba. Se comprobó, sin embargo, que para obtener los listados eran necesarios algoritmos con costes de desarrollo y temporales altos. Por ejemplo, para establecer las relaciones de un mensaje determinado, se requiere saltar entre los registros de la base de datos, con una lectura a la misma por registro. Queremos conservar el mensaje tal como llegó. Un usuario no puede cambiar su login, pero sí su email. Por lo tanto, un cambio de dirección de correo-e de un usuario haría que todos los mensajes enviados con anterioridad cambiarían también la dirección, si escogiéramos eliminar este campo “email” y leer de la tabla “users”. 11 59 El registro con id 20, refiere a 12. Ahora debo leer el registro con id 12 que me lleva al 10. Pero esto no sería suficiente, porque los registros relacionados son 10, 11, 12, 17, 20 y 19. Así, al llegar al registro con id 12 debo buscar si hay otros hijos aparte del 20, otra lectura a la base de datos . Igual con 10, que nos devolvería 11 y 19 . Aparte del abuso de accesos a la base de datos, luego habría que ordenar los resultados para mostrarlos tal como se encuentran en la ilustración. Esto es tan solo referido a las relaciones de un mensaje. No olvidemos que también deseamos mostrar todos los mensajes relacionados y en orden. Además, tiene que ser fácilmente paginable, de modo que ir a la página n de un listado no represente tener que recalcular todas las relaciones anteriores. Ejemplo, si deseo ir a la última página, y el primer registro de ella tiene id 453, primero he de haber reconstruido la lista de relaciones entera y luego fraccionar en páginas para saber que es ese registro. Luego la primera elección, es decididamente fallida e ineficiente. Es preferible, una o dos lecturas, en lugar de varias, y recibir un cursor con todos mensajes, y no de uno en uno. El método escogido se fundamenta ante todo en tener el listado en la tabla tal como se va a recuperar. Así pasar de una página de n registros a otra no contigua es inmediato. Con el campo nivel podremos saber el nivel de un mensaje sin necesidad de recorrer los mensajes anteriores en su hilo. Un mensaje que no responde a ningún otro será de nivel uno. Una respuesta a ese mensaje tendrá nivel dos. Respuesta a este mensaje estará en un nivel tres. Y así sucesivamente. El ThreadReplies irá insertando los mensajes a medida que los recibe de la cola. En más de una ocasión, encontrará un mensaje que va “en medio” de otros dos. Sirva de ejemplo tras colocados mensaje 21, mensaje 22 y unos cuantos más , todos de nivel uno aparece mensaje 49 que es respuesta de mensaje 21 y debe ir entre los dos. No podemos partir la tabla en dos. Aparte que nos obligaría a arrastrar los mensajes de 22 en adelante hacia nuevas posiciones. Necesitamos, pues, una forma en que sin alterar las posiciones físicas, se mantenga el orden, o sea, guardar un orden lógico. Un orden a nivel global sería muy costoso. Imaginemos un campo llamado posLogica y que renumeráramos a partir de un registro determinado cada vez que apareciera uno nuevo que vaya antes que él, las inserciones se ralentizarían. Sí, en cambio, podemos ordenar los mensajes de un hilo. Un orden parcial frente a un orden total. Es cierto que se presenta el mismo problema que antes, pero a un nivel mucho menor. Es decir, un hilo compuesto por mensajes en orden 1,2,3,4,5 al aparecer mensaje posterior a insertar entre 2 y 3 deberemos renumerar el orden, corriendo un número el orden a partir del antiguo 3. Contamos con la ventaja que los hilos de conversación son prácticamente siempre de pocos elementos. Y no es lo mismo ordenar diez elementos que mil. Dispondremos de dos campos: timeOrigen y ordinalThread, donde el segundo realizará el orden parcial. No confundir los campos nivel y ordinalThread. Se comprenderá fácilmente que entre varios mensajes de un mismo nivel hay un orden entre ellos según la fecha de llegada. El campo ordinalThread es necesario. Porque SELECT con un ORDER BY timeOrigen, nivel, id en este orden de parámetros o en cualquier otro no funcionará. Comprobado. Mediante timeOrigen nos aseguramos el orden entre los diferentes hilos. Anteriormente se estudió un campo similar que identificaba todos los mensajes de un mismo hilo con el id del primer mensaje del hijo. Sin embargo, dos situaciones hacían inútil esta posibilidad. La primera es que no podemos asegurar que el orden de bajada de los mensajes coincidirá con el orden de envío de los mismos, ya que un mensaje enviado antes puede llegar más tarde que otro (supongamos dos respuestas a un 60 mismo mensaje uno hace un recorrido más largo que otro por Internet), e incluso el buzón de entrada no entrega los mensajes ordenados completamente. Con lo cual orden de bajada no es del todo igual a orden de llegada de los mensajes al buzón. El segundo error parte de que si un mensaje por cualquier motivo no puede ser bajado en la sesión actual y se baja en la siguiente, el id que recibiría sería posterior y al ordenar por id, mostraría un listado inexacto. En cambio, timeOrigen ordena por la fecha y hora de envío del mensaje, y un mensaje bajado en una sesión posterior al ordenar por este campo aparecería correctamente en su posición. En consecuencia de todo lo expuesto, el listado de mensajes relacionados se obtendría, en orden ascendente con "select * from mails,relacion where mails.id=relacion.id order by relacion.timeOrigen asc,relacion.ordinalThread asc limit m,n; ", donde “m” es la posición inicial (cero si es desde el primer registro) y “n” el número de registros que recuperamos. En un listado descendente, basta con cambiar asc por desc en el order de timeOrigen. Entre las tablas de mails y relación hay una cardinalidad de 1:1, por lo que podrían fusionarse ambas en una sola. No obstante, preferimos la división ya que resulta más claro y además dividimos las inserciones entre: • inserciones en mails, ThreadPop • inserciones en relación, ThreadReplies huerfanos Archivamos los id de los mensajes que por algún motivo no se hayan podido relacionar. Podría pensarse si no es posible alguna sentencia SQL tipo left/right join que permita obtener un listado de los identificadores de los mensajes que se encuentran en la tabla mails pero no en la tabla relación. Tras diversos intentos infructuosos se optó por crear finalmente esta tabla. Cerca del 2%12 del total de mensajes no guardan la relación que debieran por el uso equivocado que esporádicamente hacen algunos usuarios. Un ejemplo sería enviar dos veces el mismo mensaje. Aparte, en principio pueden darse dos de los siguientes motivos. En primer lugar, el caso más corriente es crear un nuevo topic mediante un ‘reply’ a un mensaje. La segunda posibilidad es la que nos ocupa. Se da en mucha menor proporción, cuando en lugar de pulsar el botón “responder a”, crean un nuevo mensaje que titulan “Re: (mensaje anterior)”. Ese mensaje está relacionado lógicamente con el anterior en la mente del individuo pero no hay señal alguna en la cabecera del mensaje que nos indique dicha relación, y por lo tanto constituirá un huérfano. Podría pensarse rápidamente en nada más recibir un mensaje sin references considerar el asunto o el cuerpo del mensaje para compararlo con el de mensajes ya archivados, pero esto nos lleva a algoritmos enrevesados que tienen una fiabilidad baja. Varios intentos al respecto han hecho desestimar el uso de estas técnicas. No es tan sencillo como sondear entre mensajes que tengan el asunto “(mensaje anterior)”, ya que el mensaje anómalo puede pretender ser respuesta de otro que pertenezca a un thread de discusión en el que todos tengan como asunto “Re: (mensaje anterior)”. ¿Cómo saber a cuál pertenece? Quizás podríamos encontrar en el mensaje que es respuesta trazas del mensaje respondido, del mismo modo que también es probable que no las haya. Es posible, como se hace a menudo, que en texto del asunto haya añadido algunas palabras “Re: (mensaje anterior) xxxxxx”, con lo que el ordenador buscaría un asunto inexistente”. Si nos decantamos por buscar palabras clave entre los dos mensajes. ¿Qué palabras buscar? ¿Cómo estamos seguros que esas palabras no se encuentran en otra decena de mensajes? Etcétera, etcétera. 12 Estimado después de hacer pruebas con listas de Yahoo Groups sobre un millar de mensajes. 61 El número de posibilidades que hagan fracasar la relación en un mensaje sin references es altísimo, así como el coste computacional y de implementación. Por lo tanto, una opción más fácil sería informar al usuario del modo correcto de responder mensajes, algo que se podría realizar automáticamente al observar que el mensaje ha quedado sin relacionar. registros Registros de usuarios en listas, en los que se especifica si desea enviar o recibir correo en su e-mail. users Datos generales de los usuarios. sistema Valores de inicialización del sistema13. nombre regsXPagina pathAttachments maxSizeAttachments segsDuermePop debug_desactivaPop listaEjemplo smtp_auth smtp_login smtp_password smtp_host valorInt 30 0 512000 60 valorStr ./PFC_Attachments no [email protected] si projetse04 fentproves smtp.correo.yahoo.es La estructura con los tres campos está pensada para que se puedan añadir fácilmente entradas, identificables por el nombre, que sean números o cadenas. • regXPagina: especificamos el número máximo de registros que se volcarán en el listado de mensajes por página. • pathAttachments: directorio raíz donde se almacenarán los archivos bajados. • maxSizeAttachments: indicamos tamaño máximo aceptable en bytes de un attachment. Un archivo mayor no se guarda en disco. • segsDuermePop: tiempo que duerme el thread de bajada de mensajes una vez que ha acabado antes de volver a conectar con el servidor POP. • debugDesactivaPop: empleado para depuración, activa o desactiva la bajada de mensajes. • listaEjemplo: explicación en apartado Evaluación Pruebas. Una vez testeado, no tiene ninguna utilidad. 13 en la versión entregada los threads duermen 30 segundos para facilitar las pruebas. Asimismo, el smtp es el de urv, por lo tanto smtp_auth=no. 62 • smtp_... : parámetros de conexión al servidor de correo saliente. Si smtp_auth es “no” (no requiere autenticación) los valores de smtp_login y smtp_password no se tienen en cuenta. Script de la base de datos create database if not exists `pfcweb_sistema`; use `pfcweb_sistema`; drop table if exists `attachments`; CREATE TABLE `attachments` ( `idMail` int(11) NOT NULL default '0', `idAttach` int(11) NOT NULL auto_increment, `path` varchar(5) default NULL, `nombre` varchar(75) default NULL, `tamanyo` int(11) default NULL, PRIMARY KEY (`idAttach`), KEY `idAttach` (`idMail`) ) TYPE=InnoDB; drop table if exists `baneados`; CREATE TABLE `baneados` ( `login` varchar(25) NOT NULL default '', `idLista` int(11) NOT NULL default '0', `fecha` date default NULL, `motivo` text, PRIMARY KEY (`login`,`idLista`) ) TYPE=InnoDB; drop table if exists `huerfanos`; CREATE TABLE `huerfanos` ( `id` int(11) NOT NULL default '0', PRIMARY KEY (`id`), ) TYPE=InnoDB; drop table if exists `listas`; CREATE TABLE `listas` ( `id` int(11) NOT NULL auto_increment, `nombre` varchar(75) default NULL, `email` varchar(75) default NULL, `host` varchar(75) default NULL, `login` varchar(25) default NULL, `password` varchar(75) default NULL, `comentario` text, `registro` enum('SI','NO') default NULL, `fecha` date default NULL, `founder` varchar(75) default NULL, `descripcion` varchar(100) default NULL, PRIMARY KEY (`id`) ) TYPE=InnoDB; 63 drop table if exists `mails`; CREATE TABLE `mails` ( `xfrom` varchar(75) default NULL, `email` varchar(75) default NULL, `subject` varchar(75) default NULL, `content` text, `id` int(11) NOT NULL auto_increment, `message_ID` varchar(75) default NULL, `time_` datetime default NULL, `size` int(11) default NULL, `idLista` int(11) default NULL, PRIMARY KEY (`id`) ) TYPE=InnoDB; drop table if exists `mailsdisponibles`; CREATE TABLE `mailsdisponibles` ( `id` int(11) NOT NULL auto_increment, `email` varchar(75) default NULL, `host` varchar(75) default NULL, `login` varchar(75) default NULL, `password` varchar(75) default NULL, PRIMARY KEY (`id`) ) TYPE=InnoDB; drop table if exists `registros`; CREATE TABLE `registros` ( `login` varchar(25) NOT NULL default '0', `idLista` int(11) NOT NULL default '0', `recibeCorreo` enum('true','false') NOT NULL default 'false', PRIMARY KEY (`login`,`idLista`) ) TYPE=InnoDB; drop table if exists `relacion`; CREATE TABLE `relacion` ( `pos` int(11) default NULL, `id` int(11) NOT NULL default '0', `ordinalThread` smallint(6) default NULL, `nivel` smallint(6) default '1', `timeOrigen` datetime default NULL, PRIMARY KEY (`id`) ) TYPE=InnoDB; drop table if exists `sistema`; CREATE TABLE `sistema` ( `nombre` varchar(25) default NULL, `valorInt` int(11) default NULL, `valorStr` varchar(75) default NULL, PRIMARY KEY (`nombre`) ) TYPE=InnoDB; drop table if exists `users`; CREATE TABLE `users` ( `nombre` varchar(75) default NULL, `apellido1` varchar(75) default NULL, `apellido2` varchar(75) default NULL, `email` varchar(75) default NULL, `login` varchar(25) NOT NULL default '', `password` varchar(25) default NULL, PRIMARY KEY (`login`) ) TYPE=InnoDB; 64 4. DESARROLLO En los diagramas de colaboración exponemos todos los objetos principales que entran en juego en cada caso de uso. CDU 1. Bajar regularmente correo javax.mail 4: getFolder("Inbox") :Store INTERNET :Folder 2:getStore("pop3"): Store :Session 5: getMessages( ) Message 3: connect( ) 1:despierta 9:duerme 6: *[i=1..n]: Message :ListaDistribucion :ThreadPop :ComunPopReplies :PatternsPool 7.1: 7.2: :Message reenvío mensaje 7.4: 7.3: 8: despierta / recoge mail :MyMessage :ThreadReplies :Attachment :PatternsReply Pasos: 1: ThreadPop despierta después de un tiempo dormido fijado en el registro segsDuermePop en la tabla Sistema y que se le ha pasado a través del constructor. 2: la instancia de la clase Session representa una sesión de correo, sobre la que recuperamos un objeto Store en la que indicamos el protocolo de correo a utilizar. 3: conectamos con el servidor de correo. 4: del servidor bajamos la “carpeta” (folder) que nos interesa. En el caso de POP3 sólo existe una carpeta14, pero de todas formas debemos especificarlo por parámetro: getFolder(“inbox”). 14 en otros protocolos como IMAP existen varias carpetas como las de “mensajes enviados”, “borrador”, etcétera. 65 5: dentro de la carpeta encontramos los mensajes. Con vector de objetos de la clase Message. folder.getMessages() tenemos un 6: con un “for” recorremos cada uno de los mensajes. Cabe indicar que los mensajes no se han bajado todos a una con getMessages( ). JavaMail descarga los mensajes por demanda, y más concretamente las partes de un mensaje. Por lo tanto, cuando me encuentro en una posición cualquiera del vector recupero un objeto Message vacío. Es precisamente cuando voy solicitando las partes de este mensaje que las va descargando. Para cada mensaje: Antes que nada se comprueba que el remitente tiene permiso para escribir en la lista. De no ser así, se le envía un correo informativo, y se pasa al siguiente mensaje de entrada. 7.1: creo una instancia de MyMessage que cumplimentamos con todos los datos del Message que deseamos guardar en la base de datos. Para las distintas fases por las que pasa MyMessage se emplean PatternsPool y PatternsReply. 7.2: creamos una nueva instancia de Message, no podemos emplear el mismo Message de POP3 porque es de sólo lectura, al que le insertamos un nuevo Message_ID que será al que referirán las futuras respuestas al mensaje que pudiera tener ya que este Message está destinado a ser reenviado. No podemos emplear el mismo Message_ID, ya que identifica no sólo el mensaje sino también el instante en que fue enviado y no es posible copiarlo del mensaje original. 7.3: encolamos MyMessage en ComunPopReplies después de haberlo guardado en la base de datos invocando el método guardaEnBD( ) de ListaDistribucion. Si no ha habido ningún problema, se marca el mensaje como “borrable”. 7.4: reenviamos el mensaje: CDU 2. 8: al haber un elemento en ComunPopReplies, el ThreadReplies despierta, el cual comprueba si el mensaje recibido tiene alguna relación con algún mensaje ya almacenado y lo guarda en la tabla relación. Los pasos 7.1, 7.2, 7.3 porque forman parte del mismo proceso. El número 8, ya es independiente del trabajo de ThreadPop. Cuando ThreadPop ha tratado todos los mensajes, cerramos los objetos Folder y Store. Al cerrar Folder con el parámetro expunge a true se eliminan todos los mensajes que hemos marcado anteriormente. Si un mensaje por cualquier motivo no pudo guardarse y no se marcó, estará disponible en la siguiente ocasión. 9: ThreadPop duerme. Y después se volverá a repetir todo el proceso desde paso 1. Los objetos Store y Folder se abren en cada ocasión por no estar permanentemente conectados al servidor de correo y porque son susceptibles de generar excepciones. Si los abriéramos al crearse el Thread y hubiera un error, no habría posibilidad de recuperarse. En cambio si el error sucede al despertar el thread, vuelve a dormir y lo prueba más tarde. 66 CDU 2. Reenvío mensajes :ComunASmtp desbloquea / recoge *[i=1..n]: Message :ThreadSmtp tr.connect( ) tr.sendMessage( ) tr.close( ) javax.mail tr:Tranport INTERNET Pasos: ThreadSmtp se desbloquea cuando cualquiera de los threads de bajada de correo deposita un mensaje. Entonces conecta con el servidor de Smtp y envía cada uno de los mensajes encolados. Cuando ComunASmtp esté vacío, se desconectará del servidor y se desbloqueará a la espera de nuevos mensajes. 67 CDU 3. Iniciar sesión 2: consulta 10: consulta 1: petición index.do STRUTS 4: dirige Actor 8: getListasExistentes( ) 7: login :MySession :MyApplication :ListaDistribucion :index.jsp 6: crea / invoca 5: crea / invoca crear 3: action 11: página 12: carga 9: datos :indexJspAction struts-config.xml (mapa) crear crear :ThreadSmtp :ComunASmtp crear crear :ThreadPop crear :PatternsPool crear :ComunPopReplies :ThreadReplies crear :PattersReply crear :PattersReply Pasos 1: el usuario solicita la página index.do. 2: Struts consulta en el archivo XML para saber qué acción debe ejecutar. 3: la acción devuelta es IndexJspAction.java. 4: Struts ejecuta la clase acción. 5: IndexJspAction crea un objeto MyApplication si es la primera vez que se llama a esta página desde que se arrancó el contenedor de servlets, si no lo llama. 68 Al crearse el objeto MyApplication, se generan las clases mostradas en el cuadrado: tantas listas de distribución como las encontradas en la base de datos, más un ThreadSmtp y un ComunSmtp para conectar a él. Cada ListaDistribución tendrá su ThreadPop y su ThreadReplies y el objeto intermedio entre ambos. 6: crea un objeto MySession si el usuario entra por primera vez para una sesión en esta web, si no lo llama. 7: recogemos el login. Si el usuario no se ha identificado, será nulo, luego está como invitado. 8: obtenemos las listas existentes, una colección de referencias a los objetos ListaDistribucion. 9: colocamos en el ámbito request, el login y la colección para que pueda ser recuperado desde la jsp. Es la principal manera de compartir información entre Action y JSP en Struts. 10: Struts consulta qué pagina jsp debe cargar15. 11: devuelve que la página es index.jsp. 12: carga la página index.jsp. En el navegador aparecerá como “index.do”. En el interior de la página hay etiquetas Struts que recuperan los datos que hemos colocado en el objeto Request. CDU 4. Consulta datos lista 2: consulta 14: consulta 1: petición lista.do página JSP STRUTS 5: dirige :ListaAction 4: carga 13: vuelve struts-config.xml (mapa) 3: action y form 15: página 16.1: carga :lista.jsp 12: llena 6: invoca 8: invoca 7: referencia objeto :MyApplication 11: getListaDistribución 9: login :ListaForm 16.2: lectura 10: crea :GestionListas :MySession Pasos 1: solicitamos listado.do desde una página JSP que disponga de un enlace a él. 2: Struts consulta en el archivo XML para saber qué acción debe ejecutar. 3: devuelve que la acción es ListaAction.java y que empleamos un bean de formulario llamado ListaForm. El contenedor de servlets crea una sola instancia por cada JSP, a compartir por todas las peticiones para dicha página. Otra buena razón para no incluir código de lógica en las páginas. Por lo tanto por “cargar una página jsp” entendemos que accede a ella, y será el contenedor de servlets, no Struts, quien se encarga de interpretar el jsp y crear el servlet o bien si ya está creado, de devolver una referencia a él. 15 69 4: Struts carga ListaForm.java en blanco. 5: Struts carga ListaAction.java. 6: invoca MyApplication. 7: extrae referencia de MyApplication del contexto del servlet que ejecuta el Action. 8: invoca MySession. 9: recibe el login. Si el login coincide con el del fundador, el formulario de la página se podrá editar. 10: crea GestionListas. Le pasamos referencia a MyApplication, objeto que tiene la colección de listas de distribución16. 11: devuelve ListaDistribución. 12: llenamos el bean de formulario con los datos de la lista, y la indicación de si es modificable o no en la propiedad “action”. El bean lo hemos recibimos como un parámetro más en el método execute( ) del Action y es de ámbito request. 13: volvemos a Struts. 14: Struts consulta a qué página debe reconducir. 15: devuelve lista.jsp. 16.1, 16.2: Struts carga entonces lista.jsp. y con etiquetas Struts de formulario accedemos al bean. Si desde los datos principales de la lista, queremos conocer los usuarios registrados de la misma, haríamos clic en el enlace que realiza la petición lista2.do: 1: petición lista2.do 2:, 17: consulta :lista.jsp struts-config.xml (mapa) STRUTS 3: action y form 18: página 4.2:dirige 4.1: crea 19.1: carga 16: vuelve :Lista2Action :Lista2Form :lista2.jsp 11: llena 19.2: lectura 5: invoca 7: invoca 6: referencia objeto :MyApplication 8: referencia objeto 10, 13, 15: devuelve 9: getListaDistrib.( ) :MySession 12: getUsuariosRegistrados( ) 14: getUsuariosExpulsados( ) :GestionListas Podríamos obtener la lista desde el mismo objeto MyApplication. Pero en especificaciones indicamos que la lista la recuperaríamos a través del objeto GestiónListas. 16 70 Pasos: 1, 2, 3, 4.1, 4.2, 5, 6, 7, 8: 9, 10: a GestionListas le pasamos las referencias de los objetos llamados anteriormente. Obtenemos lista distribución. 11: llenamos Lista2Form con el nombre de la lista y algunos valores más como los títulos y los enlaces de expulsar y readmitir, si quien ha realizado la petición es el fundador de la lista. De hecho en lista2.jsp no vamos a encontrar ningún formulario. Aquí el bean de formulario se emplea para empaquetar cadenas . Luego accederemos como un bean cualquiera de visibilidad request. 12, 13, 14, 15, 16, recuperamos las colecciones de usuarios de pleno derecho y de usuarios expulsados, que depositamos en el objeto request del contenedor de servlets. 17, 18, 19.1, 19.2: lista2.jsp recupera las dos colecciones y los valores del bean. CDU 5. Acceso mensajes lista pública 1: petición creaLista.do 2:, 12: consulta página JSP struts-config.xml (mapa) STRUTS 3: action 13: página 4: dirige 11: vuelve 14: carga "listadoMails.do" :AccesoAction 5: 6: 7: 10: 8: 9: :MyApplication :MySession :GestionAcceso :acceso.jsp Pasos: 1: la página JSP desde la que se hará la petición es principalmente index.jsp, aunque también es posible desde usuario2.jsp y usuario3.jsp. 2, 3, 4: 5, 6: obtenemos el objeto ListaDistribucion que enlazaremos a MySession si se acepta el login. 7,8: login del usuario, que puede ser cadena vacía si entra como anónimo. 9, 10: se controla el estatus del usuario en la lista, si está expulsado, si está registrado. En caso de no aceptarse el login, devuelve el mensaje que se mostrará en la página. 11: antes de volver, si se acepta el acceso MySession contiene una referencia a la lista de distribución. 12, 13, 14: según el resultado se mostrará la página de acceso denegado o bien se realizará una petición a “listadoMails.do”, que se concreta en el siguiente diagrama. 71 2:, 12, 16, 26: consulta 1:petición listadoMails.do "listadoMails.do" struts-config.xml (mapa) STRUTS 3, 17: action, form 13, 27: página 4.1, 18.1: dirige 4.2, 18.2: carga 11, 25: vuelve 15: petición listadoMails.do :ListadoMailsAction 14.1, 28.1: carga 22: lectura 9, 24: 5, 19: :ListadoMailsForm 10: llena 6, 20: 18.3: cambios formulario 8, 23: 7, 21: :MySession 14.2, 28.2: lectura :ListadoMails :listadoMails.jsp Pasos: 1, 2, 3, 4: 5, 6, 7 : recibimos referencia de MySession. Dentro de este objeto hay la referencia a ListadoMails que se actualizó al hacer el acceso, en el anterior diagrama. 8,9: parametrizamos el objeto con los valores por defecto, ya que es la primera vez que llamamos a la página, recibimos el resultado de la consulta en forma de colección de FilaListado, que pasamos a la Action. 10: llenamos los valores por defecto que debe mostrarse en el formulario de consulta. 11: guardamos en el objeto request del servlet la colección del resultado. 12, 13, 14.1, 14.2: cargamos la página. 15: si deseamos hacer una consulta no genérica, como la que acabamos de recibir, cambiamos los valores en los controles del formulario, el orden, el tipo de campo a ordenar, etcétera y pulsamos en el botón de submit. 16, 17, 18.1, 18.2, 18.3: cargamos el Action y el bean de formulario, al que pasamos los cambios hechos en el formulario HTML. 19, 20, 21: idem 5,6,7. 22, 23, 24: idem 8,9 pero parametrizando con los cambios que se encuentran en el bean de formulario. Este bean se comparte entre los dos formularios que tiene el listado. 25, 26, 27, 28.1, 28.2: - En el caso que elijamos un mensaje en especial, se ejecutaría el proceso siguiente: 72 2, 12: consulta 1:petición detalle.do :listadoMails.jsp struts-config.xml (mapa) STRUTS 4.1: dirige 4.2: carga 3: action, form 13: página 11: vuelve 14.1: carga :DetalleAction :DetalleForm 9: 5: 6: 10: llena 8: 7: :MySession 14.2: lectura :ListadoMails :detalle.jsp Pasos: Al igual que en el diagrama anterior, de MySession tomamos la referencia a ListadoMails. Solicitamos que nos devuelva un mensaje en particular, así como el listado de todos los mensajes relacionados. Si pulsaramos sobre un botón de navegación entre mensajes, se realizaría una nuev a petición a detalle.do, en la que el proceso se repetiría, aunque en este caso la página que lo inicia no sería listadoMails.jsp sino la propia detalle.jsp. DetalleForm mantiene algunos valores a mostrar en detalle.jsp, pero no el mensaje mismo, ya que no era factible colocar el mensaje dentro del formulario, entre otras razones por dos principalmente: • las etiquetas Struts muestran los textos literalmente. Esto quiere decir que no se interpretarían las etiquetas HTML que se encuentran en el texto, como por ejemplo los <BR> de salto de carro. • porque detalle.jsp incluye mail.jsp, y éste espera un objeto MyMessage. Si empleáramos un bean de formulario para encapsular el propio mensaje, deberíamos cambiar mail.jsp. Si cambiamos mail.jsp ya no nos valdría para extiende.jsp, en el que se muestran varios mensajes, porque un Action sólo es capaz de emplear un solo bean de formulario. Por último dentro de detalle.jsp puede solicitarse el listado completo de mensajes relacionados vistos en una sola página: 2, 11: consulta 1:petición extiende.do :detalle.jsp struts-config.xml (mapa) STRUTS 4.1: dirige 3: action 12: página 10: vuelve :ExtiendeAction 13: carga 9: 5: 6: 8: :extiende.jsp 7: :MySession :ListadoMails 73 Pasos: Igual a los diagramas anteriores. En esta ocasión, de ListadoMails recogemos una colección de MyMessage. CDU 6. Acceso archivos de listas públicas Tanto desde el listado de archivos, al que se accede a través de listado de mensajes, como desde un mensaje determinado, para descargar un adjunto el enlace se llama a fileDownload.jsp a través de un formulario. En el caso de listado de archivos está oculto, y le pasamos los parámetros con JavaScript en el instante de hacer clic sobre el enlace. En el jsp, especificamos en el response el MIME que recibirá el navegador “APPLICATION/OCTETSTREAM”, y el nombre del archivo. Abrimos un objeto de tipo FileInputStream que lee de disco y trasvasa al objeto out del servlet. CDU 7. Alta usuario 2, 8, 12, 22: consulta 1: petición usuario.do :login:jsp struts-config.xml (mapa) STRUTS 3, 13: action y form 9, 23: página 5, 15: dirige 4, 14: carga 24.1: cargar 7, 21: vuelve :UsuarioAction 6: llena 11: pet. usuario.do :UsuarioForm 17: invoca 18: referencia :MySession 20: 16: lectura :loginYaExiste.jsp 10.1, 24.2.1: carga 19: crea 10.2, 24.2.2: lectura :GestionUsuarios :usuario.jsp Pasos: 1, 2, 3, 4, 5: 6: introducimos en el bean de form algunos valores que indican que la página es para hacer un alta, como setAction=”alta” y el título de la página. 7, 8, 9, 10.1, 10.2 : cargamos página usuario.jsp con el formulario en blanco. 11, 12, 13, 14 ,15: 16: leemos los datos del formulario con el alta. 17, 18: tomamos una referencia de MySession. 19: creamos GestionUsuarios, y le pasamos la referencia a la sesión. Efectúa el alta. Si ha sido satisfactoria, en MySession habrá el login y el email del usuario creado. 20, 21, 22, 23: - 74 24: si el alta se ha producido se seguirá mostrando usuario.jsp, donde aparte se mostrará un menú de pestañas para que pueda darse de alta a listas, o crear listas. Si no, aparecerá una pantalla de error. CDU 8. Identificarse 2:, 6:, 10:, 18: consulta 1: petición login.do :index.jsp STRUTS 3: action 11: action, form 7:, 19: página 4: dirige 8: crea :EditLoginAction 12.1:crea struts-config.xml (mapa) 12.3: dirige 20:carga 5: vuelve :bienvenido.jsp 17: vuelve 9: petición checklogin.do 13: lectura :LoginForm :CheckLoginAction :login.jsp 12.2: llena 16: setLogin( ), setEmail( ) 14: crea, log_in( ) :login_error.jsp 15: email :MyLogin :MySession Pasos: 1, 2, 3, 4: 5: a toda página le antecede un Action. En este caso, el action sólo redirige a la página. 6, 7, 8: 9: una vez el formulario HTML ha sido cumplimentado, pedimos “checkLogin.do”. 10: 11: el archivo XML devuelve el Action que necesitamos, y el bean de formulario que el Action leerá. 12.1, 12.2, 12.3: creamos el bean de formulario que contendrá los valores del formulario HTML y el Action. 13: CheckLoginAction lee el bean formulario. 14: creamos MyLogin y le pasamos los valores del bean. 15: MyLogin devuelve email del usuario o cadena vacía si no existe el usuario o los datos del login son incorrectos. 16: si la cadena de email no está vacía, actualizamos el objeto MySession. 17: volvemos a Struts. En el return del Action se indica si el login ha sido correcto (“success”) o no (“failure”). 18: consultamos qué página se mostrará en el navegador según el return del Action. 19: devuelve la página. 20: carga la página. 75 CDU 9. Crear lista 1: petición creaLista.do 2:, 10:: consulta :usuario3.jsp struts-config.xml (mapa) STRUTS 3: action 11: página 4: dirige 9: vuelve 12: carga :CreaListaAction "lista.do" 8 puedenCrearseMasListas( ) 5: 6: 7: crear :MyApplication :GestionListas :nuevasListasNo.jsp Pasos: 1,2,3,4: 5,6: necesitamos una referencia a MyApplication para pasárselo a GestionListas. 7,8: creamos GestionListas, le pasamos la referencia y consultamos si hay posibilidad de crear nuevas listas. 9: volvemos a Struts con la respuesta de GestionListas. 10,11 : según si la respuesta recibida de la Action fue “success” o “failure” cargamos una página u otra. 12: cargamos la página. Si la página es de error, terminamos aquí, si no, continuamos. Siempre que llamamos a una página con extensión .do se ejecuta una Action, mientras que una llamada a una página que tenga extensión .jsp no. Por ello “listado.do”, ejecutará los pasos mostrados en el siguiente diagrama. 1: petición lista.do "lista.do" 2:, 7:, 12:, 24: consulta struts-config.xml (mapa) STRUTS 4.2: ,15: dirige 3:, 13: action y form 8:, 25: página 4.1:, 14:1 crea 26.1: 6:, 23: nuevaListaError.jsp 21: :ListaAction 9:, 26:2 cargar :ListaForm 14.2: lectura 5: 16: :MyApplication 17: 18: 19: :MySession 22: 9:, 20: crear 76 11: petición lista.do 10, 27: lectura :GestionListas lista.jsp Pasos: 1, 2, 3, 4.1, 4.2: 5: introducimos en el bean de form algunos valores que indican que la página es para hacer un alta, como setAction=”alta” y campos que se inicializaron con el valor “automático” al crearse el formulario. 6, 7, 8, 9, 10 : cargamos la página lista.jsp con el formulario en blanco. 11: una vez hemos rellenado el formulario y pulsado el botón de envío, volvemos a Struts. 14:1, 14:2: al crearse el bean de formulario, recuperamos los valores existentes en el JSP. 15: cargamos la Action. 16, 17: tomamos referencia a MyApplication. 18,19: invocamos MySession, del que recibimos el login. 20, 21, 22: leemos los valores en el bean de formulario y damos de alta la lista. 23: volvemos. 24,25, 26.1, 26.2 : mostramos la página que corresponda según si el alta ha sido válida o no. De momento creada el alta muestra la página lista.jsp. También podría hacerse para que volviera a la página usuario3.do. 27: como estamos en lista.jsp leemos del formulario. CDU 10. Consulta datos usuario 2, 12: consulta struts-config.xml (mapa) 1: petición usuario.do :index:jsp STRUTS 3: action y form 13: página 5: dirige 4: carga 14.1: carga 11: vuelve :UsuarioAction 10: llena :UsuarioForm 6: invoca :MySession 7: referencia 9: getDatosUsuario( ) 8: crea 14.2: lectura :GestionUsuarios Pasos: 1: solicitamos usuario.do desde index.jsp. 2: Struts consulta en el archivo XML para saber qué acción debe ejecutar. 77 :usuario.jsp 3: devuelve que la acción es UsuarioAction.java y que empleamos un bean de formulario llamado UsuarioForm. 4: Struts carga UsuarioForm.java en blanco. 5: Struts carga UsuarioAction.java. 6, 7: recuperamos referencia a MySession. 8, 9: creamos GestionUsuarios y le pasamos el MySession, recuperamos los datos del usuario con el método getDatosUsuario( ). Es imposible que el usuario no exista, puesto que para llegar a este caso de uso hemos tenido que clicar un enlace que solo se muestra cuando el usuario está logineado. 10: llenamos el bean de formulario con los datos de usuario. 11,12,13, 14.1, 14.2: carga de la página, que recupera los valores de los campos del formulario HTML del bean de formulario. CDU 11. Modificar datos usuario 2, 14: consulta struts-config.xml (mapa) 1: petición usuario.do :usuario.jsp STRUTS 3: action y form 15: página 16.1: carga 4.2:llena 4.1: carga 5: dirige 13: vuelve 16.2: lectura 10: lectura :UsuarioForm :UsuarioAction 6: invoca 8: invoca 9: referencia 7: referencia :MySession :MyApplication 12: vuelve 11: crear, modifUsuario( ) :GestionUsuarios Pasos: 1, 2, 3, 4.1, 4.2, 5, 6, 7: 8,9: recibimos referencia de MyApplication necesaria por si el usuario ha cambiado su email, ya que entonces modifUsuario( ) tendrá que cambiar dicha dirección en todas las listas en las que participa. 10, 11: crea GestionUsuarios, lee el form y pasa los datos a modifUsuario( ). 12, 13, 14, 15, 16.1, 16.2: volvemos a la página inicial con los cambios hechos. 78 CDU 12. Registrarse en lista 1: petición usuario2.do 2, 12: consulta :usuario2.jsp struts-config.xml (mapa) STRUTS 3: action 13: página 14: carga 4: dirige 11: vuelve :Usuario2Action 5: invoca 10: vuelve 7: invoca 8: referencia :MySession 6: referencia 9: crea, altaEnLista( ) :MyApplication :GestionUsuarios Pasos: 1, 2, 3, 4: 5,6: referencia de MySession, necesaria para tener el login. 7,8: referencia de MyApplication necesaria para actualizar la lista de usuarios registrados de la lista de distribución donde nos registremos. 9,10: creamos GestionUsuarios. Pasamos referencia a MySession y MyApplication. Llamamos a altaEnLista( int idLista) y volvemos a Struts. 11, 12, 13, 14: la página cargada ya muestra en sí misma si estamos registrados o no. CDU 13. Consulta listas registrado 1: petición usuario2.do 2, 16: consulta :usuario.jsp struts-config.xml (mapa) STRUTS 3: action 17: página 4: dirige 15: vuelve :Usuario2Action 5: invoca 18: carga 10, 12 , 14: vuelve :usuario2.jsp 7: invoca 8: referencia :MySession 6: referencia 9: crea, 11: getListasUsuario() 13: getListasNoApuntado() :MyApplication 79 :GestionUsuarios Pasos: 1, 2, 3, 4: cargamos Usuario2Action. 5, 6, 7, 8: referencias de MySession y MyApplication a pasar a GestionUsuarios. Del primero obtenemos el login. La referencia a MyApplication no se utiliza en este CDU, pero esta Action es común a los CDU de registrarse, desregistrarse y recibir correo. El coste computacional de referenciar MyApplication es nulo, el mismo que poner un par de condiciones. 9, 10, 11, 12, 13, 14, 15: recuperamos las listas en las que está registrado y en las que puede registrarse que se pasan por el objeto request para que las etiquetas Struts del jsp receptor puedan leerlos. 16, 17, 18: cargamos la página destino. CDU 14. Desregistrarse en lista El diagrama y los pasos son los mismo que en el caso “registrarse en lista”, salvo que el método llamado en GestionUsuarios es bajaDeLista( int idLista). CDU 15. Recibir o no mails por correo El diagrama y los pasos son los mismo que en el caso “registrarse en lista”, salvo que el método llamado en GestionUsuarios es recibirCorreo(int idLista, String opcion). CDU 16. Acceso mensajes lista privada Es exactamente el mismo diagrama y pasos que en una lista pública. CDU 17. Acceso archivos listas privada El procedimiento es el mismo que para archivos de listas públicas. CDU 18. Enviar mensaje por web 2, 11, 15, 24: consulta 1: petición nuevoMail.do página JSP STRUTS struts-config.xml (mapa) 3, 16: action y form 12, 25: página 4.2, 17.2: dirige 14: peticion nuevoMail.do 26: carga 10, 23: vuelve :NuevoMailAction 13.1: carga 4.1, 17:1: crea :nuevoMail.jsp :mensajeEnviado.jsp 5, 18: invoca 7, 21: 8, 22: 20: lectura 6, 19: referencia :mensajeNoEnviado.jsp 13.2: lectura 9: llena :MySession :NuevoMail :NuevoMailForm 17.2: lectura 80 Pasos: 1: página JSP puede ser “listaMails.jsp”, “detalle.jsp”, o “extiende.jsp”. Desde los dos últimos es para responder a mensajes. 2, 3, 4: 5, 6, 7, 8: tomamos una referencia a MySession para obtener el login, la lista de distribución y el objeto ListadoMails. Si es una respuesta, recuperamos el mensaje al que responde, y lo tratamos con símbolos “>” antes de cada línea. El método que realiza esto último se encuentra en NuevoMail. 9: ponemos el nombre de la lista, y el login del destinatario en el bean del formulario, más el texto tratado si el mensaje será una respuesta. 10, 11, 12, 13.1, 13.2: cargamos la página nuevoMail.jsp. 14: cuando se ha terminado el mensaje, y pulsamos el botón del formulario se realiza una petición a la misma página. 15, 16, 17.1, 17.2: cargamos NuevoMailAction y el bean de formulario con los valores del jsp. 18, 19: referencia a MySession que pasaremos a NuevoMail, ya que necesitará llamar al método de la ListaDistribucion tanto para guardar el mensaje en la lista como para decidir los correos electrónicos que recibirán el mensaje. 20, 21, 22: leeremos del bean y pasamos los valores a NuevoMail para que lo guarde en la lista y lo reenvíe por Internet. Si se adjunta un archivo, se llama a MyApplication para conocer el tamaño máximo que puede tener dicho archivo y se crea un objeto Attachment con lo recibido del form, y que pasaremos a NuevoMail para que lo añada al MyMessage que ha de crear y enviar. 23, 24, 25, 26: cargamos una página u otra que indican si el mensaje ha sido enviado o no. Si el motivo por el cual no se acepta el mensaje es el excesivo tamaño del archivo adjunto, la página de resultado explica el motivo. CDU 19. Enviar mensaje por correo En especificaciones ya se comentó que este caso no se estudia, por ser externo a la aplicación. Si lo tendremos en cuenta en el apartado de pruebas. 81 CDU 20. Consultar listas creadas 1: petición usuario3.do 2, 13: consulta :usuario.jsp struts-config.xml (mapa) STRUTS 3: action 14: página 4: dirige 12: vuelve 15: carga :Usuario3Action 11: vuelve 5: invoca :usuario3.jsp 7: invoca 8: referencia 6: referencia :MySession 9: crea, 10: listasCreadas() :MyApplication :GestionUsuarios Pasos: 1, 2, 3, 4: cargamos Usuario3Action. 5, 6, 7, 8: referencias de MySession y MyApplication a pasar a GestionUsuarios. 9, 10, 11, 12: recuperamos la relación de listas creadas por el usuario, pasadas por un objeto request para que las etiquetas Struts del jsp receptor puedan leerlos. 13,14,15: cargamos la página destino. CDU 21. Modificar datos lista 2:, 13: consulta 1: petición lista.do struts-config.xml (mapa) STRUTS :lista.jsp 3: action y form 14: página 15.1: carga 4.2:llena 15.2: lectura 4.1: carga 5: dirige 10: lectura 12: vuelve :ListaAction :ListaForm 6: invoca 11: crea, modifLista( ) 8: invoca 9: login :MyApplication 7: referencia objeto :MySession 82 :GestionListas Pasos: 1: para modificar debe haberse entrado en modo consulta y ser fundador. Luego estamos en lista.jsp, hemos hecho algunos cambios y hemos pulsado el botón del formulario. 2: Struts consulta en el archivo XML para saber qué acción debe ejecutar. 3: devuelve que la acción es ListaAction.java y que empleará un bean de formulario llamado ListaForm17. 4.1, 4.2: Struts crea ListaForm.java y le pasa los valores del formulario de lista.jsp. 5: Struts carga ListaAction.java. 6, 7, 8, 9: idem consulta datos lista. 10: leemos formulario. 11: crea GestionListas. Le pasamos referencia a MyApplication, objeto que tiene la colección de listas de distribución y llamamos a método modifLista( ) con los datos leídos del formulario. 12: volvemos a Struts. 13: Struts consulta a qué página debe reconducir. 14: devuelve lista.jsp. 15.1, 15.2: Struts carga entonces lista.jsp y con etiquetas Struts de formulario accedemos al bean. CDU 22. Expulsar usuario de lista Desde el CDU anterior 20, si el usuario pulsa en el link de expulsión se ejecuta lo siguiente: 1: petición banear.do 2, 11, 16, 29: consulta :lista2.jsp 4.1, 18:1: crea 4.2, 18:3: dirige 10, 28: 14: lectura :BanearForm 19: 23: 6, 21: :MyApplication 3, 17, 30: action y form 12: página 15: peticion banear.do 13: cargar 9: :BanearAction 5,20: struts-config.xml (mapa) STRUTS 22: :banear.jsp 18.2: lectura 27: 8,25: 26: :MySession :GestionBaneados 7, 24: :GestionUsuarios 17 los bean de formulario se crean cuando desde struts-config.xml se indica que una Action va a hacer uso de ella. Las etiquetas Struts de formulario dentro de un JSP, no crean beans de formulario. 83 Pasos: 1: de la lista de usuarios registrados, hacemos clic sobre aquél que deseamos expulsar. 2, 3, 4, 5, 6: 7, 8: GestionUsuarios para obtener el email del usuario a expulsar. 9: guardamos nombre, email y fecha de expulsión. 10, 11, 12: 13, 14, 15: mostramos la página con los valores del bean formulario, introducimos el motivo y pulsamos el botón de alta. 16, 17, 18, 19: 20, 21: ListaDistribucion. 22, 23: MySession tiene el login de quién va a realizar la expulsión. 24, 25: obtenemos email del usuario a expulsar. 26, 27: creamos GestionBaneados y ejecutamos el alta de expulsión, pasándole el login y email del expulsado y el login del fundador. El email del expulsado es para retirarlo de la ListaDistribucion. 28, 29, 30, 31: volvemos a lista2.do, o sea volveríamos a CDU20. CDU 23. Readmitir usuario a lista 1: petición banear.do 2, 14: consulta :lista2.jsp struts-config.xml (mapa) STRUTS 16: cargar 4.1, 18:1: crea 4.2: dirige 3: action y form 15: página 13: vuelve :BanearAction :BanearForm 8: 5: 6: :MyApplication 7: 10: 12: 9: :MySession :GestionUsuarios 11: :GestionBaneados Pasos: Similar a los pasos del segundo diagrama de expulsar usuario, pero tras la readmisión se vuelve a lista2.jsp sin pasar por banear.jsp. 84 5. EVALUACIÓN PRUEBAS A fin de mantener la coherencia seguida en el curso del documento, las pruebas son expuestas bajo los epígrafes de los casos de uso. Debido a que se encuentran entre ellos en algunos casos fuertemente relacionados es poco menos que inevitable que algunas pruebas se repitan. De las listas de distribución empleadas, contamos con una, llamada “lista principal”, cuya fuente de mensajes será externa a la aplicación, ya que su cuenta de correo está suscrita a una lista de Yahoo Groups. Ello permite disponer de un número considerable de mensajes, nos evita redactarlos nosotros mismos y aumentamos las posibilidades de localizar errores en el tratamiento del correo y en la relación entre los mismos. No hay que olvidar que es una lista externa, con el propósito de ser usada en funciones de depuración y demostración pero que no correspondería con el funcionamiento normal de la aplicación: las listas deben contener mensajes de ellas mismas. Al ser una lista externa, no controlaremos los usuarios que participan en ella, al contrario que en una lista creada en nuestra aplicación. En las pruebas realizadas fuera de la universidad se ha empleado el smtp de Yahoo que requiere autenticación. Ello me ha permetido emplear varias cuentas para hacer las pruebas. La recepción de mensajes a través de correo electrónico puede no ser inmediata, tardando algunos minutos (dos, cinco... ) según el proveedor del servicio. Es algo a tener en cuenta al probar el envío de mensajes a la lista fuera de la aplicación. La red de ETSE no permite emplear otro servidor de correo saliente que no sea smtp.urv.es, que no exige autenticación, pero sí que el destinatario sea de dominio urv.es. Por ello, la aplicación entregada funciona con dicho servidor de correo saliente y no incluye usuarios, habrá de crearlos quien lo ejecute con cuentas de correo urv.es, puesto que yo únicamente dispongo de una cuenta con extensión de la universidad. La base de datos además viene con dos listas creadas: “la principal” y una propia, más la posibilidad de crear una tercera sobre la marcha. Por abuso de lenguaje hablaremos de páginas al referirnos a elementos con la extensión .do cuando en realidad son peticiones que tras pasar por una clase Action nos llevan a la verdadera página de igual nombre y extensión .jsp. CDU 1. Bajar regularmente correo En la base de datos existen tres listas vacías, una de ellas es la “lista principal”. Arrancamos la aplicación. En el output de Tomcat: pop pop pop pop zapateros bajando 0 mensajes pensadores bajando 0 mensajes lista 2 bajando 0 mensajes lista principal bajando 195 mensajes 85 pop zapateros duermo 3018 segundos pop pensadores duermo 30 segundos pop lista 2 duermo 30 segundos Los threads que no tienen mensajes duermen al instante. Al cabo de 30 segundos vuelven a leer el buzón de correo, y así cíclicamente. La lista principal duerme una vez bajados todos los mensajes. Se constata que los 195 mensajes de “lista principal” son guardados en la BD en orden y relacionados correctamente. Los mensajes enviados por usuarios expulsados, o no registrados (tanto en la lista como en la aplicación en general) son eliminados. Vemos que reciben mensajes de aviso, al visitar las cuentas de correo de los mensajes. Esto no se aplica en la “lista principal”, pues es una lista de ejemplo. Se crea una nueva lista (CDU 9) y se observa como un nuevo thread se encarga del buzón de la nueva lista. Probamos con otra nueva lista (CDU 9) , pero en este caso los datos de la cuenta son incorrectos con lo cual no será posible una conexión. pop cuenta_mal error en conexión duermo 30seg, y volveré a intentarlo. ( 1 de 5 intentos ) Pasados los 5 intentos pasa a dormir durante una hora. Probamos a enviar mensajes desde web en lista principal, mientras sigue recibiendo correo por internet: sin incidencias. Prueba de recibir correo por Internet desde dos cuentas: se guardan los mensajes de ambas en sus respectivas listas. CDU 2. Reenvío mensajes Los mensajes de listas que no son la de ejemplo reenvían los mensajes que reciben a todos los usuarios registrados y no expulsados, salvo el usuario mismo que lo ha enviado. CDU 3. Iniciar sesión Este CDU inicia una sesión para cada usuario que accede a la página principal. La petición de esta página hace que para el primer usuario que entra recién puesto en marcha Tomcat arranque automáticamente la aplicación antes de recibir la sesión. En el directorio bin de Tomcat escribimos startup.bat. Con Tomcat cargado y la aplicación todavía no en marcha, abrimos el navegador e introducimos la dirección: http://localhost:8080/PFC_Web_Struts/index.do. En la ventana de Tomcat, se observa como la aplicación arranca y se ejecuta CDU 1. Si ahora cerramos el navegador, la aplicación sigue su curso. Volvemos a abrir el navegador e introducimos de nuevo la dirección anterior, estudiamos la página index.do. Los datos y los enlaces son correctos. Cada ocasión que refrescamos la página si en el 18 Se escogen 30 segundos para hacer las pruebas cómodamente. Este valor debería tener en cuenta si hay usuarios conectados en ese momento en ella, para descargar con mayor o menor frecuencia. 86 buzón ha recibido nuevos mensajes o si hemos creado un nuevo mensaje, aparece actualizado el contador de mensajes de la lista implicada, y la fecha u hora del último mensaje bajado. Si creamos una nueva lista al volver a index.do aparecerá en la relación, con el número de mensajes a cero y un usuario registrado, el propio fundador. CDU 4. Consulta datos lista Este caso se da cuando consultamos las propiedades de una lista, por ejemplo desde index.do al hacer clic sobre “descripción” de una lista. El funcionamiento de este CDU se observa en dos páginas: lista.do y lista2.do. Los datos en ambas páginas aparecen correctos y sus enlaces funcionan. En lista.do se comprueba que sólo como usuario fundador se tienen los cuadros de texto habilitados por si se desea hacer algún cambio. Cualquier otro usuario los encuentra desactivados. En lista2.do, los enlaces que permiten la expulsión y readmisión de usuarios en la lista sólo se presentan al fundador. Cualquier otro usuario encuentra listados de sólo lectura. Seguridad: Intentos de acceder directamente a las páginas sin pasar por index.do (donde se inicia la sesión) muestran en lista.do un formulario en blanco de nueva lista que no permite guardar y en lista2.do un página en blanco. Introducir direcciones lista.jsp y lista2.jsp con o parámetros o sin ellos produce resultado en blanco. Con la sesión iniciada, desde cualquier lugar de la aplicación introducir lista.do?idLista=2 &return=index.do nos mostrará la lista pero con los derechos que tengamos, ya sea usuario normal o fundador. Igual con lista2.do. CDU 5. Acceso mensajes de listas públicas Acceso Probamos con un usuario expulsado en la lista. Surge un mensaje como el anterior informando además de la expulsión. Pulsamos enlace “entrar” y accedemos a listado mensajes. Probamos con un usuario registrado: accede directamente a listado de mensajes. El acceso se controla internamente y no es posible saltárselo poniendo directamente en la barra de URL la dirección de listadoMails, listadoAttachments, detalle, extiende. En todos estos casos, sólo muestra la página si ya hemos accedido a ella anteriormente, y para la última lista que hayamos visitado. Bien distinto es que haciendo diversas pruebas, escribamos la dirección y obtengamos la página almacenada en la caché del navegador, pero ello es porque hemos accedido antes. Listado Entramos como registrados en una lista que veamos que no tiene mensajes. Indica “no se produjeron resultados”. Hacemos clic en “redactar nuevo mensaje”, y hacemos un mensaje con un adjunto. Aceptamos. Volvemos a la lista y vemos que se ha actualizado correctamente. 87 Introducimos otros mensajes relacionados entre ellos. Elegimos “listado relacionado” formulario de consulta, y vemos como aparecen indentados según la jerarquía en la relación. en el Como hay menos registros que el número de registros por el que se pagina no aparece el índice de páginas. Vamos ahora a “lista principal”, como no registrados a ella. No aparece el enlace “redactar nuevo mensaje”. El número de registros es superior al que se muestra por página, por lo que vemos que aparece un índice de páginas al estilo anterior [1] [2] [3] siguiente . Probamos a movernos clicando en página 1, página 2... probamos anterior , siguiente. Nos aseguramos que no aparezcan páginas en blanco, o que el enlace “anterior” esté desactivado cuando estemos en la primera página o el “siguiente” cuando estemos en la última. Probamos el panel de consulta: probamos orden ascendente y descendiente por todos los campos posibles. Filtramos por varios criterios en orden ascendente y descendiente. Se visa con JavaScript si: dejamos campos en blanco, si al esperar formato fecha se introduce otra cosa, si se colocan letras donde se esperan números. El panel funciona correctamente y el listado que se obtiene es acorde al panel. Al filtrar la consulta, el número de registros puede ser cero, entonces muestra “no se produjeron resultados” o un número inferior al número de registros por página, con lo que no se muestra el índice de páginas, o se muestra un número inferior. Mensaje Elegimos mensajes que estén en los extremos de las páginas: el primer y último mensaje de cada página. Para cada mensaje, recorremos dentro de detalle.do con los enlaces “anterior” y “siguiente”. Se observa que el recorrido a través de la lista es correcto en todos los casos. Los enlaces se desactivan, “anterior” cuando estamos en el primer mensaje y “siguiente” al estar en el último. Al volver a la lista nos encontramos en la página perteneciente al último mensaje que hemos consultado. Por ejemplo, si estábamos en el mensaje de la segunda fila de la página 2 y pulsamos “anterior” tres o más veces al volver nos encontramos en la página uno. Si el mensaje es accedido por un usuario con permiso para responder, correspondiente. El resto de los enlaces están correctos. aparece el enlace Los mensajes con adjunto permite descargarlos. Si el mensaje pertenece a un hilo de discusión, vemos el listado de todos los mensajes relacionados. El mensaje actual aparece en negrita y sin enlace. En el listado del hilo de discusión muestra el mensaje actual en negrita y sin link. Pulsamos en cualquiera de ellos, ahora el recorrido de mensajes es el del hilo de discusión. 88 Extender Pulsamos en [EXTENDER] para la lista de hilo de discusión dentro de un mensaje que tenga relaciones. Muestra todos los mensajes del hilo. Si el usuario tiene permiso, en cada mensaje hay un enlace para responder. La página extiende.do tiene los enlaces correctos. Entramos como usuario registrado, respondemos un mensaje, al volver a extiende.do el mensaje está en su posición correcta dentro de la jerarquía de relaciones. CDU 6. Acceso archivos de listas públicas Acceso El acceso nunca es directamente a la lista de archivos. El usuario accede a la lista de mensajes y a partir de ahí hace clic en el link “archivos”. Listado Escogemos una lista sin adjuntos. Aparece “no se produjeron resultados”. El panel de consulta es idéntico. Escogemos una lista con adjuntos. Los enlaces son correctos. Pulsamos sobre el enlace de nombre de archivo, se abre la ventana del navegador para descarga del archivo, guardamos en el escritorio. Abrimos el archivo: se bajó correctamente. Volvemos a lista, pulsamos ahora sobre el enlace que indica quién envió el adjunto: se abre el mensaje que incluía el adjunto. Probamos con el panel de consulta, lo mismo que en CDU 5, pues es un formulario prácticamente idéntico. CDU 7. Alta usuario Entramos en usuario.do desde el enlace en login.do o bien tecleándolo directamente. Aparece un formulario en blanco, con algunos campos obligatorios. Los enlaces son correctos. En las siguientes pruebas, JavaScript nos avisa de campos en blanco: • • • Pulsamos el botón de submit con todos los campos en blanco. Pruebo a rellenar los campos con espacios. Relleno todos los campos menos alguno o algunos obligatorios. Todos los campos correctos, salvo el de e-mail el cual no tiene formato de correo eléctronico: aviso de JavaScript. Los dos campos de password no coinciden: aviso de JavaScript. Intentamos introducir un alta de un usuario que emplea un login que ya pertenece a otro usuario y nos redirige a una página con el aviso. Probamos una alta válida, nos aparece la misma página por si deseamos modificar algo, y unos nuevos links, conforme al nuevo usuario que acabamos de crear. Los enlaces son correctos. Al producirse el alta, somos logineados automáticamente. 89 CDU 8. Identificarse Entramos en login.do desde el enlace en index.do o bien tecleándolo directamente. Los enlaces a “inicio”, “alta nuevo usuario” o “ayuda” son correctos. Pulsamos directamente sobre el botón del formulario con uno o los dos campos en blanco (ya sea por no haber introducido nada o por haber colocado sólo espacios): una alerta de JavaScript nos avisa que hemos olvidado completar uno o ambos campos. Introducimos login y/o passwords incorrectos: nos redirige a página de “usuario no existe o datos incorrectos”. En dicha página, login_error.jsp (al que accedemos por forward bajo la dirección “checkLogin.do”) probamos ambos enlaces: funcionan. Uno de los enlaces es “probar de nuevo”. Volvemos a página de login. Probamos ahora con entradas válidas de varios usuarios: nos redirige en cada caso a la pantalla de bienvenida. La sesión creada al entrar por primera vez en index.do ya no es anónima, volviendo a esa página comprobamos que es así. No existe manera externa de hacerse pasar por un usuario sin conocer su password. CDU 9. Crear lista La tabla “mailsdisponibles” está vacía. Desde la página de listas creadas por el usuario, usuario3.do, hago clic en el enlace “nueva lista”. Aparece una página avisando que no es posible crear nuevas listas. Agrego un registro en tabla “mailsdisponibles” con la configuracion necesaria para acceder a una cuenta de correo válida: nombre de la cuenta, login, password, y dirección del servidor POP. Esta tabla se supone que tendría un conjunto de registros válidos de forma que el usuario al crear una nueva lista, se le asigne transparentemente la cuenta de correo. En la misma página de antes, volvemos a clicar “nueva lista”. Aparece la página de nueva lista, un formulario en blanco donde especificar las propiedades de la lista. Pruebo a enviar el formulario totalmente en blanco y luego también con uno o varios campos obligatorios en blanco o con espacios: una ventana en JavaScript avisa que los campos señalados con asterisco deben cumplimentarse. Los enlaces de la página de crear lista son los mismos que los de consultar datos lista, y son correctos. Finalmente, pongo los valores correctos y damos a botón de formulario. Si hubiera algún fallo aparecería el mensaje de nuevaListaError.jsp. Pulsado el botón, la página muestra un nuevo enlace a “usuarios registrados”, si lo visitamos veremos que hay un único usuario por el momento, el fundador de la lista. Miramos en la ventana de output de Tomcat: ha aparecido un nuevo thread encargado de descargar los mensajes de la nueva lista. Visitamos index.do, muestra una nueva fila. Enviamos un mensaje a la nueva lista, cuando el thread vuelve a estar despierto recoge el mensaje y lo muestra en el listado de mensajes. Hacemos otra prueba con una cuenta no válida. Seguimos todos los pasos. Observamos en la ventana de Tomcat como avisa que la conexión no es válida, que va a dormir n segundos antes de 90 volverlo a intentar. Como la cuenta es inexistente, lo intentará cinco veces y finalmente el thread permanecerá parado durante una hora. Intentar crear listas sin estar registrado es imposible. En primer lugar no le aparecerá el link para acceder y prueba a entrar introduciendo la dirección en la barra de URL: http://localhost:8080/PFC_Web_Struts/creaLista.do?idLista=0&return=usuario3.do le aparecerá un formulario en blanco pero al pulsar el botón le dará error al crear la lista. CDU 10. Consulta datos usuario Entramos desde el enlace perfil que aparece en index.do en los usuarios logineados. La página es la misma que la de alta usuario. En este caso no está en blanco, sino que muestra los datos generales del usuario. CDU 11. Modificar datos usuario Los datos de CDU 10 se pueden modificar en cualquiera de los campos, salvo login que se mantiene deshabilitado. Se realizan las mismas pruebas que en CDU 7, y se prueban diversas modificaciones válidas en cualquiera de los campos. CDU 12. Registrarse a lista Se siguen los pasos del CDU-1319. Escogemos varias listas en “Listas a las que puedo registrarme” y hago clic en las aspas de registrar. Para cada una, la fila sube a “Listas en las que estoy registrado”. Nos registramos en una lista pública, vamos a la lista de mensajes de dicha lista y vemos que podemos escribir mensajes dentro de la aplicación. Nos desregistramos. Volvemos a intentar redactar un mensaje en la misma lista pero ya no hay enlaces de “responder” en los mensajes o de “redactar nuevo mensaje”. Nos registramos en una lista privada, vamos a la lista de mensajes de dicha lista y vemos que podemos leer y escribir mensajes dentro de la aplicación. Nos desregistramos. Volvemos a la lista, pero tenemos el acceso denegado. Enviamos un mensaje a la dirección de correo electrónico de la lista a una lista pública y sin registrar: el mensaje no se guarda y nos envía un mensaje avisándonos. Enviamos un mensaje a la dirección de correo electrónico de la lista a una lista privada y sin registrar: el mensaje no se guarda y nos envía un mensaje avisándonos. Enviamos un mensaje a la dirección de correo electrónico de la lista a una lista pública y registrado: el mensaje se guarda, se muestra en el listado de mensajes. Enviamos un mensaje a la dirección de correo electrónico de la lista a una lista privada y sin registrado: el mensaje se guarda, se muestra en el listado de mensajes. Nota: el tiempo que tarde en llegar un mensaje a una cuenta, ya sea del destinatario a la lista o de la lista al destinatario no la podemos prever: a veces es al minuto, a los cinco minutos... depende del proveedor del servicio de correo. 19 El orden de estos dos CDU aquí resulta caprichoso, pero seguimos el orden del diagrama de casos de uso donde sí tenía un sentido, porque pertenecían a actores diferentes “registrado” y “registrado en lista”. 91 CDU 13. Consulta listas registrado El acceso normal a este caso es el de un usuario logineado o recién creado que desea acceder a la página “usuario2.do” para comprobar en qué listas está registrado y quizás registrarse o desregistrarse en alguna. Desde usuario.do o usuario3.do por el enlace, vamos a “usuario2.do”. Si es un usuario anónimo no logineado a través de estas páginas no podrá, ya que no está logineado. Si introduce la petición a través de la barra del navegador encontrará una página en blanco. En los casos de usuario recién creado o usuario ya existente la página mostrada tiene todos los enlaces correctos. Para un usuario nuevo, bajo el título “Listas en las que estoy registrado” aparece “No estás registrado en ninguna lista“. Para un usuario que se haya registrado en todas las listas aparece bajo “Listas en las que no estoy registrado”, “Estás registrado en todas las listas”. En esta página se pueden llevar a cabo los casos CDU-12, CDU-14 y CDU-15. CDU 14. Desregistrarse de lista Se siguen los pasos de CDU-13. Escogemos una lista cualquiera dentro de “Listas en las que estoy registrado” hacemos clic en “baja” y baja a “Listas a las que puedo registrarme”. El resto de pruebas a realizar son las mismas que en CDU-12. CDU 15. Recibir o no mails lista por correo Escogemos una lista registrada de CDU-12. Al registrarnos en una lista por defecto la recepción de la lista por correo es “No”. Hacemos clic en la fila “estado email” se pone a “Sí” Enviamos un mensaje a la lista por correo y otro desde la web de la aplicación, ambos por otro usuario registrado, y al minuto abrimos cuenta correo del usuario registrado: recibe los mensajes enviados a la lista por el otro usuario. Sin embargo, si abrimos la cuenta de correo del usuario que escribió los mensajes vemos que no los ha recibido, ya que sólo se reciben los mensajes del resto de participantes, no los de uno mismo. CDU 16. Acceso mensajes de lista privadas Probamos el acceso de un usuario no registrado. Aparece página de acceso denegado informando que debe registrarse para leer o escribir mensajes. No podemos entrar en el listado. Probamos el acceso de un usuario expulsado. Aparece página de acceso denegado informando fecha de la expulsión y motivos. No podemos entrar en el listado. Nos logineamos como un usuario registrado a la lista y sin expulsión: acceso directo. El resto de apartados es idéntico a CDU 5 92 CDU 17. Acceso archivos de listas privadas Idéntico CDU 6. La lista de archivos de listas privadas es a través del CDU 16, por lo que si no hemos sido aceptados tampoco podremos entrar aquí. CDU 18. Enviar mensaje desde web Redactamos nuevo mensaje por web desde listado de mensajes. Redactamos de nuevo desde listado de mensajes con adjunto que supera el máximo aceptado: se avisa de la restricción en el tamaño de los adjuntos y no se permite guardar. Probamos con uno con un tamaño tolerado, se guarda y se envía el mensaje con adjunto a los participantes que solicitan reenvío. Los mensajes se reenvían, salvo si la lista tiene un solo usuario que sólo se guarda en la base de datos pero no se envía por correo al usuario que ha escrito en la web. Probamos a redactar mensajes como respuestas a mensajes ya existentes en la base de datos. Se relacionan correctamente. CDU 19. Enviar mensaje por correo Este CDU está relacionado con CDU-12 y CDU-14. Como ya han sido probados, faltará probarlo para un usuario expulsado, en el CDU-22. Se prueba que no se pueda enviar mensaje mayor de 500 KB. Esta restricción no la aplicaremos a la lista de ejemplo. El remitente recibe correo de aviso que su mensaje no ha sido guardado en la lista. Probamos que no admite un mensaje mayor de 500KB, salvo “lista principal”. CDU 20. Consultar listas creadas Desde el enlace de perfil, llegamos a una página con una pestaña con el título “listas creadas”, la pulsamos. Muestra todas las listas de las que el usuario es fundador. Todos los enlaces están correctos. CDU 21. Modificar datos lista La página desde donde se realizan las modificaciones es lista.do, la misma de CDU 4. Sólo el fundador encuentra ciertos cuadros de texto activados. Se comprueba que se puede modificar por cualquiera de ellos. De igual modo, dejar en blanco campos obligatorios lanza un aviso de JavaScript. Al cambiar la naturaleza de la lista de pública a privada se pueden observar las restricciones de acceso en usuarios no registrados. 93 CDU 22. Expulsar usuario lista Desde la página de usuarios registrados en lista en la que hemos entrado como fundador, escogemos un usuario y hacemos clic en expulsar. Una vez en la página de expulsión, decidimos que no queremos sancionarlo, pulsamos en “volver” y vemos como el usuario sigue en la lista de arriba. Volvemos a escoger un usuario. Clicamos en expulsar. En la página decido no explicitar un motivo al darle al botón de submit. Un mensaje en JavaSript nos avisa. Esta vez, exponemos un motivo. Pulsamos lo que nos lleva a la página anterior. Comprobamos que el usuario se encuentra en la lista de abajo. En otros CDU’s ya hemos comprobado qué sucede con los usuarios expulsados. Expulsamos todos usuarios y la lista queda vacía con el mensaje “no hay ningún usuario registrado en la lista”. CDU 23. Readmitir usuario lista Escogemos cualquiera de los usuarios antes expulsados, y hacemos clic en “readmitir”. Realizando otros CDU vemos que es usuario ha sido rehabilitado. Intentamos ahora expulsar o readmitir algún usuario a través de la URL sin ser el fundador. Probamos tanto como usuario anónimo como usuario registrado en lista pero no fundador introduciendo “banear.do?login=nuria&idLista=2&accion=alta &return=index.do “ en ambos casos muestra formulario pero no permite expulsar. 94 6. CONCLUSIONES Struts implica un trabajo extra en la conversión de una aplicación puramente en JSP, ya que deben implementarse varias clases Action y beans de formulario adicionales. Aun así, la ventaja de poder separar entre capas es evidente desde el primer momento, puesto que facilita el mantenimiento y la legibilidad. En el modelo de JSP todo lo referido a una página se encuentra apelmazado dentro de ella, y pese al empleo de beans y de bibliotecas de tags para atenuar este inconveniente, código y presentación siguen yendo juntos. Sin embargo, gracias a Struts, la separación es fácil y natural: cada elemento tiene su sitio y así es más fácil encontrarlo y depurar errores. La presentación corresponde a las páginas JSP, el negocio a clases que no son accedidas directamente desde la página. Struts hace de mediador entre unos y otros proporcionando además un pool de conexiones a las clases de negocio. Durante el desarrollo del proyecto también se ha constatado la dificultad que entraña blindar una aplicación frente a la innata capacidad del ser humano a equivocarse, ya sea usuario o programador, muy en especial los mensajes descargados del buzón de correo. La casuística de mensajes mal formados es afortunadamente baja pero diversa y son en principio por uno de estos dos motivos: ausencia de algún header en el mensaje y sobre todo por errores de usuarios, tal como se explicó respecto a tabla “huérfanos”, en las decisiones de bases de datos. Dos ojos nunca son suficientes para testear una aplicación. 95 7. RECURSOS UTILITZADOS BIBLIOGRAFÍA • HTML y CGI, José Manuel Framiñán Torres, editorial Anaya Multimedia, 1997. • Programming Jakarta Struts, Chuck Cavaness, editorial O’Reilly. • Apuntes de Enginyeria del Software, Benet Campderrich, para la realización de este documento. PÁGINAS WEB He visitado un buen número de páginas web. Como es lógico, tuve que filtrar entre diversos contenidos de escaso interés. Entre las páginas que he tenido en consideración, ya sea porque me han aportado en ocasiones pequeñas ideas útiles, información sobre clases, sobre librerías, o ya sea para desestimar ciertas líneas de actuación se encuentran las siguientes: En primer lugar, la documentación más estudiada ha sido, sin duda, la proporcionada en las webs de MySQL, Jakarta-Tomcat y las API’s de Java y JavaMail. Además debo mencionar, clasificando por temas: Java, general: http://mindview.net/ , “Thinking in Java” de Bruce Eckel. http://www.cica.es/formacion/JavaTut/Cap7/comunica.html , comunicación entre threads. http://www.programacion.com/java/articulo/expresionesreg/ , expresiones regulares en Java. http://www.programacion.com/java/tutorial/jdcbook/ , tutorial de Sun traducido al castellano sobre uso de servlets y JDBC, sesiones, entre otros, con un ejemplo de una Casa de Subastas on line. http://www.sc.ehu.es/sbweb/fisica/cursoJava/applets/javaBeans/fundamento.htm , introducción a los JavaBeans. http://www.lab.dit.upm.es/~lprg/material/apuntes/log/log.htm , sobre el uso de loggers para depuración. JavaMail: http://www.javaworld.com/javaworld/jw-10-2001/jw-1026-javamail.html , introducción a la funcionalidad de JavaMail. http://www.jguru.com/faq/home.jsp?topic=JavaMail&page=3 , conocer Message-ID de un mensaje antes de enviarlo. http://www.echomountain.com/support/JavaMailAPI.html , autenticación en servidores SMTP que lo requieren. 96 http://java.sun.com/products/javabeans/glasgow/jaf.html , información sobre JAF, Java Activation FrameWork, usado por JavaMail. http://www.jguru.com/forums/view.jsp?EID=1077307 , JavaMail y los proxies. JSP: http://www.ciberteca.net/articulos/programacion/arquitecturajsp/ , arquitectura JSP y acceso a base de datos. http://www.desarrolloweb.com/articulos/832.php , comparando JSP con ASP http://www.programacion.com/foros/6/msg/49637/, breve respuesta a cómo controlar número de sesiones abiertas. http://www.javahispano.org/articles.print.action?id=83, depuración de errores en JSP. Posibilidad de redigirir a una página de “error”. http://www.codigoescrito.com/archivos/000075.html, sobre las ventajas e inconvenientes de recuperar los datos en un array de campos. Una ventaja código más legible y rápido de crear y una desventaja, si alteramos orden campos en la base de datos hay que retocar donde se reciba este array. http://forums.devshed.com/search.php?searchid=241155, descarga de un attachment con JSP. http://www.programacion.com/tutorial/jspyxml/ , tutorial de Sun traducido al castellano sobre el desarrollo de aplicaciones web JSP y XML. http://www.wmlclub.com/articulos/jsp.htm , ventajas de JSP frente ASP. STRUTS: http://struts.apache.org/ , descarga del framework, ejemplos , tutorial, FAQ’s, ... http://programacion.com/java/tutorial/joa_struts/ , iniciación a Struts. http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=strutsb , tutorial con una breve aplicación de ejemplo. http://www.reumann.net/struts/main.do , otro tutorial con ejemplos. http://j2ee.masslight.com/index.html, tutorial de JSP , Struts y EJB’s. Detalla los principales tags de Struts. http://www.programacion.com/bbdd/articulo/ale_poolstruts/ , pool de conexiones en Struts. http://www.informit.com/articles/article.asp?p=23734&redir=1 , configuración del descriptor strutsconfig.xml. http://www.mmbase.org/download/builds/2003-0601/mmdocs/administrators/webxml onfiguration.html , configuración descriptor web.xml. JDBC: http://www.programacion.com/java/tutorial/jdbc/ , tutorial de jdbc de Sun traducido al castellano. 97 http://dev.mysql.com/doc/connector/j/en/, sobre el conector JDBC de MySQL. Javascript: http://www.desarrolloweb.com/articulos/705.php?manual=26 , estudiando las librerías de funciones de JavaScript. http://javascriptkit.com/javatutors/re3.shtml , expresiones regulares en JavaScript. Estudio de listas existentes: http://www.phpbb.com/ , foros en PHP. http://www.elistas.net/es/ , ejemplo de lista distribución donde estudiar estilo y características. http://yahoo.es , analizando los Yahoo Groups. http://www.mail-archive.com/, ejemplo de lista de distribución más sencillo que el mío, en aspecto y funcionalidad HTML: http://www.duiops.net/curso/ , curso de html. http://geneura.ugr.es/~pedro/dhtml/dhtml3.htm , estudio de los layers en HTML por si los empleaba para mostrar mails largos haciendo correr la capa dejando fijo el cursor sobre un botón para avanzar el scroll... al final se optó por hacerlo como en cualquier web: si mail es largo que vayan haciendo el scroll vertical del navegador. http://www.webexperto.com/articulos/articulo.php?cod=94 , idem. anterior layers y scroll. http://www.baby.com.ar/doc/protocolos_correo.html , información sobre tipos MIME. http://www.math.northwestern.edu/~mlerma/iworld/correo.html , idem sobre MIME. http://www.w3.org/TR/REC-html40/interact/forms.html , explicado en profundidad, según el estándar de W3C. controles de un formulario HTML http://www.webexperto.com/articulos/articulo.php?cod=134 , sobre hojas de estilo en cascada, CSS. Otros: Muchas de estas direcciones ofrecían productos o tecnologías que yo no iba a emplear pero que me llevaron a dirigirme a otros lugares. http://bulma.net/body.phtml?nIdNoticia=770 , tutorial de expresiones regulares de PERL. Servía como iniciación al tema. http://www.hallogram.com/jmail/index.html , en mi búsqueda de información sobre envío de correos encontré una utilidad de pago llamada Jmail que desestimé. http://java.sun.com/products/javamail/Third_Party.html , utilidades de mail creadas por empresas ajenas a Sun. En su mayoría de pago. Desestimadas. 98 http://cr.yp.to/immhf/thread.html , sobre como se realiza un seguimiento de un hilo de discusión: Message-ID, References, In-Reply-To. http://www.guiffy.com/diff_java_h.html , algoritmo diff en Java de comprobación de diferencias entre dos ficheros de texto. Se rechazó por dos motivos: primero, porque al ser muy complejo era también propenso a fallos, y porque comportaría un coste computacional alto teniendo en cuenta que en mi proyecto se aplicaría en mensajes que no incorporen en su header los campos In-Reply-To o References pero que aún así observáramos que refieren a algún otro mensaje leyendo su contenido. ¡Ello implicaría aplicar diff a esa mensaje y a los restantes de la base de datos! http://www.oscookbook.com/index.pl/las_caractersticas_de_webgui , Perl, por lo tanto se aleja de mis intereses. creación de foros emplea http://www.karneim.com/jrexx/project01/project01.htm , librería de expresiones regulares para java, llamado jrexx. Desestimado cuando descubrí que java dispone una desde la versión 1.4. http://jpinedo.webcindario.com/doc-paginator.php , paginador de resultados en PHP. Buscaba uno en jsp. No sirve. http://www.glocksoft.com/ep , utilidad shareware que procesa y filtra de forma automática mensajes de correo entrante. No me es útil. http://www.cica.es/formacion/JavaTut/Cap9/smtp.html , implementación de un cliente mínimo de SMTP mediante sockets y escribiendo uno mismo los comandos que espera el protocolo. Como empleamos JavaMail, sería absurdo descender a ese nivel. http://www.softonic.com/ie.phtml?n_id=9189 , Advanced Maillist Verify 4.25. Programa shareware que verifica si las direcciones de e-mail son válidas. No es java. http://www.programacion.com/java/articulo/paginacion_asp/, ejemplo paginación con ASP. http://www.linux.or.cr/listas/archivo/gulcr_200002/msg00074.html, mensaje de una lista sobre la Netiquette en foros y listas. http://www.ucm.es/info/dsip/Docencia/P-Concurrente/monit.html , concurrencias. http://www.programacion.com/tutorial/aplic_jsp/ , tutorial sobre crear aplicaciones web con Tomcat-4 y MySQL. SOFTWARE Marco de trabajo de la aplicación: • j2sdk_1.4.2, Java ( jre no es válido, Tomcat necesita compilar clases) • jakarta-tomcat-5.0.24, contenedor de servlets. • MySQL 4.0.14, servidor y cliente de bases de datos. • Integrado en el WAR (Web Application Archive): o JavaMail, gestión de correo electrónico. o jakarta-struts 1.1, modelo vista-controlador, connection-pooling y uploading de archivos de cliente a servidor. 99 Entorno de desarrollo: • JBuilder 9 en la compilación, depuración de Actions de Struts y clases Java, . • SQLYog v3.52 para consultas y modificaciones rápidas en la base de datos. • DreamWeaver MX para aspecto externo páginas JSP. • Paint Shop Pro 6.00 , en la elaboración del manual: retoque capturas imágenes, escala de grises, redimensionar. • Pacestar UML Diagrammer Version 4.17, en la elaboración de los diagramas del manual. Pruebas en los navegadores Explorer 6, Mozilla 1.5, Opera 7.23 bajo Windows XP, y Opera, Linux y Konqueror bajo SuSE Linux 8.2 Pro. Las pruebas más exhaustivas se han realizado con Explorer y Mozilla en Windows XP. HARDWARE El ordenador empleado en el desarrollo y los test ha sido un Athlon con 1GHz y 256MB bajo Windows XP y SuSE 8.2. Principalmente bajo el primer sistema operativo. Funciona sin problemas. En un P-III 850 MHZ , 64MB y Windows XP , la aplicación iba forzada al compilar JSP porque el sistema operativo dejaba poca memoria de trabajo. En el administrador de tareas java consumía por término medio unos 15-19 MB ( más que por la aplicación, la memoria la ocupa Tomcat, el JVM...) con picos de hasta 25 MB al compilar Tomcat JSP bajo demanda. También se probó en un ordenador de la universidad bajo Windows 98, del cual no se recuerdan las características pero creo recordar similares a la del Pentium III. Funcionaba bien. 100 8. MANUALES 8.1 INSTALACIÓN La instalación es algo laboriosa, ya que no se trata tan solo de un ejecutable sino que se requiere además un marco de trabajo asociado. Al ser una aplicación Java que emplea MySQL y Tomcat, presentes en varias arquitecturas y sistemas operativos, no debiera suponer una especial complicación instalarlo bajo Linux. Las primeras pruebas se realizaron tanto en este sistema como en Windows. No obstante, hace tiempo que centré su desarrollo bajo Windows XP. La instalación ha sido probada en Windows 98 (universidad) y Windows XP. Todo el material necesario se encuentra en el cedé. Instalación en windows. 1. Si no existe en el sistema una versión de java 1.4.2 ó superior ( probamos en la línea de comandos “java –version”) copiamos instalable en escritorio e instalamos java en directorio C:\cuberes\jdk. 2. Si no existe en el sistema versión de jakarta-tomcat-5.0.24 ó superior: copiar zip en escritorio y descomprimir en C:\cuberes. (si existe tomcat, pero versión java es anterior a la 1.4.2 o no sabemos versión tomcat, realizar pasos 1 y 2) Establecer variables de entorno: • • CATALINA_HOME=”c:\cuberes\jakarta-tomcat-5.0.24” JAVA_HOME=”c:\cuberes\jdk” En Windows NT y XP se establecen en: panel de control sistema opciones avanzadas botón variables de entorno. En windows 98, hay que editar el fichero autoexec.bat, cada línea anterior debe ir precedida por SET. 3. Grabamos mysql-connector-java-3.0.11-stable-bin.jar en la carpeta de tomcat common\lib. (struts-config.xml no lo ve si está en el WEB-INF\lib del WAR) Desconozco si este archivo es válido para versiones de MySQL superiores a la 4.0.14. 4. Si no existe MySQL 4.0.14, copiar zip en escritorio, descomprimir en c:\cuberes\temp, ir a la carpeta y ejecutar setup. Debe instalarse en c:\mysql (directorio por defecto). En C:\mysql\bin hacemos clic en winmysqladmin. Si todo está correcto aparecerá un semáforo verde en la barra de iconos. Si no, siempre podemos probar a arrancar el demonio directamente en C:\bin\mysqld.exe 5. Script de base de datos: No podemos inicializar la base de datos desde la aplicación, ya que lo primero que se lee es struts-config.xml y en ella se hace mención a la base de datos pfcweb_sistema. Por lo tanto, la base debe estar creada antes de arrancar el proyecto. 101 Cogemos el script creaBD.sql y la colocamos en C:\mysql\bin. En la línea de comandos para C:\mysql\bin escribimos mysql. Se abre un cliente de mysql. Escribimos “\. creaBD.sql”. Mostrará una serie de lineas conforme va creando las tablas. Salimos con “quit”. 6. Coger el archivo PFC_Web_Struts.war y colocarlo dentro del directorio de tomcat webapps. Funcionamiento Arrancamos tomcat, en el directorio bin “startup”. Abrimos el navegador e introducimos http://localhost:8080/PFC Web Struts/ (escrito así PFC_Web_Struts) con lo que se pondrá en marcha la aplicación. Una vez hechas todas las pruebas, cerramos Tomcat en el directorio bin con “shutdown”, porque no basta con cerrar el navegador. Nota para windows 98: es muy probable que bajo este sistema operativo, al intentar arrancar tomcat, dé un error de insuficiente memoria para las variables de entorno. En tal caso deberemos aumentarla mediante este comando en el CONFIG.SYS: SHELL=c:\windows\command.com/E:1024/P. Y luego reiniciar. En las peticiones a las páginas JSP podemos encontrar en principio una ligera lentitud (dependerá de la memoria y la potencia de la máquina), ya que Tomcat para la primera vez que ejecuta una página, compila un servlet que será quien atienda las peticiones. Este servlet lo guarda en su directorio de trabajo, de forma que para siguientes peticiones a la misma página se accede directamente al servlet y el tiempo de respuesta es mucho más rápido. 102 8.2 MANUAL DE USUARIO Inicio Página de inicio a nuestro servicio de listas de distribución. Se muestran todas las listas disponibles, cada una con su nombre, una breve descripción, si es pública o privada, el número de mensajes que contiene, el número de usuarios registrados a la misma y la fecha de recepción del último mail, o bien si el mail ha sido recibido el mismo día, su hora de llegada. Para obtener mayor información sobre la lista, pulse el link que aparece bajo la columna “descripción”. Igualmente, los links bajo la columna “nombre” le dirigirán, en caso de tener acceso, al listado con el total de mensajes existentes de la lista escogida. Cabe indicar que usted al conectarse entra como “invitado”, lo cual le permite visitar únicamente las listas públicas y sin posibilidad de introducir mensajes. Si desea un acceso pleno a todas las listas, debe registrarse, para lo cual haga clic en el link de login y dese de alta. Si ya es usuario registrado, entre en login igualmente e introduzca su login y password. En caso de duda, pulse en ayuda cuando se encuentre ya en la página de login. Recuerde que en caso de dudas, en cualquier página que visite encontrará el link “ayuda”. 103 Una vez registrado puede acceder a cualquier lista pública o privada si se ha dado de alta en ella. En la imagen se observa un usuario con login “alex”. Al identificarnos y volver a esta página de inicio observaremos que aparece un link al lado de nuestro login, “perfil”, con el que podemos acceder a nuestra cuenta. Login Si usted ya es usuario registrado, introduzca su login y password. En caso contrario tiene dos opciones: La primera es pulsar en “volver” y mantenerse como usuario invitado, lo cual le permite visitar sólo las listas de tipo público, sin posibilidad de escribir mensajes. La segunda es darse de alta, haciendo clic en el link correspondiente en un proceso que no le llevará ni treinta segundos. Siendo usuario registrado, podrá más adelante registrarse en las listas que le interesen, escribir y leer mensajes desde nuestra web o desde su correo electrónico e incluso crear su propia lista. 104 Alta usuario En el momento de introducir sus datos personales requerimos como mínimo una dirección de correo electrónico, el login con el que quiera identificarse para siempre y ser conocido por el resto de los miembros de las listas a las cuales pertenezca y una contraseña. Sin completar estos campos, no será posible darle de alta. Una vez haya pulsado “ENVIA” ya estará registrado en nuestra web, y abierto la sesión con dicho login. Observará entonces que la página cambia el nombre y pasa a llamarse de “Alta usuario” a “Perfil usuario”, al mismo tiempo que aparecen junto a la pestaña de “datos personales” otras dos llamadas “listas registradas” y “listas creadas”. Para mayor información, lea la siguiente sección Modificación usuario. Si por el momento no desea darse de alta en ninguna lista puede pulsar “inicio” que le llevará a la página principal donde aparecerá no ya como “invitado” sino como el login que usted ha elegido. 105 Modificación usuario datos personales En cualquier ocasión, ya sea justo en el momento de darse de alta por primera vez, como en cualquier otro día que acceda a la web puede acceder a su perfil. Si acaba de darse de alta, ya ha sido dirigido ahí. La primera página que se le muestra es la de sus datos personales, donde puede modificarlos o bien gestionar su registro en listas o crear las suyas propias, si el servicio está disponible. Dentro del apartado de datos personales puede modificar cualquier elemento que considere oportuno, como un cambio en la dirección de correo electrónico o un cambio de password, o cualquier equivocación que haya tenido y desee rectificar. Recuerde que hay unos campos mínimos que deben ser rellenados y que no puede modificar el login que lo identifica exclusivamente a usted. Cuando haya acabado, pulse en inicio. Si lo que desea es gestionar las listas a las que pertenece o crear una propia, pulse para lo primero “listas registradas” y para lo segundo “listas creadas”. 106 Registro en listas “Listas registradas” nos muestra las listados a las que estamos apuntados en la parte de arriba y a las que podemos apuntarnos en la parte de abajo. La primera vez que entramos, no estamos registrados en ninguna lista, por lo que bajo el título “Listas en las que estoy registrado” no aparecerá ninguna fila. Para registrarse, basta que clique el link en forma de [X] perteneciente a la lista que le interese. Si desea informarse antes, clique en el campo descripción de la lista. Una vez registrado a la lista, que aparecerá ya en la parte de arriba, puede desregistrarse si se dio de alta por equivocación o bien ha decidido que no quiere pertenecer a la lista y que tampoco desea recibir ni enviar correos de la misma. Para ello pulse en la aspa [X] en “baja”. Perteneciendo a una lista, puede leer sus mensajes aunque sea privada (privada se refiere a que requiere registro incluso para su lectura), y puede también escribir en ella, ya sea desde nuestra web, como a través de correo electrónico, siempre que el correo enviado coincida con el que usted nos ha indicado en el apartado de datos personales. Para escribir en una lista siempre hay que estar dado de alta en ella, ya sea pública o privada. Si no lo está, desde nuestra web no se le permitirá redactar mensajes. Y externamente, cualquier mensaje con dirección de correo electrónico no registrada en nuestra base de datos será ignorado. Exigimos 107 registro, para evitar el acceso incontrolado a las listas y evitar un mal uso por parte de elementos como spammers. Por último, si desea recibir en su dirección de correo electrónico todos los mensajes que son escritos en la lista, clique en el aspa bajo la columna “estado mail”. Obsérvese que por defecto, al darse de alta en una lista aparece como que no desea recibir los mensajes de la lista en su buzón de correo. Aparece como un “No” de color rojo. Si pulsó sobre el aspa, cambiará a “Sí” en verde. La decisión de recibir correo o no puede cambiarla cuando desee y es independiente de una lista a otra. Puede disponer de supongamos cinco listas, y desear recibir correo en una, dos... todas o ninguna. Si ha terminado, vuelva a “inicio”. Si desea crear una lista o ver las listas creadas, pulse en “listas creadas”. Creación de listas Podemos acceder a la lista directamente, o ver sus características, que podemos modificar, ya que somos sus creadores. El servicio de creación de listas puede estar desactivado momentáneamente. Si podemos crear una lista nueva, aparecerá una imagen como en la siguiente página. 108 En ella, especificamos un nombre corto que sea lo más identificable posible con el tema sobre el que deseamos crear la discusión. A mostrar junto al nombre de la lista en muchas páginas encontramos una descripción breve. Con el fin de poder explicar los fines de la lista de un modo más detallado, tenemos el campo “comentario”. Los campos fundador de la lista, email de la lista (por si deseamos participar en la lista externamente, mediante el correo electrónico) y fecha creación se rellenan automáticamente. Por último decidimos si nuestra lista debe ser pública o privada. Si en algún momento decidimos echarnos atrás, como en todas las altas, podemos pulsar el link “volver”. En caso afirmativo, pulsamos el botón de alta. Esto nos llevará a la misma página en modo de modificación, en la que podemos cambiar cualquiera de los tres campos o el tipo de lista. 109 Desde muchas páginas que ofrecen una relación de listas de distribución podemos acceder a esta página clicando en el campo “descripcion”. Si nosotros somos los fundadores de la lista, la encontraremos en modo de modificación. Es por ejemplo el caso de haber creado la lista, volvemos a la página con los campos automáticos ya rellenados. Las modificaciones únicamente se guardarán si pulsamos en el botón de modificar. De no ser los creadores de la lista, entramos en modo consulta, tal como muestra la anterior imagen en la cual no es posible cambiar los campos, ni tampoco encontraremos ningún botón. En ambos casos encontraremos una pestaña “usuarios registrados” donde se muestran los usuarios de la lista. 110 La página de usuarios registrados a una lista es accesible a todos. En el caso que seamos el fundador tendremos pleno derecho sobre el acceso de usuarios que a nuestro parecer no muestren la debida consideración hacia los demás. Esta misma página accedida por un usuario corriente no muestra los enlaces “readmitir”, tal como veremos en la siguiente imagen. 111 “expulsar” y Expulsión, readmisión de usuarios En la captura anterior, usuarios registrados visto por el fundador, permite la expulsión y readmisión de usuarios. El usuario expulsado no podrá enviar ni recibir la lista por correo electrónico, ni tampoco escribir mensajes en la web. Si la lista es pública podrá leerla como cualquier usuario invitado, si es privada no podrá acceder. Página empleada para expulsar a alguien si se es fundador, así como para ver los motivos de su expulsión, si se es usuario. 112 Si es fundador de la lista y va a expulsar un usuario, los tres primeros campos son rellenados automáticamente. Usted debe indicar un motivo de su decisión. Al pulsar “modifica” la expulsión se lleva a cabo. Siempre podrá readmitirlo más adelante. De haber entrado en esta página por error, pulse en “volver”. Un usuario no fundador puede consultar esta página, sin posibilidad de hacer cambios. El fundador puede cambiar el texto con los motivos en cualquier momento. 113 Si el usuario expulsado accede a una lista se le mostrará una página, llamada de acceso que le informará que ha sido baneado. Si la lista es pública podrá acceder, con los derechos limitados tal como hemos comentado antes. Si la lista es privada, esta página le impide el acceso. De hecho esta página también impide el acceso a los usuarios no registrados a una lista privada. En el caso que un usuario no tenga acceso vía web a la lista de mensajes, tampoco podrá enviar mensajes a través de un cliente de correo electrónico. De hacerlo, su mensaje no será incorporado en la lista, y el remitente recibirá un correo informativo en el cual se le explicarán los motivos. A continuación, veremos las listas en sí mismo. 114 Lista de mensajes Al acceder desde el link con el nombre de la lista que puede encontrarse en muchas páginas, como la de inicio, encontramos la página mostrada sobre el texto. Podemos observar un panel para la ordenación o filtrado de la lista. Para la ordenación debemos activar el cuadro de opción de “listado”, el mismo que se obverva en la imagen. El listado puede mostrarse “normal” o “relacionado”, que es la forma de mostrar la relación entre los mensajes y sus respuestas. Por defecto, se muestra “normal” ordenado por fecha descendiente, con lo cual los mensajes más recientes son los primeros a mostrarse. No obstante, puede cambiarse el orden de presentación, “ascendente” o “descendente”. También existe la posibilidad de ordenar por otro campo que no sea la fecha como puede ser los mostrados en el cuadro de lista: “de”, “asunto” o “tamaño”. 115 A continuación mostramos un ejemplo de listado relacionado: Las cruces indican el nivel del mensaje dentro de lo que llamaremos el hilo de discusión, esto es, un conjunto de mensajes relacionados entre sí. El primer mensaje que inicia la cadena, será de nivel uno, por lo que no mostrará ninguna cruz. Quienes responda a este primer mensaje tendrán “++”. Si luego a su vez son respondidos, el mensaje de respuesta tendría “+++”, etcétera. Si lo que deseamos es filtrar los resultados ya que buscamos un mensaje o conjunto de mensajes. Tenemos la posibilidad de hacerlo también por los campos de “fecha”, “de”, “asunto” o “tamaño” y ordenarlos en orden ascendente o descendente. 116 En este ejemplo buscamos por fecha. Los filtrados por fechas o por tamaño son por un rango, mientras que para “de” o “asunto” se introduce en el campo “contiene” del formulario la palabra que buscamos. En este caso, al ser únicamente 9 registros, no mostramos el índice de páginas. Como el usuario ha entrado en una lista pública sin registrarse, no puede introducir mensajes nuevos ni responder a los existentes. Sí puede leerlos, clicando en el enlace bajo la columna “asunto”. Mostramos ahora un ejemplo, de la ventana para un usuario registrado que busca registros que procedan de remitentes que contengan la letra elle. 117 A la izquierda del remitente, vemos unos clips para los mensajes que tienen algún adjunto. También podemos observar que al entrar como usuario registrado aparece un link para introducir un mensaje nuevo. 118 Mensaje Página que aparece tras clicar “Redactar nuevo mensaje”. Si no deseamos finalmente enviar ningún mensaje, podemos clicar en “volver”. Para enviar un adjunto, puede escribirse la ruta directamente en el campo “adjunto” o bien abrir una ventana para movernos por los directorios de nuestro ordenador pulsando el botón “Examinar...”. La página es la misma si el mensaje a componer es respuesta de un mensaje anterior en la lista, con la salvedad que el asunto tendrá un formato de la forma "Re[..]:.. (asunto original)", que puede variarse, y en el contenido aparecerá el mensaje original. Después de pulsar en “ENVIA” se nos informará que el mensaje ha sido enviado, con lo cual podremos volver a la lista o bien redactar otro mensaje. El mensaje enviado se mostrará en la web y se enviará por correo a todos los usuarios que hayan solicitado dicho servicio, salvo al remitente del mensaje. Si en la lista, clicamos sobre el mensaje, nos mostrará la siguiente página: 119 Los links “anterior” y “siguiente” son para movernos por los mensajes a través de la lista. En este caso, este mensaje es el primero de la lista, ya que nos encontramos en una lista con sólo 2 mensajes ordenada en descendiente. Por ello, el link a anterior está desactivado. Podemos movernos a través de la lista entera pulsando estos dos links. Si nos encontramos al final de una página y seguimos pulsando siguiente, automáticamente saltamos a la página que sigue, y cuando volvamos a la lista nos mostrará la página donde se encuentra el último mensaje que examinamos. Igualmente sucede con anterior. Otros links son “inicio”, que nos llevaría a la página inicial, “lista” que nos devuelve a la página anterior, y “responder” para contestar el mensaje. Como este mensaje incluye un adjunto, aparece un cuadro donde se muestra su nombre y tamaño y un botón para descargarlo, si el usuario lo desea. Supongamos que deseamos responder a este mensaje, entonces nos llevará a la página de redacción de mensajes donde automáticamente el asunto estará encabezado por un “Re:” y aparecerá el texto 120 del mensaje que respondemos precedido por “>” en cada línea. Si lo que replicamos es ya un “Re:” entonces nos aparecerá “Re[2]:”, y si el mensaje es de nivel n, “Re[n]”. Al volver, si el listado estaba relacionado, encontrará su mensaje que aparece dependiente del mensaje que respondió. Todos los mensajes, procedan de un listado “normal” o “relacionado” muestran el hilo de discusión (grupo de mensajes relacionados) al que pertenecen, de pertenecer a alguno. En la imagen anterior, el mensaje no pertenece a ningún hilo. En esta imagen podemos observar el final de la página de un mensaje que pertenece a un hilo. El mensaje al que hemos accedido se muestra en negrita. Los hilos se pueden “EXTENDER” con lo cual vemos el total de mensajes del hilo en una sola página. También podemos pulsar en un mensaje en concreto, o movernos entre los mensajes Hay que señalar que los links “anterior” y “siguiente” de un mensaje nos permiten navegar por el listado de mensajes que no tiene por qué coincidir con el listado del hilo. Si provenimos de un listado relacionado sí coincidirá, pero también podemos venir de un listado al que se le ha aplicado un filtro, por ejemplo. Si lo que deseamos es navegar entre los mensajes del hilo , podemos hacerlo clicando en cualquiera de los enlaces del hilo. En este caso accedemos a una página similar a la anterior, pero que tiene como 121 título “Recorrido del hilo”. En ese caso, al pulsar “anterior “ o “siguiente” sí nos moverá entre los mensajes de dicho hilo. Cuando queramos volver al modo anterior, pulsar en “volver”. Listado archivos Desde la página de listado de mensajes, haciendo click sobre “archivos” El panel de búsqueda de los registros es similar al que se encuentra en el listado de mensajes, por lo que no merece más comentarios que el señalar que al buscar por “nombre” o “tamaño”, nos referimos a los atributos del adjunto, y no del mensaje. Para cada archivo, tenemos dos enlaces, el de más a la izquierda permite la descarga del archivo, y el de la derecha acceder al mensaje que adjuntaba el archivo. 122 9. IMPLEMENTACIÓN EN SOPORTE FÍSICO En CD-ROM se incluye: • todo lo necesario para la instalación bajo Windows. • archivo .WAR comprimido y compilado. • directorio PFC_Web_Struts, que es el WAR descomprimido y con el código fuente en su directorio src. • este manual. 123