UN FRAMEWORK PARA LA VISUALIZACIÓN DE PATRONES DE DISEÑO DISTRIBUIDOS Y CONCURRENTES IMPLEMENTADOS CON PROGRAMACIÓN ORIENTADA A ASPECTOS: ACVF(ASPECTUAL COMPONENT VISUALIZATION FRAMEWORK) TESIS DE GRADO EN INGENIERIA INFORMATICA FACULTAD DE INGENIERIA UNIVERSIDAD DE BUENOS AIRES TESISTA: Sr. Diego M.S. ERDÖDY DIRECTORA: Prof. María FELDGEN Laboratorio de Sistemas Distribuidos Heterogéneos DICIEMBRE 2006 UN FRAMEWORK PARA LA VISUALIZACIÓN DE PATRONES DE DISEÑO DISTRIBUIDOS Y CONCURRENTES IMPLEMENTADOS CON PROGRAMACIÓN ORIENTADA A ASPECTOS: ACVF(ASPECTUAL COMPONENT VISUALIZATION FRAMEWORK) TESIS DE GRADO EN INGENIERIA INFORMATICA Laboratorio de Sistemas Distribuidos Heterogéneos FACULTAD DE INGENIERIA UNIVERSIDAD DE BUENOS AIRES Sr. Diego M.S. Erdody Tesista DICIEMBRE 2006 Prof. María Feldgen Directora Resumen En este trabajo se hace un análisis exhaustivo de los beneficios y desventajas que tiene una implementación de patrones de diseños distribuidos y concurrentes utilizando el paradigma de programación orientada a aspectos. Junto con la implementación de las aplicaciones de cada patrón de diseño, se modela y desarrolla el framework ACVF (bautizado con el nombre de “Aspectual Component Visualization Framework”) para poder integrar gráficamente los componentes de cada patrón y observar la interacción resultante. Cada patrón se visualiza como una mini-aplicación. Los patrones a analizar se han dividido en 3 categorías: Concurrentes, Manejo de Eventos y Fiabilidad. Como eje del análisis, se analizarán las características de las implementaciones orientadas a aspectos para determinar las mejoras en modularidad. Palabras clave: programación orientada a aspectos, patrones de diseño distribuidos. Abstract This work consists of an exhaustive analysis about the benefits of applying aspect oriented programming to distributed and concurrent design patterns. Along with the implementation of the study cases for the different design pattern, a framework called ACVF (Aspectual Component Visualization Framework) was designed and developed. The framework is used to visually integrate the pattern components and study its interactions. In this way, each pattern can be considered a mini-application. The design patterns under study have been divided in three groups: Concurrent, Event Handling and Security. The main objective of this work is to analyze the aspect oriented implementations and establish its improvements in modularity. Keywords: aspect oriented programming, distributed design patterns. TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Índice de Contenidos Índice de Figuras ............................................................................................................... 1 1. Introducción ............................................................................................................... 3 1.1. Estructura del trabajo ...................................................................................... 4 1.1.1. Programación Orientada a Aspectos ........................................................... 5 1.1.2. Patrones de diseño distribuidos ................................................................... 5 1.1.3. Análisis de patrones .................................................................................... 5 1.1.4. Framework de Visualización ....................................................................... 5 1.1.5. Patrones de concurrencia............................................................................. 5 1.1.6. Patrones de manejo de eventos.................................................................... 5 1.1.7. Patrones de comunicación distribuida ......................................................... 5 1.1.8. Patrones de seguridad y fiabilidad .............................................................. 5 1.1.9. Resultados y conclusiones ........................................................................... 5 1.1.10. Futuros trabajos ........................................................................................... 6 2. 3. Programación Orientada a Objetos ........................................................................... 7 2.1. Fundamentos ..................................................................................................... 7 2.2. Problemas........................................................................................................... 8 Programación Orientada a Aspectos ......................................................................... 8 3.1. Separación en intereses ..................................................................................... 8 3.1.1. Programación de patrones ......................................................................... 10 3.1.2. Filtros de composición .............................................................................. 11 3.1.3. Programación de meta-objetos .................................................................. 12 3.1.4. Conclusiones ............................................................................................. 13 3.2. Estructura de un aspecto ................................................................................ 13 3.3. Combinación de intereses ............................................................................... 14 3.4. Ventajas y Problemas de la AOP ................................................................... 16 3.5. AspectJ ............................................................................................................. 18 3.5.1. Sintaxis de un aspecto ............................................................................... 19 3.5.2. Definición de un pointcut .......................................................................... 20 3.5.3. Declaración de un advice .......................................................................... 25 3.5.4. Ejemplos .................................................................................................... 26 3.5.5. Declaraciones de miembros inter-tipo....................................................... 30 3.5.6. Declaraciones de derivación...................................................................... 31 3.5.7. Otras declaraciones ................................................................................... 33 3.5.8. Intercalado ................................................................................................. 34 3.6. Comparación con otras implementaciones de AOP ..................................... 35 3.6.1. AspectWerkz ............................................................................................. 35 3.6.2. JBoss AOP ................................................................................................ 36 3.6.3. Spring AOP ............................................................................................... 37 Diego M.S. Erdödy 1 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 3.6.4. 3.6.5. 4. CaesarJ ....................................................................................................... 38 Conclusiones .............................................................................................. 40 Patrones de diseño distribuidos ................................................................................ 45 4.1. Patrones de diseño ........................................................................................... 45 4.1.1. Beneficios y problemas de los patrones de diseño .................................... 45 4.1.2. Descripción de patrones de diseño ............................................................ 46 4.2. 5. Uso en sistemas distribuidos y concurrentes ................................................. 47 Metodología de Análisis de patrones ....................................................................... 49 5.1. Objetivos ........................................................................................................... 49 5.2. Descripción de las características a analizar ................................................. 49 5.2.1. Reusabilidad .............................................................................................. 49 5.2.2. Transparencia de composición .................................................................. 49 5.2.3. Independencia ............................................................................................ 49 5.2.4. Localidad del código ................................................................................. 49 5.3. Notación UML.................................................................................................. 50 5.3.1. Aspect ........................................................................................................ 50 5.3.2. Pointcut ...................................................................................................... 51 5.3.3. Advice ........................................................................................................ 51 5.3.4. Introduction ............................................................................................... 52 5.3.5. Declare ....................................................................................................... 53 6. Framework de Visualización .................................................................................... 55 6.1. Introducción ..................................................................................................... 55 6.2. Frameworks ..................................................................................................... 55 6.3. Características de ACVF ................................................................................ 57 6.3.1. Orientación a Componentes ....................................................................... 57 6.3.2. Relaciones .................................................................................................. 60 6.3.3. Simulación ................................................................................................. 60 6.3.4. Vista de resumen........................................................................................ 61 6.4. Arquitectura y Diseño ..................................................................................... 62 6.4.1. GEF Framework ........................................................................................ 63 6.4.2. ACVF Framework ..................................................................................... 64 7. 6.5. Otros Frameworks de Visualización .............................................................. 76 6.6. Conclusión ........................................................................................................ 77 Patrones de concurrencia y sincronización ............................................................. 79 7.1. Rendezvous ....................................................................................................... 79 7.1.1. Resumen .................................................................................................... 79 7.1.2. Otras denominaciones................................................................................ 79 7.1.3. Problema .................................................................................................... 79 7.1.4. Solución ..................................................................................................... 79 7.1.5. Caso de estudio .......................................................................................... 79 2 Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7.1.6. 7.1.7. 7.1.8. 7.1.9. Estructura .................................................................................................. 80 Estrategia de implementación Orientada a Aspectos ................................ 80 Análisis ...................................................................................................... 86 Componente gráfico .................................................................................. 87 7.2. Balking ............................................................................................................. 88 7.2.1. Resumen .................................................................................................... 88 7.2.2. Otras denominaciones ............................................................................... 88 7.2.3. Problema.................................................................................................... 88 7.2.4. Solución..................................................................................................... 88 7.2.5. Caso de Estudio ......................................................................................... 89 7.2.6. Estructura .................................................................................................. 89 7.2.7. Estrategia de implementación Orientada a Aspectos ................................ 89 7.2.8. Análisis ...................................................................................................... 91 7.2.9. Componente gráfico .................................................................................. 92 7.3. Observador ...................................................................................................... 93 7.3.1. Resumen .................................................................................................... 93 7.3.2. Otras denominaciones ............................................................................... 93 7.3.3. Problema.................................................................................................... 93 7.3.4. Solución..................................................................................................... 94 7.3.5. Caso de estudio ......................................................................................... 94 7.3.6. Estructura .................................................................................................. 94 7.3.7. Estrategia de implementación Orientada a Aspectos ................................ 95 7.3.8. Análisis ...................................................................................................... 98 7.3.9. Componente gráfico .................................................................................. 98 7.4. Optimistic Locking .......................................................................................... 99 7.4.1. Resumen .................................................................................................... 99 7.4.2. Otras denominaciones ............................................................................... 99 7.4.3. Problema.................................................................................................... 99 7.4.4. Solución..................................................................................................... 99 7.4.5. Caso de estudio ....................................................................................... 100 7.4.6. Estructura ................................................................................................ 100 7.4.7. Estrategia de implementación Orientada a Aspectos .............................. 102 7.4.8. Análisis .................................................................................................... 105 7.4.9. Componente gráfico ................................................................................ 105 8. Patrones de manejo de eventos .............................................................................. 107 8.1. Reactor ........................................................................................................... 107 8.1.1. Resumen .................................................................................................. 107 8.1.2. Otras denominaciones ............................................................................. 107 8.1.3. Problema.................................................................................................. 107 8.1.4. Solución................................................................................................... 108 8.1.5. Caso de estudio ....................................................................................... 108 8.1.6. Estructura ................................................................................................ 108 8.1.7. Estrategia de implementación Orientada a Aspectos .............................. 109 8.1.8. Análisis .................................................................................................... 112 Diego M.S. Erdödy 3 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 8.1.9. 9. Componente gráfico................................................................................. 113 Patrones de seguridad y fiabilidad ......................................................................... 115 9.1. Watchdog ........................................................................................................ 115 9.1.1. Resumen .................................................................................................. 115 9.1.2. Otras denominaciones.............................................................................. 115 9.1.3. Problema .................................................................................................. 115 9.1.4. Solución ................................................................................................... 115 9.1.5. Caso de estudio ........................................................................................ 115 9.1.6. Estructura ................................................................................................. 116 9.1.7. Estrategia de implementación Orientada a Aspectos............................... 117 9.1.8. Análisis .................................................................................................... 119 9.1.9. Componente gráfico................................................................................. 119 10. Resultados y Conclusiones ..................................................................................... 123 11. Futuros trabajos...................................................................................................... 125 Referencias ...................................................................................................................... 127 Glosario ........................................................................................................................... 131 4 Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Índice de Figuras Figura 3-1: Mapeo entre espacio de intereses y espacio de implementación...................... 9 Figura 3-2: Modelo Conceptual de Filtros de Composición ............................................. 12 Figura 3-3: Mecanismo de combinación de intereses ....................................................... 15 Figura 3-4: Impacto de cambios sobre sistemas convencionales y orientados a aspectos 16 Figura 3-5: Diagrama de estructura para los ejemplos de AspectJ ................................... 27 Figura 3-6: Captura de Eclipse mostrando el resultado de “declare error” ....................... 34 Figura 3-7: Mecanismo general de funcionamiento de AspectJ ....................................... 35 Figura 5-1: Ejemplo de notación UML para un Aspecto .................................................. 51 Figura 5-2: Ejemplo de notación UML para un Pointcut .................................................. 51 Figura 5-3: Ejemplo de notación UML para un Advice simple ........................................ 52 Figura 5-4: Ejemplo de notación UML para un Advice con argumentos ......................... 52 Figura 5-5: Ejemplo de notación UML para una Introducción ......................................... 53 Figura 5-6: Ejemplo de notación UML para una declaración de implementación ........... 54 Figura 6-1: Paleta de componentes ................................................................................... 57 Figura 6-2: Propiedades comunes a todos los componentes ............................................. 58 Figura 6-3: Propiedades particulares para el componente RoboArm ............................... 58 Figura 6-4: Ejemplo de un Componente ........................................................................... 59 Figura 6-5: Relaciones entre Componentes ...................................................................... 59 Figura 6-6: Ejemplo de un diagrama con varios componentes y relaciones ..................... 60 Figura 6-7: Vista de resumen ............................................................................................ 61 Figura 6-8: Capas de la arquitectura del Framework y sus características principales ..... 62 Figura 6-9: Interacción entre las distintas capas MVC del framework ............................. 63 Figura 6-10: Capa de Modelo de ACVF ........................................................................... 65 Figura 6-11: Compartimientos del componente ................................................................ 67 Figura 6-12: Capa de Vista de ACVF ............................................................................... 68 Figura 6-13: Capa de Controlador de ACVF .................................................................... 69 Figura 6-14: Capa de Componentes de ACVF.................................................................. 71 Figura 6-15: Diagrama de secuencia para el movimiento de un componente .................. 73 Figura 6-16: Diagrama de secuencia para el inicio de la simulación ................................ 75 Figura 6-17: Diagrama de estructura básico del framework de visualización JHotDraw . 76 Figura 7-1: Diagrama de Estructura del Patrón “Rendezvous”......................................... 80 Figura 7-2: Núcleo de la estructura Orientada a Aspectos del Patrón “Rendezvous” ...... 81 Figura 7-3: Estructura de la aplicación del patrón “Rendezvous” .................................... 83 Figura 7-4: Estructura de la distintas implementaciones del Rendezvous ........................ 84 Figura 7-5: Estructura de la implementación remota del Rendezvous.............................. 85 Figura 7-6: Estructura completa para el Rendezvous........................................................ 86 Figura 7-7: Diagrama ACVF del caso de estudio para el patrón “Rendezvous” .............. 88 Figura 7-8: Diagrama de estructura del patrón “Balking” ................................................ 89 Figura 7-9: Estructura del Núcleo del patrón “Balking” ................................................... 90 Figura 7-10: Estructura de la aplicación del patrón “Balking” ......................................... 91 Figura 7-11: Diagrama ACVF del caso de estudio para el patrón “Balking” ................... 93 Figura 7-12: Diagrama de estructura del patrón “Observador” ........................................ 94 Figura 7-13: Estructura del Núcleo del patrón “Observador” ........................................... 95 Diego M.S. Erdödy Índice de Figuras 1 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 7-14: Estructura de la aplicación del patrón “Observador”.................................... 97 Figura 7-15: Diagrama ACVF del caso de estudio del patrón “Watchdog”...................... 99 Figura 7-16: Interacción del patrón de concurrencia optimista ....................................... 101 Figura 7-17: Estructura del Núcleo del patrón “Optimistic Locking” ............................. 102 Figura 7-18: Estructura de la aplicación del patrón “Optimistic Locking” ..................... 104 Figura 7-19: Diagrama del patrón “Optimistic Locking” ................................................ 106 Figura 8-1: Estructura del patrón “Reactor” .................................................................... 108 Figura 8-2: Estructura del Núcleo (sector Event Handling) del patrón “Reactor” .......... 110 Figura 8-3: Estructura del Núcleo (sector Event Dispatching) del patrón “Reactor”...... 111 Figura 8-4: Estructura de la aplicación concreta del patrón “Reactor” ........................... 112 Figura 8-5: Diagrama ACVF del caso de estudio para el patrón “Reactor” .................... 114 Figura 9-1: Diagrama de estructura del patrón “Watchdog” ........................................... 116 Figura 9-2: Estructura del Núcleo del patrón “Watchdog” ............................................. 117 Figura 9-3: Estructura de la aplicación del patrón “Watchdog” al caso de estudio ........ 118 Figura 9-4: Simulación de patrón Watchdog ................................................................... 120 Figura 9-5: Propiedades de los componentes gráficos Pacemaker y Watchdog ............. 120 2 Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 1. Introducción En los últimos años, la programación orientada a objetos (POO) ha sido el modelo de lenguaje de programación predominante dada su gran capacidad para abstraer la complejidad de un problema determinado. La programación orientada a objetos clásica se basa en el paradigma de que todo programa está compuesto de una colección de unidades individuales llamadas objetos. Cada objeto es capaz de recibir mensajes, procesar información o enviar mensajes a otros objetos. Si bien este modelo ha sido un gran paso en la evolución de los lenguajes de programación, no es de ninguna forma perfecto. Uno de los grandes problemas que posee, por ejemplo, es la incapacidad de abstraer problemas que afectan a varios sectores del sistema en forma transversal. La programación orientada a aspectos (POA) provee una solución para abstraer código “transversal”, que se encuentra distribuido en varias clases sin relación funcional con ellas (por ej. instrucciones de seguridad o de bitácora). En vez de dispersar este código dentro de las clases, la POA permite abstraer estos fragmentos en módulos independientes, llamados aspectos, para luego poder aplicar este código dinámicamente cuando sea necesario. Esto se logra definiendo lugares específicos (llamados puntos de corte o “pointcuts”) en el modelo de objetos donde el código transversal debe ser aplicado. Este código es insertado dentro de las clases tanto en tiempo de ejecución como en tiempo de compilación, dependiendo del framework POA utilizado y de la configuración. Esencialmente, la POA permite agregar nueva funcionalidad dentro de los objetos sin la necesidad de que ellos tengan conocimiento de dicha introducción. Las implementaciones de POA más desarrolladas y activas que existen hasta este momento son: AspectJ [Ram 03] El proyecto fue iniciado por uno de los creadores de la POA, el cual se basa en una extensión al lenguaje Java y se ha transformado en una de las implementaciones más completas y genéricas de este paradigma1. AspectWerks [AWerks 05] Es una herramienta concebida por el grupo de código libre Codehaus. JBossAOP [Bur 03] Es una extensión para el servidor de aplicaciones JBoss cuya implementación se basa en interceptores configurables via XML. SpringAOP [Rus 04] Es uno de los módulos del framework multipropósito llamado Spring, el cual también está basado en interceptores configurables via XML. CaesarJ [Mez 03] Es la más reciente y prometedora implementación de AOP basada en extensión al lenguaje Java, nacida en la Universidad de Darmstadt, Alemania. Dentro de la POO, los patrones de diseño son estructuras conocidas y definidas como soluciones estándar para problemas comunes en el diseño de software. El objetivo de estos patrones es disminuir los tiempos de diseño e implementación de los sistemas, 1 Patrón de pensamiento y conjunto de reglas que enmarcan y sirven de guía en una especialidad determinada. Diego M.S. Erdödy Introducción 3 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA proveyendo soluciones prefabricadas que resultaron eficientes para determinado tipo de problema. Es por este motivo, que mientras más independientes y fáciles de “ensamblar” sean estas soluciones, mayor será el tiempo ahorrado, tanto en diseño e implementación como en mantenimiento. Dado que los beneficios de la introducción de la POA en los patrones de diseño tradicionales, también llamados GoF (Gang of Four, debido a los autores libro [Gam 95]) han sido ampliamente verificados [Han 02], se demuestra, en este trabajo, que lo mismo ocurre con patrones de diseño distribuidos y concurrentes, es decir, los que tratan con problemas en donde intervienen más de una máquina o hilo de ejecución2. El objetivo del trabajo es hacer un análisis exhaustivo de los beneficios y desventajas que tiene una implementación de patrones de diseños distribuidos y concurrentes utilizando el paradigma de programación orientada a aspectos. Junto con la implementación de las aplicaciones de cada patrón de diseño, se modela y desarrolla un framework (bautizado con el nombre de “Component Visualization Framework”) para poder integrar gráficamente los componentes de cada patrón y observar la interacción resultante. Cada patrón se implementa simulando una aplicación clásica. Por ejemplo, el “Rendezvous” se visualiza como una mini-aplicación donde un conjunto de brazos robóticos esperan un evento para comenzar su tarea. Estas mini-aplicaciones pueden conectarse entre sí y con el ambiente. En la cadena así armada se puede evaluar el impacto de los cambios según POO y POA. Los patrones a analizar se han dividido en 4 categorías: Concurrentes, Distribuidos, Manejo de Eventos y Control o Tiempo Real [Dou 02] [Sch 00]. Para cada uno se hace el desarrollo de una mini-aplicación del patrón usando las mejores técnicas conocidas en POO y POA. El framework está basado en una conjunción de caja blanca [Ras 03] para los componentes y caja negra para las utilidades asociadas al entorno. Se utiliza metainformación como mecanismo de extensión de ciertos puntos definidos, denominados “hotspots”. Las simulaciones pueden ser ejecutadas tanto en modo automático como en modo manual o interactivo, permitiendo una visualización con mayor detalle. En el modo manual, el usuario es el encargado de hacer que los componentes actúen sobre otros, pudiendo ejecutar acciones sobre cada componente y ver las reacciones en el resto. En el modo automático, cada componente ejecuta un comportamiento predeterminado, que en la mayoría de los casos se puede configurar a través de las propiedades del componente, que se inicializa al comenzar la simulación. 1.1. Estructura del trabajo Este trabajo consta de los siguientes capítulos: 2 4 Unidad de división de paralelización de procesamiento de un programa determinado. Introducción Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 1.1.1. Programación Orientada a Aspectos Breve introducción a la programación orientada a objetos (POO) y su evolución hacia la programación orientada a aspectos (POA). Las definiciones de elementos de la POA y una comparación de herramientas que implementan la POA. Por último se justifica la elección de la herramienta elegida, AspectJ. 1.1.2. Patrones de diseño distribuidos Definición de un patrón de diseño. Características de los patrones de diseño distribuidos y sus distintos tipos. 1.1.3. Análisis de patrones Especificación de características a analizar, metodología de trabajo y lenguaje de modelado que se utiliza para la especificación de los diseños orientados a aspectos. 1.1.4. Framework de Visualización Descripción de las características del framework desarrollado para la visualización de los casos de estudio. Explicación de la arquitectura y diseño. 1.1.5. Patrones de concurrencia Descripción y análisis de patrones de diseño de concurrencia. 1.1.6. Patrones de manejo de eventos Descripción y análisis de patrones de diseño de manejo de eventos. 1.1.7. Patrones de comunicación distribuida Descripción y análisis de patrones de diseño de comunicación distribuida. 1.1.8. Patrones de seguridad y fiabilidad Descripción y análisis de patrones de diseño seguridad y fiabilidad. 1.1.9. Resultados y conclusiones Análisis comparativo de los resultados de los distintos casos de estudio. Conclusiones obtenidas aplicando distintas métricas. Diego M.S. Erdödy Introducción 5 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 1.1.10. Futuros trabajos Lineamientos para profundización del presente trabajo y trabajos relacionados. 6 Introducción Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 2. Programación Orientada a Objetos 2.1. Fundamentos La programación orientada a objetos consiste básicamente en descomponer un problema en un grupo de objetos que interactúan entre sí por medio de mensajes. Cada objeto puede contener atributos para almacenar su estado interno, el cual puede ser alterado a través de los mensajes. Los mensajes, también llamados métodos, son subrutinas que se ejecutan en el ámbito de un objeto en particular. Cada objeto debe estar asociado a un tipo de objeto, llamado clase. La clase es la que contiene la definición de los métodos que los objetos de su tipo poseerán así como también los atributos que contendrán. Una vez definido los elementos fundamentales, se enumeran los conceptos principales presentes dentro de este paradigma: Abstracción: Al igual que en matemáticas, es el mecanismo por el cual se identifican características comunes en un cierto conjunto de elementos. Por este mecanismo, se pueden establecer formas homogéneas de ver a un objeto desde el exterior, llamadas interfaces. La abstracción permite independizar dichas interfaces, de la forma de implementar el comportamiento semántico definido por ellas. Encapsulación: Asegura que los atributos internos de un objeto, también llamados privados, no puedan ser accedidos desde afuera, dando así la seguridad de que futuros cambios sobre ellos no impactarán en el resto de los objetos. Para acceder a dichos atributos se deberán proveer maneras específicas y controladas como, por ejemplo, métodos públicos. Herencia: Los elementos de una clase (métodos y atributos) pueden ser heredados por otra clase. Esto significa que la clase hija (la que hereda) contendrá implícitamente todos los elementos de la clase padre. Se dice que la clase hija “extiende” o “especializa” a la clase padre, ya que es un mecanismo por el cual se incorporan nuevos elementos a partir de los ya existentes. Polimorfismo: En su definición clásica, consiste en poder utilizar la misma definición con distintos tipos de datos indistintamente. Una de las técnicas de polimorfismo que provee la POO es la posibilidad de contar con distintas implementaciones del mismo método dentro de una jerarquía de clases. Dicha técnica pertenece al grupo del polimorfismo dinámico dado que la decisión, de que tipo de comportamiento aplicar, se realiza en tiempo de ejecución. Otra de las formas de polimorfismo presentes en la POO es el mecanismo denominado “sobrecarga” que es la redefinición de un mismo método con distintos tipos de argumentos. De esta forma se logra un comportamiento acorde a cada tipo de objeto. Esta última técnica pertenece al grupo del polimorfismo “ad-hoc”, ya que la cantidad de variantes de tipos argumentos debe ser explícitamente declarada en tiempo de compilación (un método distinto por cada tipo). Diego M.S. Erdödy Programación Orientada a Objetos 7 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 2.2. Problemas Uno de los principales problemas de la POO, es la incapacidad de abstraer eficientemente la funcionalidad que se repite en distintos módulos que no estén relacionados en la jerarquía de clases. Uno de los ejemplos más claros de este inconveniente son las instrucciones de bitácora y de traza de ejecución. Si en un sistema se quisiera mostrar un mensaje cada vez que comienza la ejecución de un método, se tendría que agregar dicha instrucción en todos los métodos del sistema. Supongamos, que se incorpora una instrucción en cada uno de los métodos del sistema que muestre el nombre del método al cual se está invocando. Si en el futuro, se decide que es necesario ver además, los valores de los argumentos con los cuales fue llamado cada método, se necesitaría hacer una modificación en cada uno de los lugares donde fue aplicada la instrucción originalmente. Dicha modificación implica un esfuerzo lineal en comparación con el tamaño del sistema. Todos estos problemas surgen de la falta de mecanismos del paradigma para la abstracción de comportamiento transversal. 3. Programación Orientada a Aspectos 3.1. Separación en intereses Es posible ver un sistema como la combinación de múltiples intereses. Se define interés como cualquier aspecto3 que le concierne a un programa, ya sea relacionado a la infraestructura, al negocio, a los requerimientos o a las estructuras de diseño. Nótese que la definición es genérica por naturaleza. Ejemplos típicos de intereses son lógica de negocio, performance, bitácora, persistencia, autenticación, restricciones de tiempo real y tolerancia a fallos. Los intereses se pueden dividir en locales y transversales. Los intereses locales son los que sólo le conciernen a un componente (o grupo reducido) en particular. El ejemplo clásico de este tipo de intereses es la lógica de negocio. Los intereses transversales en cambio, son aquellos que involucran a más de un componente a la vez. Un ejemplo es el logging (o bitácora de ejecución) de una aplicación. La mayoría de los componentes, si no todos, deben preocuparse por tener un registro persistente de su ejecución. Los intereses se pueden ver en dos planos o espacios distintos, el conceptual y el de la implementación. El primero es el espacio lógico en el cual se identifica el interés. El segundo es el espacio físico en el cual se traduce el interés conceptual a una implementación específica, en un leguaje de programación determinado. El problema principal con el mapeo entre ambos espacios utilizando lenguajes orientados a objetos, es que el espacio conceptual posee múltiples dimensiones, una dimensión por cada interés existente. En cambio, el espacio de implementación presenta una naturaleza “unidimensional” inherente. En el caso de la POO, el espacio de implementación se limita a un flujo lineal de traspaso de mensajes entre objetos. Por esta razón, el mapeo produce una pérdida de información valiosa. En la siguiente figura se puede apreciar gráficamente este efecto: 3 8 En su acepción tradicional, no confundir con el aspecto definido más adelante Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Lógica de Negocio Mapeo Logging Au te n tic ac ió n Lógica de Negocio Autenticación Logging Espacio de intereses Espacio de Implementación Figura 3-1: Mapeo entre espacio de intereses y espacio de implementación En el espacio de intereses, se observan tres intereses ortogonales que generan dicho espacio. Al llevar estos intereses a un espacio de implementación, que como se indicó, tiene una naturaleza unidimensional, éstos se deben mapear a un único eje de acción. Este proceso conlleva una pérdida de identidad de cada interés. El proceso de conversión de un espacio multidimensional a un espacio unidimensional produce problemas y efectos colaterales. Los principales síntomas de este fenómeno, aplicados al caso de logging, son los siguientes: Mezcla de código: La implementación del interés principal de un módulo contiene en muchos casos, la implementación de otros intereses transversales intercalada. Esto dificulta la lectura y el mantenimiento del mismo. Cada elemento que quiera ser registrado en la bitácora, debe agregar instrucciones especiales, las cuales estarán mezcladas con las que implementan el interés especial. Dispersión de código: Es el caso contrario al anterior. La implementación del comportamiento de un interés transversal se encuentra distribuido en distintos módulos. Por este motivo resulta muy difícil identificar el interés. Para saber qué componentes están siendo registrados en la bitácora, es necesario revisar todo el sistema para rastrear en dónde se están utilizando tales instrucciones. Repetición de código: Una consecuencia del problema anterior es que el código disperso, frecuentemente se encuentra repetido. Las mismas instrucciones de logging aparecerán una y otra vez en cada módulo que precise ser registrado. Y las principales desventajas son: Problemas al analizar la implementación: Al implementar múltiples intereses se hace cada vez más difícil distinguir cada uno de los intereses en su implementación. Este es un problema exponencial, dado que cada interés transversal incorporado al sistema, potencialmente dificulta cada uno de los intereses existentes. Baja productividad: Al momento de implementar un módulo, es necesario preocuparse por la correcta implementación del interés principal así como también de los intereses transversales que afectan al módulo. Evidentemente preocuparse por Diego M.S. Erdödy Programación Orientada a Aspectos 9 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA un solo interés a la vez resultaría más sencillo y reduciría el riesgo de errores, lo cual aumentaría considerablemente la productividad. Imposibilidad de reutilización: Dado que cada módulo contiene la implementación de los intereses transversales, otros sistemas que tengan distintos requerimientos de sistema, no podrán reutilizar la implementación del módulo. Mantenimiento difícil: En caso de necesidad de cambios en los requerimientos del sistema, los cambios a nivel de implementación tienen un costo, en el mejor de los casos, de orden lineal al tamaño del sistema. Esto se debe a que la modificación se debe hacer en todos los lugares en donde se encuentre intercalada la implementación del interés. Por ejemplo, para el caso del logging, si durante la evolución de un sistema se determina que aparte de registrar un mensaje, es necesario identificar al usuario involucrado, cada una de las llamadas que el sistema realiza al componente de log, tendrán que ser modificadas. Mientras más grande sea el sistema, más llamadas habrá al componente de log y de la misma forma aumentará la cantidad de cambios necesarios. Evolución compleja: Incorporar nuevos intereses es también costoso y aumenta con la cantidad de intereses transversales que existan. Siguiendo con el ejemplo de logging, si una aplicación inicialmente no contempló la necesidad de registrar su ejecución, el costo de la incorporación de dicho interés será proporcional al tamaño del sistema. Es por todas estas razones que se debe encontrar un método que permita implementar cada interés en forma independiente. De tal forma que cualquier modificación en un interés no afecte al resto de los intereses, pero los intereses requieren interactuar entre ellos. Esto requiere que existan mecanismos que permitan la interacción entre ellos sin perder la independencia. Existen varios métodos para lograr el objetivo planteado [Hur 95]. Estos métodos se describen y analizan en las siguientes secciones. 3.1.1. Programación de patrones Lieberherr [Lie 96] describe la programación adaptativa basada en patrones de código. Los patrones de código, que no son patrones de diseño, se pueden dividir en diferentes categorías: Patrones de propagación: Consiste en identificar subgrafos dentro de la estructura general de objetos, que están ligados a una operación en común. De esta forma, el acceso a la información de esa operación se hará de forma tal que no sea necesario conocer el camino. Sólo es necesario saber a qué operación pertenece. Por ejemplo, sea la estructura simple de tres clases: Alumno, Clase (que posee referencias a un conjunto de alumnos) y Escuela (que se asocia a un conjunto de clases). En la POO, para acceder a un alumno a partir de una Escuela se debe conocer el camino, es decir a través de la clase “Clase”. Si se modifica la estructura de objetos, y se agrega un nivel “Departamento” entre Escuela y Clase, debe modificarse la estrategia de acceso. Los patrones de propagación tratan este tipo de 10 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA problemas y permiten hacer llamadas tales como “a partir de una Escuela, obtener el Alumno tal”, es decir, abstraen el camino dentro de la estructura. Patrones de transporte: El objetivo de estos patrones es la abstracción del concepto de parametrización. Se utilizan dentro de los patrones de propagación para enviar los parámetros entre los subgrafos. Patrones de sincronización: Define esquemas de sincronización entre los objetos en aplicaciones concurrentes. Este conjunto de patrones debe ser complementado con una estructura de clases sobre la cual aplicarlo. Un compilador de patrones tomará tanto la estructura de clases como los patrones y generará un programa orientado a objetos. Cada categoría trata con un conjunto de problemas distintos y puede considerarse un interés de alto nivel dentro de una aplicación. 3.1.2. Filtros de composición El modelo de filtros de composición desarrollado por Bergmans y Aksit [Aks 92] [Ber 94] en la Universidad de Twente, es una extensión del modelo convencional orientado a objetos. Este modelo fue aplicado a un sistema de pagos de subsidios médicos por invalidez implementada en el lenguaje Sina [Ber 01]. El siguiente diagrama representa el modelo de un filtro de composición. Mensajes Recibidos Filtros de entrada Interface Métodos y Condiciones Objetos internos Variables de Instancia Objetos externos Implementación (objeto núcleo) Filtros de salida Mensajes Enviados Diego M.S. Erdödy Programación Orientada a Aspectos 11 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 3-2: Modelo Conceptual de Filtros de Composición El núcleo o implementación del objeto representa el comportamiento y sólo puede ser modificado a través del procesamiento de los mensajes de entrada y salida. Esto se logra a través de la capa externa del núcleo del objeto, llamada interface. La interface contiene los elementos más importantes que son los filtros, tanto de entrada como de salida. Los filtros son los encargados de combinar el comportamiento del objeto con otros objetos. Los otros objetos pueden ser tanto internos como externos. El objeto núcleo contiene variables de instancia que representarán el estado del componente y en el límite con la interface se encuentran los métodos y las condiciones. Los métodos pueden ser invocados por mensajes, siempre y cuando no sean rechazados por alguno de los filtros de entrada. Las condiciones son simplemente expresiones booleanas que se refieren al estado del objeto y son usadas por los filtros en el momento de procesar los mensajes. Los filtros, que son los encargados de combinar los intereses, aceptando, rechazando y modificando los mensajes, tienen un orden determinístico de aplicación formando filtros en cascada. La composición de filtros en cascada posee un comportamiento booleano del tipo AND mientras que los distintos elementos dentro de un mismo filtro proveen el comportamiento OR. Esta propiedad hace que el modelo sea apropiado para representar intereses que se deben aplicar en un orden especificado. 3.1.3. Programación de meta-objetos Un “meta-objeto” es una entidad que puede crear o modificar a otros objetos. Los protocolos de meta-objetos (MOP) son interfaces definidas en el lenguaje de programación que posibilita al usuario, cierto grado de libertad, para modificar el comportamiento y la implementación del lenguaje. De esta forma los objetos que se encuentran en el nivel primario, por lo general encargados de los algoritmos fundamentales de un programa, pueden ser modificados por meta-objetos con el objetivo de adaptarlos a requerimientos específicos de un entorno determinado. Los protocolos de meta-objetos [Kic 92] se pueden clasificar en meta-objetos en tiempo de ejecución y meta-objetos en tiempo de compilación. Uno de los lenguajes más representativos del primer grupo es el Common Lisp Object System [Kic 91], el cual permite alterar mecanismos del lenguaje LISP tales como la herencia, instanciación de objetos o procesamiento de mensajes, en tiempo de ejecución. Sullivan [Sul 01] enumera los siguientes beneficios de un MOP en tiempo de ejecución: Se permite la modificación de objetos en el momento de cargar las clases, agregando un nivel de extensibilidad mayor. Los meta-objetos pueden utilizar valores de tiempo de ejecución para tomar las decisiones de cómo modificar los objetos. 12 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Los meta-objetos pueden ser modificados en tiempo de ejecución. Esto tiene como corolario que el meta-comportamiento puede ser removido también en tiempo de ejecución. Además muestra cómo un lenguaje reflexivo como Java no cumple con los requerimientos de un MOP ya que si bien permite obtener información de otros objetos, el acceso que brinda es de “sólo lectura”, es decir, no admite que los objetos sean modificados. Kojarski [Koj 03], demuestra que todas las funciones que brinda la biblioteca de reflexión de Java (CJR) se pueden realizar con un lenguaje basado en MOP como AspectJ, incluso con mejoras en la performance. Desde luego que hay una decisión de compromiso en dicho beneficio. Se requiere un aumento en la memoria necesaria ya que la meta-información es almacenada completamente en un caché. Para minimizar este problema, se puede reducir el conjunto de clases que van a ser almacenadas. Esto se puede lograr indicando explícitamente el conjunto de clases que podrá ser accedida reflexivamente. Esto no es posible con la CJR dado que la reflexión es implícita para todas las clases. 3.1.4. Conclusiones El primer modelo, programación de patrones, tiene una gran limitación en la cantidad de intereses que puede representar dado que fue diseñado para tratar problemas específicos como la abstracción de caminos de acceso a ciertos elementos, pasaje de parámetros y sincronización general. Para la implementación de los patrones que se analizaron en este trabajo, no es conveniente dada la heterogeneidad de los mismos. En el segundo modelo, filtros de composición, la única forma de combinar los intereses es por medio del procesamiento de los mensajes de entrada y salida. Otra de las desventajas para la implementación de los patrones de este trabajo es que no permite la modificación en la estructura de los objetos, limitando considerablemente las formas de inserción del comportamiento de los nuevos intereses. Se ha elegido un modelo similar a la programación de meta-objetos para la implementación de los patrones de diseño de este trabajo, dada la gran generalidad y flexibilidad del modelo. 3.2. Estructura de un aspecto Los términos “aspecto” y “AOP” fueron definidos por Gregor Kiczales en 1997 [Kic 97]. Estructuralmente, un aspecto es una clase con ciertos elementos adicionales que lo caracterizan. Estos elementos le dan la expresividad que necesita para poder abstraer intereses transversales. Los elementos son: Join point: es un punto específico dentro de la estructura o ejecución de un programa. Puede ser, por ejemplo, la ejecución de un método, la creación de un objeto o la modificación en el valor de un atributo. Mientras más tipos de join points Diego M.S. Erdödy Programación Orientada a Aspectos 13 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA se puedan especificar con una herramienta AOP, más casos se podrán representar en un aspecto y por ende más potente será dicha herramienta. Pointcut: es una agrupación lógica de un grupo de join points dentro de un aspecto. Advice: es un fragmento de código equivalente al contenido de un método, en donde se especifica el comportamiento transversal que contendrá el aspecto. Este comportamiento va acompañado de la indicación del pointcut donde debe ser insertado y la forma de insertarlo (antes, después o en reemplazo del pointcut). Declaraciones de miembros inter-clase: son declaraciones que permiten agregar métodos o atributos a interfaces o clases ya existentes. También conocido como introducción. Otras declaraciones: son declaraciones de distintos tipos cuya disponibilidad dependen del lenguaje orientado a aspectos que se utilice. Por ejemplo para el caso de AspectJ se cuenta con los siguientes tipos: o parents: que especifica que una clase determinada herede de otra en particular. o implements: que agrega la implementación de una interface a una clase dada o soft: que elimina la obligación de capturar las excepciones. o warning: que genera un aviso en tiempo de compilación cuando se cumple la condición requerida. o error: que genera un error en tiempo de compilación cuando se cumple la condición requerida. o precedence: que especifica el orden de aplicación de los distintos aspectos para eliminar conflictos de precedencia de aspectos. Matemáticamente, los aspectos constituyen una extensión de segundo orden en el paradigma de programación, es decir que mientras los paradigmas conocidos utilizan simples funciones, mensajes o equivalentes, la AOP permite razonar en términos de conjuntos de dichas entidades a través del uso de los puntos de corte. De esta forma se puede ver a la AOP como una poderosa extensión lógica más que como un nuevo paradigma. 3.3. Combinación de intereses La combinación de intereses es el mecanismo por el cual distintos elementos de los aspectos se insertan dentro de las clases. Existen dos grandes grupos de elementos a insertar, los estructurales y los advices. Los estructurales implican cambios en la estructura de la clase, ya sea agregando la implementación de nuevas interfaces, nuevos atributos o nuevos métodos. En el segundo caso, la inserción es más compleja ya que depende de la incorporación de los advices en los distintos pointcuts que puede requerir el cumplimiento de distintas condiciones. Las formas clásicas de llevar a cabo la combinación de intereses son: Preprocesador de código fuente. Postprocesador que modifique el código binario ya compilado. 14 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Compilador preparado para AOP que genere el código binario intercalado. Intercalado en tiempo de carga de las clases (por ej., en el caso de Java, se intercala cada advice a medida que se cargan las clases en la máquina virtual (JVM)) Intercalado en tiempo de ejecución (capturar cada join point en tiempo de ejecución y ejecutar todos los advices correspondientes). Las primeras dos opciones complican la tarea del programador mientras que las dos últimas pueden afectar la performance del sistema. La última opción necesita de un entorno de ejecución especial para que sea factible (en el caso de Java esto implica una JVM a medida). Se resume el mecanismo de la combinación de intereses en el siguiente gráfico: Implementación de intereses principales (objetos) Compilador e Intercalador AOP Sistema Implementación de intereses transversales (aspectos) + reglas de intercalado Figura 3-3: Mecanismo de combinación de intereses Todas estas soluciones implican modificaciones del código compilado en algún punto. Esto quiere decir que el código binario resultante es distinto al generado por un compilador tradicional. Esto es un grave problema al momento de depurar código con las herramientas tradicionales. Cohen y Gil [Coh 04] proponen una estrategia de combinación denominada “deploy-time weaving”. La diferencia principal con los métodos anteriores es que en vez de modificar el código compilado inyectándole los aspectos, estos son introducidos a través de clases que extienden las clases ya existentes y sobrescriben sus métodos. La ventaja de este mecanismo es que el código original queda intacto y por ende se pueden utilizar herramientas de depuración tradicionales sin mayor inconveniente. También se observa como ventaja la compatibilidad con el código tradicional ya que los aspectos pasan a ser objetos que extienden otros objetos. Como desventaja se puede mencionar el hecho de que la creación de nuevas capas en la jerarquía de herencia reduce la eficiencia final. Diego M.S. Erdödy Programación Orientada a Aspectos 15 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 3.4. Ventajas y Problemas de la AOP La implementación de cada interés en forma independiente, facilita el mantenimiento del sistema y la introducción de modificaciones o nuevas funcionalidades. Esto se puede ver en el siguiente diagrama: Caso con la POO Caso con la POA Sistema original Sistema original Sistema original + interés transversal Sistema original Sistema original + interes modificado Sistema original Implementación del interés transversal (aspecto) Aspecto modificado Figura 3-4: Impacto de cambios sobre sistemas convencionales y orientados a aspectos Por un lado se muestra que con en el caso de la POO, una modificación sobre el código concerniente a un interés transversal, impacta en diversos lugares del sistema. Es decir que los mismos cambios se tienen que repetir más de una vez. En cambio, en el caso de la POA, al poder modularizar el código asociado al interés transversal, las modificaciones que se le deban aplicar siempre estarán centralizadas en un único lugar físico. La depuración de código es uno de los grandes problemas de la AOP. El código fuente transversal en la AOP es independiente mientras que una vez compilado y en tiempo de ejecución esto no es cierto debido al intercalado. Una solución a este problema 16 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA es utilizar herramientas especializadas de depuración para AOP, que puedan relacionar el código fuente con el código binario correspondiente. Existe otro problema asociado a la captura de pointcuts con comodines. Los pointcuts con comodines representan diversos puntos (llamada a métodos, asignación de atributos, etc.) dentro del sistema, agrupados por una convención de nombres que contiene caracteres especiales denominados comodines. El comodín “*”, por ejemplo, representa un grupo de caracteres. Si se usa por ejemplo, un esquema de pointcuts basado en el nombre de los métodos (como en AspectJ 1.2) se corre el riesgo que en futuras modificaciones, se cambie el nombre de uno de los métodos referenciados por el pointcut. Esto produciría el funcionamiento incorrecto del aspecto. Esto se puede controlar usando estándares de nombres rígidos pero aún así no se elimina el problema completamente. Otra alternativa es contar con herramientas de desarrollo que permitan visualizar fácilmente la integración entre clases y aspectos de forma tal de poder contar con controles visuales al momento de hacer cambios. Sin embargo, la solución más prometedora a dicho inconveniente es la introducción de información adicional (o meta-información) a la especificación de las clases, ya sea métodos o atributos. De esta forma se tiene un control certero y flexible al mismo tiempo. La forma de especificar esta meta-información se analizará más adelante, pero para ejemplificar el problema, se tomará el ejemplo de una clase a la que se le debe aplicar políticas de seguridad. Para ello se define que para llamar a cualquier método cuyo nombre empiece con “get”, se deben poseer privilegios especiales. Si con la evolución de la clase, alguien que no tenga conocimiento de la política de seguridad, intenta agregar otro método bajo la misma convención (una práctica habitual en Java), se verá forzado a hacer entrar dicho método a la política de seguridad, aunque no sea el comportamiento esperado. Para solucionar el problema, se puede agregar meta-información a los métodos que los categorice según su funcionalidad, por ejemplo, “Privilegiado”. De esta forma, los nuevos métodos no se verán afectados. La meta-información puede estructurarse en distintas formas. Una de las más complejas, actualmente en desarrollo, es la estructura semántica. En el framework SetPoint [Alt 04], se hace uso de un lenguaje de ontologías llamado OWL [OWL 06] (Ontology Web Language), aumentando considerablemente el grado de expresividad de la metainformación. Por ejemplo, se pueden vincular pointcuts con expresiones del tipo “todos los objetos que representen una manufactura y cuyo costo sea mayor a 100”. Como indica Roger Alexander [Ale 02], existe un peligro inherente a la abstracción transversal que implica la orientación a aspectos. Este peligro se asocia a la carga cognitiva que se adiciona con la aplicación de nuevos intereses transversales. A esto se lo denomina “distancia cognitiva” [Kru 92] entre la abstracción base (la implementación antes de aplicar funcionalidad transversal) y la que resulta luego del intercalado con intereses transversales. Esta distancia cognitiva lleva a que invariantes asumidas por el autor de la implementación original, ya no se cumplan al intercalar el o los intereses transversales. Diego M.S. Erdödy Programación Orientada a Aspectos 17 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Otro problema que ha sido identificado dentro de la AOP, se denomina “pointcuts frágiles”. El problema se presenta al efectuar una reestructuración de código en un sistema que ha sido implementado en AOP. Los cambios introducidos por la reestructuración pueden afectar la cantidad de joinpoints representados por cada pointcut y en última instancia hacer que el comportamiento no sea el esperado. Este problema ha sido analizado por Koppen y Stoerzer [Kop 04] y han propuesto una herramienta para minimizar sus efectos. La herramienta básicamente calcula la diferencia en número de joinpoints representados por cada pointcut antes y después de una reestructuración, permitiendo visualizar los resultados para poder tomar medidas correctivas. 3.5. AspectJ AspectJ [Asj 05] es una implementación en Java de AOP creada en los laboratorios de XEROX Parc y que gradualmente se convirtió en el lenguaje orientado a aspectos más popular. El proyecto ha sido integrado a la fundación Eclipse en diciembre de 2002 y conjuntamente se le ha dado soporte dentro del entorno de desarrollo Eclipse (proyecto llamado AspectJ Development Tools [AJDT 06]). Este proyecto provee un aporte importante hacia el mejor entendimiento de la interacción entre los aspectos y la base de código existente, a través de ayudas gráficas y atajos. AspectJ está diseñado como una extensión a la especificación del lenguaje Java. De la misma forma que Java, se trata de un lenguaje multipropósito, es decir, no está ligado a un tipo de dominio en particular. Es una implementación diseñada para ser compatible en varios niveles [Kic 01]: Compatibilidad con versiones anteriores: todos los programas escritos en Java, son también programas válidos en AspectJ. Compatibilidad de plataforma: todos los programas AspectJ corren en máquinas virtuales de Java (JVM) tradicionales, sin ninguna modificación adicional. Compatibilidad de herramientas: las herramientas de desarrollo de Java son extensibles de una forma natural para poder trabajar con programas AspectJ. Compatibilidad para el programador: para el programador, AspectJ es una extensión natural del lenguaje Java. Las dos formas de implementación transversal que brinda AspectJ son: Implementación transversal dinámica: permite definir comportamiento adicional que se ejecutará en puntos específicos dentro del programa. Implementación transversal estática: permite modificar la estructura estática del programa, agregando superclases o implementaciones de interfaces. La implementación transversal dinámica se logra a partir de los join points, mientras que la implementación transversal estática se logra con las declaraciones de miembros inter-clase y las declaraciones de extensión e implementación. En todos estos 18 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA casos la modificación de la estructura de clases se realiza desde un elemento localizado y modular como lo es un aspecto. 3.5.1. Sintaxis de un aspecto En AspectJ, un aspecto se define de manera similar a una clase y tiene la siguiente sintaxis: <Aspecto> [ [ [ { := privileged ] [ <Modificadores> ] aspect <Identificador> extends <Tipo> ] [ implements <ListaDeInterfaces> ] <ClausulaDeInstanciación> ] <Cuerpo> } La palabra reservada “class” que define una clase en Java es reemplazada por “aspect” para el caso de un aspecto en AspectJ. De la misma forma que una clase, un aspecto debe tener un identificador único dentro de su paquete, que le dará el nombre al aspecto. Los modificadores permitidos para un aspecto son similares a los que puede preceder a una clase: public: el aspecto es expuesto públicamente y por ende puede ser accedido desde cualquier punto en la jerarquía de paquetes. default o package: el modificador por defecto indica que sólo es posible acceder al aspecto dentro del paquete donde fue definido. final: al igual que el modificador de clases, indica que el aspecto no puede ser extendido. abstract: de la misma forma que en una clase, los aspectos abstractos tienen la característica de contener elementos abstractos, es decir, sólo definidos pero no implementados. En el caso de los aspectos, estos elementos pueden ser atributos, métodos o pointcuts. La palabra reservada privileged es un modificador introducido por AspectJ exclusivamente para los aspectos e indica que el aspecto tiene el privilegio de acceder a miembros de clases que comúnmente no podría, ya sean privados, protegidos o “default” de otros paquetes. Los aspectos, al igual que una clase, pueden implementar interfaces y/o extender otra clase. Además, pueden extender de otros aspectos, manteniendo la misma semántica que en el caso de las clases, es decir, se heredan los elementos públicos y protegidos del aspecto o clase base. Como restricciones, los aspectos no pueden implementar ni la interface “Serializable” ni la interface “Clonable”. La semántica de estas interfaces no tiene sentido para el caso de un aspecto. Si bien un aspecto puede extender tanto de una clase como de un aspecto, lo contrario no es cierto en AspectJ, una clase sólo puede extender otra clase, ya que los aspectos tienen un grupo de elementos que incluyen a los de un clase. Es decir, un aspecto es una clase con elementos adicionales. Diego M.S. Erdödy Programación Orientada a Aspectos 19 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA A diferencia de una clase, un aspecto no puede poseer constructores debido a que no puede ser instanciado explícitamente. En cambio, el aspecto es automáticamente instanciado. El momento y forma de su instanciación depende de la política que se defina en la <ClausulaDeInstanciación>. Las distintas políticas disponibles son: issingleton(): política por defecto que indica que existirá sólo una instancia del aspecto. perthis( <Pointcut> ): una instancia es creada y asociada a cada objeto que actúa de iniciador al llegar al punto de ejecución definido por cualquiera de los join points que forman el pointcut que se recibe como parámetro. pertarget( <Pointcut> ): similar al caso anterior, pero con la diferencia que la instancia se asocia al objeto destino del join point perteneciente al pointcut pasado como parámetro. percflow( <Pointcut> ): es la abreviación de “per control flow” (por cada control de flujo). El aspecto es instanciado al entrar en el flujo de ejecución definido por el pointcut. A diferencia de los dos casos anteriores, puede haber más de una instancia de un aspecto definido para el mismo objeto, si el flujo del programa pasa más de una vez por los join points definidos en el pointcut. En los últimos dos casos sólo puede haber una instancia del mismo aspecto asociada a un objeto. percflowbelow( <Pointcut> ): Variación del caso anterior en donde la instanciación se hace inmediatamente después de llegar a uno de los join points del pointcut. pertypewithin( <PatronTipo> ): A diferencia de los demás tipos de instanciación, pertypewithin recibe como parámetro una expresión del tipo <PatronTipo>. Dicha expresión representa un conjunto de Clases o Interfaces con ciertas características en común y será explicada en detalle más adelante. Al utilizar este tipo de instanciación, una instancia del aspecto es creada por cada Clase o Interface que concuerda con la expresión. Por último, un aspecto debe poseer un cuerpo en donde se definen los elementos integrantes del aspecto. Estos elementos pueden ser, al igual que en una clase: atributos, métodos, clases internas o interfaces internas, teniendo en cuenta que un aspecto no puede definir constructores. En el cuerpo de un aspecto también se pueden definir los siguientes elementos: pointcut, advice, declaraciones inter-tipo y otras declaraciones. En las siguientes secciones se analiza la gramática y semántica de cada uno de estos elementos. 3.5.2. Definición de un pointcut Un pointcut, es la estructura que no se asemeja a ninguno de los miembros tradicionales de una clase. Básicamente, se trata de un identificador seguido de parámetros y la definición de una expresión que indicará cuales son los join points a capturar. La estructura formal es: 20 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <Pointcut> := <PointcutAbstracto> | <PointcutConcreto> <PointcutAbstracto> := abstract [ <Modificadores> ] pointcut <Id> ( <Parametros> ); <PointcutConcreto> := [ <Modificadores> ] pointcut <Id> ( <Parametros> ) : <PointcutExpression> ; El primer caso corresponde a un pointcut abstracto el cual, de la misma forma que un método abstracto, sólo declara la “firma” del pointcut, comprendida por el identificador y sus parámetros. Al igual que en un método abstracto, su objetivo es declarar la existencia del elemento para que pueda ser referenciado dentro del aspecto, dejando abierta la posibilidad de definirlo en aspectos derivados. El segundo caso se aplica para los pointcuts concretos. El pointcut se define de la misma forma que en el primer caso, sin la palabra reservada “abstract”. Los modificadores posibles son los mismos que para el caso de un aspecto: public, private, default y final. Estos tienen la misma semántica. Los parámetros tienen la misma sintaxis que en el caso de los métodos de Java, es decir, una lista separada por coma de parámetros los cuales se componen del tipo (clase o interface) y el identificador del parámetro. Si bien los pointcuts se pueden sobrescribir en aspectos derivados, al igual que un método, no existe la noción de sobrecarga. Eso quiere decir que no se puede declarar más de un pointcut con el mismo identificador en un mismo aspecto o aspectos derivados, que posean una lista de parámetros distinta, ya sea en cantidad o en tipos. La expresión <PointcutExpression> está formada principalmente por elementos llamados pointcuts primitivos. Hay varios tipos de pointcuts primitivos y cada uno define un conjunto específico de join points prefijado dentro del universo de join points posible en un programa. Dicho conjunto puede ser limitado a través de los parámetros que se le proveen. Estos pointcuts primitivos pueden ser combinados entre sí con operaciones de conjuntos para formar la expresión final que especificará exactamente los join points que integrarán el pointcut en cuestión. Las operaciones disponibles son: unión (||), intersección (&&) y complemento (!). Por último, en el caso de utilizar parámetros dentro de la declaración del pointcut, se deben utilizar operadores especiales que se encargan de enlazar los parámetros con elementos del join point, como se verá al final de la sección. AspectJ permite utilizar los siguientes tipos de pointcuts primitivos: call( <PatronFirma> ): incluye los join points en donde se realice una llamada a métodos que coincidan con la firma pasada como parámetro. Esta firma puede incluir comodines en distintos puntos, para poder agrupar varios métodos relacionados con una sola expresión. La expresión <PatronFirma> puede ser una patrón de método o de constructor y la sintaxis formal es: Diego M.S. Erdödy Programación Orientada a Aspectos 21 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <PatronFirma> := PatronMetodo | PatronConstructor <PatronMetodo> := [ <PatronModificadoresMetodo> ] <PatronTipo> [ <PatronTipo> . ] <PatronIdentificador> ( <PatronTipo> | .. , ... ) [ throws <PatronThrow> ] <PatronConstructor> := [ <PatronModificadoresConstructor> ] [ <PatronTipo> . ] new ( <PatronTipo> | .. , ... ) [ throws <PatronThrow> ] Como se puede ver en la sintaxis formal, la diferencia entre ambos es que para el caso de un constructor, no hace falta el tipo de retorno y en vez de un identificador, se utiliza la palabra “new”. Las expresiones <PatronModificadoresMetodo> y <PatronModificadoresConstructor> son simplemente una enumeración de los modificadores de Java que se pretenden incluir o el modificador precedido por el signo „!‟ si se lo quiere excluir. Los modificadores posibles para un constructor son “public”, “protected” y “private”. Para el caso de un método se agregan los modificadores “static”, “final” y “synchronized”. La expresión <PatronIdentificador> es una secuencia de caracteres válidos para un identificador en Java con la posibilidad de utilizar el carácter comodín „*‟ para reemplazar un grupo de caracteres. La expresión <PatronThrow> es una lista separadas por coma de <PatronTipo> en donde cada elemento puede ir precedido del signo „!‟ en caso de querer excluirlo. Por último, la expresión <PatronTipo> conceptualmente representa un patrón que agrupa clases o interfaces. Este patrón es utilizado en la expresiones <PatronMétodo> y <PatronConstructor> para reemplazar el tipo de retorno del método, el calificador (a qué tipo pertenece el método) y a los tipos de cada uno de los parámetros. A partir de la versión 1.5 de AspectJ se introdujo compatibilidad con Java 1.5 y es por eso que se aceptan anotaciones dentro de este patrón. La sintaxis es: 22 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <PatronTipo> := <PatronTipoSimple> | ! <PatronTipo> | ( [ <PatronAnotacion> ] <PatronTipo> ) <PatronTipo> '&&' <PatronTipo> | <PatronTipo> '||' <PatronTipo> <PatronTipoSimple> := <PatronNombreTipo> [ + ] [ '[]' ... ] Esta definición recursiva, muestra que el <PatronTipo> consiste de expresiones simples de tipos que pueden ser combinadas con operadores booleanos y a los cuales se les puede indicar un patrón de anotación. El <PatronTipoSimple> consta de una expresión <PatronNombreTipo> que consta de una cadena de caracteres para especificar un identificador de tipo, opcionalmente con su paquete y puede contener dos tipos de comodines, „*‟ y „..‟. El comodín „*‟ reemplaza una cadena de caracteres que no incluyan el carácter „.‟ mientras que el comodín „..‟ reemplaza un grupo de términos separados por el carácter „.‟. Si bien se pueden agrupar distintos tipos de esta manera (basados en patrones de nombre o de paquete), el signo „+‟ permite hacer coincidir además, a todos los tipos derivados de las coincidencias originales, es decir clases o interfaces hijas según sea el caso. En caso de tratarse de vectores se deben agregar la cantidad correspondientes de pares de corchetes según la dimensión. La expresión <PatronAnotacion> permite reducir el grupo de coincidencias basándose en las anotaciones que posee el elemento. La sintaxis es: <PatronAnotación> := [ ! ] @ <PatronNombreTipo> | ( <ExpresionTipo> ) ... Es una lista de términos encabezados por el signo „@‟. La semántica en el caso de los términos de la lista es la conjunción, es decir una coincidencia se dará cuando se cumplan las condiciones de todos los términos. Opcionalmente se puede preceder a cada término por el signo „!‟ para negarlo. Cada término puede ser simple o complejo. En el primer caso consta de un identificador de tipo. En el segundo caso, la expresión debe ir encerrada entre paréntesis y consta de una expresión booleana de identificadores del primer tipo con la posibilidad de combinarlos con los operadores „||‟, „&&‟ o „!‟. execution( <PatronFirma> ): similar al pointcut primitivo del tipo call, con la diferencia de que el join point se encuentra inmediatamente después de haber realizado la llamada e inmediatamente antes de iniciar la ejecución del Diego M.S. Erdödy Programación Orientada a Aspectos 23 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA método. La expresión <PatronFirma> es la misma que se describió en el caso anterior. get( <PatronAtributo> ): representa los join points en donde se efectúa una lectura de los atributos que coinciden con la expresión <PatronAtributo>. Dicha expresión tiene la siguiente sintaxis: <PatronAtributo> := [ <PatronAnotacion> ] [ <PatronModificadoresAtributo> ] <PatronTipo> [ <PatronTipo> ] . <PatronIdentificador> La expresión consta de un patrón de anotación opcional seguido de un <PatronModificadoresAtributo> que tiene la misma sintaxis que en los casos anteriores y las posibilidades son equivalentes a los modificadores permitidos para un atributo en Java, es decir, “public”, “private”, “protected”, “static”, “transient” y “final”. La expresión se completa con un término <PatronTipo>, explicado anteriormente, que indica el tipo de los atributos a capturar y otra expresión opcional del mismo tipo que especifica a qué clase pertenecen los atributos a capturar. Por último se especifica el patrón que aplicará al identificador del atributo. set( <PatronAtributo> ): similar al pointcut primitivo del tipo “get”, con la diferencia que se seleccionan los join points de escritura en vez de lectura, de los atributos que coincidan con el patrón especificado. handler( <PatronTipo> ): representa los join points en donde se captura una excepción del tipo indicado por la expresión <PatronTipo>. Esta captura está siempre especificada por una cláusula “catch” en Java. adviceexecution(): representa los join points donde se inicia la ejecución de un advice. No requiere argumentos. within( <PatronTipo> ): representa el conjunto de todos los join points que tienen como elemento en la cadena de llamadas al tipo especificado en la expresión pasada como argumento. withincode( <PatronFirma> ): similar al pointcut primitivo anterior, con la diferencia que en vez de aplicarse a un tipo, se aplica a una firma, es decir un método o constructor. Formalmente, el conjunto de todos los join points que tienen como elemento en la cadena de llamadas a un método o constructor que coincida con la expresión pasada como argumento. cflow( <PointcutExpression> ): representa los join points que están dentro del flujo de ejecución de los join points capturados por la expresión pasada como parámetro, incluyendo a estos últimos. Dentro del flujo de ejecución significa que los join points figuran dentro de la cadena de llamada. 24 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA cflowbelow( <PointcutExpression> ): similar al caso anterior, con la diferencia que los join points representados por la expresión pasada como parámetro, son excluidos del conjunto. staticinitialization( <PatronTipo> ): captura los join points del inicio de la ejecución de un inicializador estático para los tipos que coinciden con el patrón pasado como parámetro. initialization( <PatronFirma> ): captura los join points del inicio de la ejecución de un constructor desde que retorna la ejecución del constructor padre (super) hasta el retorno del primer constructor, para los constructores que coinciden con el patrón pasado como parámetro. preinitialization( <PatronFirma> ): a diferencia del anterior, los join points capturados son los de la ejecución desde el inicio del constructor hasta la llamada del constructor padre (super). Para poder enlazar los parámetros que se le proveen al pointcut con la expresión que lo definirá, se cuenta con un proceso denominado “Exposición de Contexto”. En dicho proceso, ciertos operadores llevan a cabo la tarea de relacionar los parámetros del pointcut con objetos definidos según el tipo de operador. Estos operadores son: this( <Tipo> | < PointcutVar> ): representa el conjunto de join points en donde el objeto que se está ejecutando es del tipo indicado por el parámetro. En el caso de pasar como parámetro un identificador de variable del pointcut, se asigna a dicha variable el objeto en ejecución al momento de alcanzar el join point. target( <Tipo> | < PointcutVar> ): similar al caso anterior con la diferencia de que en vez de referirse al objeto que se está ejecutando, se refiere al objeto destino del join point. Por ejemplo, en un join point del tipo “call” corresponderá al objeto sobre el cual se efectúa la llamada. args( <Tipo> | <PointcutVar> , … ): en este caso, se trata de referenciar los argumentos del pointcut. A diferencia de los otros dos casos, recibe una lista de argumentos que pueden ser tipos o identificadores. Semánticamente representa los join points cuyos tipos parámetros coinciden con los tipos (o los tipos de los identificadores) pasados como parámetro. En el caso de proveerse identificadores como parámetro, a dicha variable se le asigna el valor que posee el argumento correspondiente en el join point al momento de ser capturado, por ejemplo al ejecutar un advice. 3.5.3. Declaración de un advice Un advice define el comportamiento que deberá ejecutarse en los join points capturados por un pointcut. La declaración es muy similar a un método, con la diferencia que se debe especificar a qué tipo pertenece y sobre qué pointcuts actuará. La sintaxis formal de la declaración de un advice es: Diego M.S. Erdödy Programación Orientada a Aspectos 25 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <Advice> := [ strictfp ] <AdviceSpec> [ throws <ListaDeTipos> ] : <PointcutExpression> { <CuerpoAdvice> } <AdviceSpec> := before ( <Argumentos> ) | after ( <Argumentos> ) | after ( <Argumentos> ) returning [ ( <Argumento> ) ] after ( <Argumentos> ) throwing [ ( <Argumento> ) ] <Tipo> around ( <Argumentos> ) | | El modificador opcional strictfp, al igual que en Java, indica que las operaciones de punto flotante realizadas dentro del código del advice deben tener estrictamente la precisión indicada por el estándar, independientemente del hardware utilizado. La expresión <AdviceSpec> contiene el tipo del advice a declarar junto con los parámetros necesarios. Los tipos disponibles de advice en AspectJ son los siguientes: before: el código se ejecuta antes de los join points capturados. after: el código se ejecuta después de los join points capturados. Por defecto, el advice se ejecuta sin importar si el código que sigue al join point, finalizó normalmente o por una excepción. Para indicar específicamente cada uno de estos dos casos, se utilizan las palabras reservadas “returning” y “throwing” respectivamente al final de la expresión, seguidos opcionalmente de un argumento entre paréntesis que representa el valor de retorno o la excepción capturada. around: el método se ejecutará en vez del join point capturado. Por ejemplo, si el join point es del tipo “call”, la llamada original no se realizaría (a no ser que se especifique explícitamente en el método del advice) y en cambio se ejecutaría el código indicado en el advice. Al igual que un método, se puede indicar opcionalmente la lista de excepciones que pueden ocurrir dentro del cuerpo del advice, precedida de la palabra throws. Por último, se completa la declaración del advice con un signo „:‟ seguido de una <PointcutExpression> que ya fue definida anteriormente y el cuerpo de advice encerrado entre llaves. 3.5.4. Ejemplos Una vez definidos los tres elementos fundamentales (aspecto, pointcut y advice) se muestran distintos ejemplos para clarificar las definiciones hasta aquí presentadas. Los ejemplos están basados en una implementación de un modelo sencillo de cuentas bancarias. La estructura del ejemplo es la siguiente: 26 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Customer Account -id : String -name : String -address : String -accounts : List<E->Account> -amount : double -id : String <<getter>>+getAccounts() : List<E->Account> <<getter>>+getAddress() : String <<setter>>+setAddress( address : String ) : void <<getter>>+getId() : String <<setter>>+setId( id : String ) : void <<getter>>+getName() : String <<setter>>+setName( name : String ) : void -has 0..* <<getter>>+getAmount() : double <<getter>>+getId() : String <<setter>>+setId( id : String ) : void <<JavaElement>>+deposit( dAmount : double ) : void{JavaAnnotations = @Transaction} <<JavaElement>>+withdraw( dAmount : double ) : void{JavaAnnotations = @Transaction} +transferTo( destination : Account, dAmount : double ) : void SavingAccount SecurityManager CurrentAccount +userLogged( user : User ) : boolean User <<getter>>+getId() : String Sentinel Teller -id : String -id : String <<constructor>>+Sentinel( nid : String ) <<getter>>+getId() : String <<getter>>+getAccounts( cust : Customer ) : void <<constructor>>+Teller( nid : String ) <<getter>>+getId() : String <<getter>>+getAccounts( cust : Customer ) : List<E->Account> Figura 3-5: Diagrama de estructura para los ejemplos de AspectJ Los clientes están representados por la clase Customer que tiene atributos como identificación, nombre, dirección y una lista de cuentas. La clase abstracta Account encapsula el comportamiento de una cuenta y posee dos clases derivadas SavingAccount y CurrentAccount, para una caja de ahorro y una cuenta corriente respectivamente. La cuenta posee una identificación y un monto. Además de consultar el saldo, se puede depositar o extraer dinero mediante los métodos deposit() y withdraw(). Por otro lado, los usuarios internos del sistema están representados por la interface User. Existen dos tipos: Sentinel y Teller, que representan un empleado de seguridad y un cajero, en ese orden. Por último, la clase SecurityManager, permite averiguar si un usuario está registrado en el sistema a través del método estático userLogged(). El siguiente ejemplo muestra la implementación más simple de un interés transversal de auditoría sobre todas las llamadas críticas, es decir los movimientos de fondos. Diego M.S. Erdödy Programación Orientada a Aspectos 27 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA public aspect Audit { public pointcut auditableCall(): call(public * Account+.deposit(..)) || call(public * Account+.withdraw(..)); before(): auditableCall() { audit("Audit transaction: " + thisJoinPoint); } } El interés es implementado por el aspecto Audit, el cual consta del poincut auditableCall() que representa las llamadas a auditar. En este caso, por simplicidad, las llamadas a los métodos de depósito y extracción son enumeradas (vinculadas con el operador „||‟). Nótese que se utilizan comodines para el tipo de devolución de los métodos, así como también para los argumentos. Esto garantiza que en caso de sobrecargar los métodos en futuras implementaciones, los métodos sobrecargados seguirán formando parte del poincut. También cabe destacar que al utilizar el operador „+‟, el pointcut abarca las llamadas a los métodos de la clase Account y a todas las clases derivadas, en este caso SavingAccount y CurrentAccount. Por último, el aspecto define un advice del tipo “before” sobre el poincut, para realizar la operación de auditoría, representada por el método audit(). Para hacer la implementación más flexible, se puede usar metainformación para describir la funcionalidad de los participantes del sistema. De esta manera, se pueden usar construcciones que consuman esta metainformación y así evitar la enumeración. Si los métodos deposit() y withdraw() de la clase Account, estuvieran modificados con la anotación de negocio @Transaction, describiendo a las operaciones monetarias atómicas, el poincut se podía reescribir de la siguiente manera: public pointcut auditableCall(): call(@Transaction public * Account+.*(..)); De esta forma se están capturando todas las llamadas a métodos públicos de la clase Account o derivadas, que estén modificados con dicha anotación. Por último se analiza el caso de la implementación de un interés de seguridad. Se pretende que el acceso a cuentas de un cliente, sea permitido sólo a los cajeros que están registrados en el sistema. El aspecto que lo implementa es el siguiente: 28 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA public aspect AccessRestriction { public pointcut securityCalls(): call( * Customer+.getAccounts(..)); public pointcut checkedCalls(User usr): within(Teller+) && securityCalls() && this(usr); public pointcut forbiddenCalls(): !within(Teller+) && securityCalls(); before(User usr): checkedCalls(usr) { if (!SecurityManager.userLogged(usr)) { throw new BankingException("User should be logged"); } } before(): forbiddenCalls() { throw new BankingException("Access denied"); } } En el aspecto AccessRestriction, se comienza declarando el pointcut securityCalls() que encapsula las llamadas que se pretenden controlar. En este caso son las llamadas a métodos de acceso a las cuentas getAccounts() de la clase Customer o derivadas. A continuación, se define el pointcut checkedCalls() que representa las llamadas que se deben permitir solamente cuando el usuario esté registrado en el sistema. Está compuesto por todos los puntos contenidas en securityCalls() que sean llamados desde un objeto del tipo Teller o derivado. Además, se vincula un parámetro del tipo usuario, con el objeto origen de la invocación, es decir el cajero. Para el pointcut checkedCalls(), se define un advice del tipo “before” que consulta con el SecurityManager si el usuario está registrado. De no ser así, se lanza una excepción indicando la situación. El segundo advice lanza una excepción cuando se llega a un punto contenido en forbiddenCalls(), indicando que no se tiene acceso. Para la definición de las llamadas a controlar, en el pointcut securityCalls(), existe el peligro de que en el futuro se agreguen métodos que accedan a la lista interna del objeto, pero bajo otra convención de nombres (un iterador, por ejemplo). Esto se puede solucionar con el mecanismo de las anotaciones, aunque en este caso la anotación no tiene una semántica definida, es decir sería una solución forzada. Otra posible solución al problema, es definir el pointcut a partir del acceso a la lista interna, utilizando el join point del tipo “get”. El pointcut sería: public pointcut securityCalls(): cflow(call( * Customer+.*(..))) && get(private List<Account> Customer+.accounts); Diego M.S. Erdödy Programación Orientada a Aspectos 29 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA En esta nueva versión se está incluyendo a todos los puntos que precedan a una llamada a un método de la clase Customer o derivada (indicado con el join point “cflow”) y en cuya ejecución se acceda al atributo privado “accounts” de la misma clase. Esta implementación tiene el problema de depender de la estructura interna del objeto, aunque en este caso no es muy probable que se vaya a modificar. 3.5.5. Declaraciones de miembros inter-tipo Para la implementación transversal estática, AspectJ cuenta con las declaraciones de miembros inter-tipo. Estas declaraciones consisten en agregar miembros, ya sea atributos o métodos, a clases externas al aspecto. De esta forma se logra introducir en la clase, la lógica específica al interés que implementa el aspecto, sin que dicha clase tenga conocimiento. Existen varias estructuras sintácticas para este tipo de declaraciones, según sea el tipo de miembro que se quiera agregar. Estas son: 1) abstract [ <Modificadores> ] <Tipo> <Tipo> . <Id> ( <Parametros> ) [ throws <ListaDeTipos> ] ; 2) [ <Modificadores> ] <Tipo> <Tipo> . <Id> ( <Parametros> ) [ throws <ListaDeTipos> ] { <Cuerpo> } 3) [ <Modificadores> ] <Tipo> . new ( <Parametros> ) [ throws <ListaDeTipos> ] { <Cuerpo> } 4) [ <Modificadores> ] <Tipo> <Tipo> . <Id> [ = <Expresion> ] ; El primer y el segundo caso corresponden a declaraciones de introducción de métodos abstracto y concreto, respectivamente. En estos casos la sintaxis es casi idéntica a la declaración de un método en Java. La única diferencia es que el nombre o identificador del método debe ir precedido del nombre de la clase en donde se desea introducir. El tercer caso corresponde a la introducción de un constructor. La declaración cuenta con la particularidad de que en vez de requerir un identificador, como en el caso de un método, se debe utilizar la palabra reservada new, para indicar que se trata de un constructor. Al igual que el identificador de un método, la palabra debe ir precedida de la clase en donde se quiere introducir el constructor. El cuarto caso representa la introducción de un atributo. Sigue la forma de una declaración de atributos en Java, con la diferencia de anteponer el tipo al que se quiere introducir, antes del identificador del atributo. 30 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 3.5.6. Declaraciones de derivación Llamamos declaraciones de derivación a las sentencias de un aspecto que permiten introducir extensión de padres o implementación de interfaces en una clase dada. La sintaxis de estas declaraciones es: 1) declare parents : <PatronTipo> extends <Tipo> ; 2) declare parents : <PatronTipo> implements <ListaDeInterfaces> ; En ambos casos, la declaración comienza con las palabras reservadas “declare parents : ” y es seguida de una expresión del tipo <PatronTipo>. Dicha expresión representa un conjunto de clases a las cuales se les efectuará la introducción. El primer caso corresponde a la introducción de la extensión de la clase especificada. Como el código compilado que genera AspectJ debe ser compatible con cualquier JVM tradicional, no se pueden introducir extensiones a clases que ya poseen un padre. En otras palabras, si una clase extiende de otra, no se puede forzar a que extienda de una tercera ya que Java no permite le herencia múltiple. Existe, sin embargo, una excepción a dicha regla que consiste en introducir una extensión de una subclase del padre original. Por ejemplo, si A extiende de B, un aspecto puede hacer que A extienda de C, siempre y cuando C sea subclase de B. El segundo caso corresponde a la introducción de implementación de nuevas interfaces. Al igual que en Java, se puede especificar una lista de interfaces a implementar. La única restricción que existe, es que los métodos definidos por las nuevas interfaces, deben ser implementados, o bien con métodos preexistentes, o con métodos introducidos por el aspecto. Siguiendo con el ejemplo propuesto, se analiza la implementación del interés “cobro de servicios”, para brindar un ejemplo de declaraciones de miembros inter-tipo como de declaraciones de derivación. El objetivo es registrar los accesos a los servicios de las cuentas corrientes de cada cliente y acumularlo para poder realizar los resúmenes a fin de mes. Si bien esta implementación se podría realizar sin la ayuda de aspectos, el código asociado al interés se mezclaría con el interés principal de una cuenta que es administrar fondos. El aspecto que lo implementa es el siguiente: Diego M.S. Erdödy Programación Orientada a Aspectos 31 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA public aspect Billing { interface Billable { double getBill(); void addToBill(double amount); } public double Billable.bill; public double CurrentAccount.getBill() { return bill; } public void CurrentAccount.addToBill(double amount) { bill += amount; } declare parents: CurrentAccount implements Billable; public pointcut billableCall(Billable billable): call(@Transaction public * CurrentAccount.*(..)) && target(billable); before(Billable billable): billableCall(billable) { billable.addToBill(1); } } En el aspecto Billing, se declara la interface Billable que representa un objeto contable. Esta interface posee el método getBill() para obtener el acumulado de consumo y addToBill() para agregar un nuevo consumo. En las siguientes tres declaraciones de intertipo, se realiza la implementación de la interface, agregando un atributo que representa el acumulado de consumo y la implementación de los métodos. Este mecanismo es similar a implementar una clase abstracta, pero brinda la facilidad de poder utilizar varias implementaciones al mismo tiempo, como se haría en un esquema de herencia múltiple. La siguiente línea indica que la clase CurrentAccount debe implementar la interface Billable. Luego se declara el poincut billableCall(), similar al utilizado en el ejemplo de auditoría, el cual captura los métodos modificados con la anotación @Transaction. Por último, se define un advice de tipo “before” sobre el pointcut, para adicionar un costo arbitrario al consumo de la cuenta. Vale aclarar que los ejemplos utilizados son ilustrativos para comprender el uso de la sintáxis de AspectJ. La mayoría de los ejemplos se pueden resolver con aspectos abstractos que hacen que la implementación sea reutilizable, y aumentan las propiedades de modularidad, pero esto aumenta considerablemente la complejidad de los mismos. 32 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 3.5.7. Otras declaraciones Por último, se describen las declaraciones generales que provee AspectJ. La sintaxis de las declaraciones restantes es: 1) declare warning : <PointcutExpression> : <String> ; 2) declare error : <PointcutExpression> : <String> ; 3) declare soft : <Excepcion> : <Pointcut> ; 4) declare precedence : <ListaDeAspectos>; La primera y la segunda declaración, tienen semánticas y sintaxis muy similares. Ambas definen que cierto conjunto de join points, especificados por la expresión <PoincutExpression>, deben ser tratados por el compilador, al momento de compilación como advertencias o como errores, según se trate del primer o del segundo caso respectivamente. El mensaje a mostrar es el último término de la declaración. Este tipo de declaraciones sirven, por ejemplo, para reforzar la modularidad de cierto componente y asegurarse que sus métodos sean utilizados sólo desde algunas clases seleccionadas, es decir, casos complejos donde la visibilidad no alcanza para cumplir dicho propósito. La declaración del tipo “soft”, convierte al tipo org.aspectj.lang.SoftException la excepción especificada en la declaración que se lanza en los join points que representa el pointcut. Dicha excepción es del tipo RuntimeException, por lo que no es necesario capturarla. Por último, la declaración “precedence” indica en qué orden se deben aplicar los aspectos, en caso que haya alguna ambigüedad al respecto. Simplemente se enumera la lista de aspectos en el orden de su aplicación, pudiendo utilizar opcionalmente comodines para hacer más simples las instrucciones del tipo “primero aplicar el aspecto A y después cualquier otro que deba aplicarse”. Continuando con el ejemplo del interés de seguridad, para las llamadas prohibidas, (es decir, los pedidos de cuentas a usuarios por parte de objetos que no sean cajeros ni derivados) la acción que se tomó fue lanzar una excepción. El inconveniente que trae esta decisión es que las llamadas ilegales serán identificadas en tiempo de ejecución, cuando tal vez sea demasiado tarde. Para poder identificar dichas llamadas en tiempo de compilación, se puede usar una llamada del tipo “declare error” de la siguiente forma: declare error: forbiddenCalls() : "Access denied"; Diego M.S. Erdödy Programación Orientada a Aspectos 33 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA De esta manera utilizando extensiones que soportan el análisis sintáctico en tiempo de edición, como por ejemplo AJDT en la IDE de Eclipse, las llamadas prohibidas serán identificadas como errores de compilación tradicionales. La siguiente captura muestra este caso: Figura 3-6: Captura de Eclipse mostrando el resultado de “declare error” 3.5.8. Intercalado Con respecto a la técnica de intercalado, AspectJ utiliza un compilador dedicado, el cual genera código Java binario a partir del conjunto de clases y aspectos, ya sea en código fuente o archivos compilados, que están disponibles en tiempo de compilación. 34 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Código fuente Java (.java) AspectJ Compiler (ajc) Sistema intercalado (archivos .class + .jar ) JVM tradicional (java) Código fuente AspectJ (.aj) Archivos compilados (.jar + .class) Figura 3-7: Mecanismo general de funcionamiento de AspectJ La gran desventaja de este mecanismo es la necesidad de contar con todas las clases que intervienen en el sistema al momento de compilación, en vez de poder hacer el intercalado a medida que se van cargando las clases en tiempo de ejecución. Sin embargo, de este modo se logra una mayor performance del sistema ya que todo el código ha sido procesado e intercalado en tiempo de compilación y no existe ninguna penalidad en cuanto a performance en tiempo de ejecución. 3.6. Comparación con otras implementaciones de AOP Para poder seleccionar la herramienta que se utiliza en este trabajo, se realizó un análisis de otras implementaciones AOP, similar al trabajo de Mik Kersten [Ker 05], sobre versiones actualizadas y nuevas implementaciones. Sin entrar en los detalles sintácticos de cada una de las implementaciones, se realiza un análisis y comparan las características de cada implementación, las cuales se resumen al final en una tabla comparativa. 3.6.1. AspectWerkz AspectWerkz [AWerks 05] (AW) es un framework AOP para Java, diseñado para ser dinámico, liviano y de alta performance. La primera diferencia importante que presenta frente a AspectJ, es que la implementación no está basada en una extensión a la sintaxis del lenguaje Java. Existen dos opciones para realizar declaraciones de aspectos. La primera opción consiste en un archivo de configuración en formato XML que contiene todas las instrucciones que indican las clases que implementarán los aspectos, pointcuts, advices y demás declaraciones de AOP. Es decir que los aspectos son en realidad clases tradicionales de Java, las cuales deben cumplir con alguna convención, como por ejemplo poseer cierto tipo específico de constructor. La otra opción, incorporada recientemente para la definición de declaraciones AOP de AW, es usar anotaciones de Java 5. Básicamente, se traslada la información del archiDiego M.S. Erdödy Programación Orientada a Aspectos 35 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA vo XML a anotaciones dentro de clases tradicionales de Java. De esta forma, la información AOP se encuentra mucho más localizada que en el caso del archivo de configuración, permitiendo un mejor entendimiento y facilitando el mantenimiento. Ambas opciones tienen la desventaja de no proveer un chequeo sintáctico de las expresiones de pointcuts en tiempo de compilación. Dado que dichas expresiones son atributos de XML o argumentos de anotaciones, según la opción que se prefiera, los chequeos que se realizan en tiempo de compilación no analizan las expresiones. En AW en cambio, las expresiones son verificadas en tiempo de ejecución. En el caso de AJDT, el plugin de AspectJ para Eclipse, los errores son marcados en tiempo de edición. Esto no se puede lograr con anotaciones ni con archivos XML. En cuanto a la estrategia de intercalado, AW utiliza varios modelos según la versión de Java y el tipo de JVM que se utilice. La opción más moderna, es el intercalado en tiempo de carga que se puede realizar con la nueva API de instrumentación de Java 5, especificado en el Java Specification Request número 163 del Java Community Process [JSR 163]. Dicha API, permite especificar al iniciar la JVM, una o más clases con ciertas características que actuarán de “agentes java”. Estas clases deben contener un método llamado “premain” el cual será llamado al iniciar la JVM. Uno de los argumentos de este método, es un objeto que implementa la interface “Instrumentation” que brinda los servicios específicos de instrumentación. Entre estos servicios, se encuentra la posibilidad de incorporar implementaciones propias de la interface “ClassFileTransformer” con la habilidad de modificar el código intermedio de las clases cargadas. AW soporta la mayoría de los pointcuts primitivos soportados por AspectJ con las excepciones de adviceexecution, cflowbelow, initialization y preinitialization. Los únicos dos pointcuts de AW que no posee AspectJ son hasmethod y hasfield. Ambos ayudan a limitar la cantidad de join points seleccionados en relación a propiedades estructurales como es la existencia de otros métodos o atributos. AW no soporta las políticas de instanciación del tipo pertarget ni percflow que posee AspectJ. 3.6.2. JBoss AOP JBoss AOP [JBA 06][Bur 03], es un módulo de soporte de AOP ideado para utilizar con el Servidor de Aplicaciones JBoss [JBS 06]. Si bien los conceptos básicos de AOP están presentes en JBoss AOP (JBA), la forma de presentarlos tiene ciertas diferencias con AspectJ. En primer lugar, JBA al igual que AW, ofrece dos opciones para declarar los elementos AOP que acompañan a las clases que los implementan: anotaciones de Java5 (o un equivalente de XDoclet [XDoc 06] en Java 1.4) o un archivo de configuración XML. La funcionalidad que se logra con cualquiera de las dos opciones es la misma, ya que existe un mapeo entre ambas notaciones. En segundo lugar, en JBA las declaraciones de AOP se pueden realizar en dos tipos distintos de clases, a diferencia de AJ o AW en donde sólo existe el aspecto. En JBA se utiliza la noción de “interceptores” para poder declarar advices, aunque sigue existiendo la noción de aspecto. Estos interceptores son simplemente clases que deben implementar una interface específica (Interceptor) y ser afectadas por la anotación que la identifi36 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA cará como tal. La mayoría de los elementos AOP restantes pueden declararse tanto dentro de un aspecto como dentro de un interceptor. A diferencia de las implementaciones analizadas anteriormente, JBA sólo soporta los advices del tipo “around”, es decir, los interceptores reemplazan a los join points interceptados. La interface Interceptor contiene un método que se ejecutará al momento de llegar al join point y tiene como argumento un objeto del tipo Invocation. Este objeto brinda por un lado meta-información sobre el join point actual (p. ej. tipo de miembro, nombre del método/constructor, argumentos, etc.) y por otro lado acciones tales como “seguir con el joinpoint”. De esta forma se logran simular los advices del tipo “after” o “before”por medio de programación. En otras implementaciones se puede hacer también en forma “declarativa”, que resulta más sencillo y visual. Los pointcuts tienen una sintaxis similar a la de AJ y son representados por un atributo estático de tipo “Pointcut” modificado por la anotación que lo identifica. Los advices deben relacionarse con los pointcuts a través de declaraciones especiales denominadas “Bind” en JBA. Los pointcuts primitivos de AJ que no son soportados por JBA son: handler, adviceexecution, staticinitialization y preinitialization. Al igual que AW, adiciona los siguientes pointcuts primitivos “has” y “hasfield” que sirven para relacionar con la estructura del tipo, es decir si poseen cierto atributo, método o constructor. Por último, JBA posee un pointcut primitivo llamado “field” que es equivalente a la expresión “get(x) || set(x)” en AJ. En cuanto a la introducción de elementos, JBA permite introducir solamente nuevas implementaciones de interfaces, no soporta la extensión de una nueva clase. Para poder implementar los métodos de las interfaces introducidas, JBA utiliza el concepto de “mixin”. Un mixin en JBA, consta de una clase que implementa las interfaces a introducir y que cuenta con un constructor que recibe el objeto en donde las interfaces serán introducidas. Esta clase debe ir acompañada de la declaración del mixin en donde se indica qué interfaces se quiere introducir en qué clase y cual mixin lo implementará. No es posible introducir métodos o atributos a una clase, si no es por medio de la introducción de un nueva interface y de un mixin que la implemente. Las políticas de instanciación son muy similares a las de AJ, salvo que no permite “percflow”. En cambio, permite instanciación por clase a diferencia de AJ. Las declaraciones adicionales de AJ también son soportadas en su mayoría, excepto las del tipo “soft”. El tipo de intercalado que utiliza JBA, es en tiempo de carga, llevado a cabo por un classloader especial que debe indicarse en el momento de inicializar la VM. 3.6.3. Spring AOP Spring nació como un framework genérico y modular de soluciones elegantes a problemas comunes en aplicaciones de empresa, como por ejemplo la configuración de componentes. Este problema es resuelto por uno de los módulos más populares de Spring llamado IoC (inversión de control) [Fow 04]. El módulo de AOP [Rus 04], según la propia documentación, no fue diseñado para ser la solución más completa en AOP, sino para proveer una buena integración con el módulo de IoC. Es por esto, que la declaración de Diego M.S. Erdödy Programación Orientada a Aspectos 37 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA elementos AOP en Spring, se realiza en el mismo archivo de configuración xml en donde se definen y configuran los componentes. En Spring, los pointcuts se declaran a través de clases que deben implementar la interface “Poincut”. Esta interface tiene dos métodos destinados a seleccionar las clases y métodos que incluirá el pointcut. La implementación más usada, se basa en expresiones regulares. Los advice se deben implementar también en una clase que implemente a la interface del tipo de advice que se desee aplicar, por ejemplo “AfterReturningAdvice”. Dentro de dicha clase se debe implementar el método declarado que contendrá la lógica del advice. Este método cambia de nombre dependiendo del tipo de advice. Para el caso de AfterReturningAdvice, el método que se declara se denomina “afterReturning”. Los otros tipos de advice soportados son “before” y “throws”. Este último cumple la misma función que la declaración “after throwing” de AspectJ. Al igual que JBoss AOP, Spring incorpora el concepto de interceptores. La idea es muy similar, existe una interface por cada tipo de interceptor (de método, atributo o constructor) las cuales definen métodos que serán llamados al momento de llegar al punto interceptado. Estos métodos proveen, a través de los argumentos, la meta información sobre el punto de intersección que se está utilizando (nombre del método, argumentos, etc.). Para introducir nuevas interfaces en Spring, se utiliza un mecanismo similar al caso del advice. Se debe también implementar una interface específica que define varios métodos. Uno de los métodos está destinado a seleccionar las clases destino de la introducción. Otro de los métodos indica la lista de clases que serán las interfaces introducidas. Por último, un tercer método, similar a los utilizados para interceptores de métodos, implementa los métodos de las interfaces introducidas, utilizando un mecanismo de identificación por nombre de los métodos, lo cual resulta engorroso. Si bien en Spring no existe la noción de aspecto, la conjunción de implementación de advices y pointcuts en una misma clase se la conoce como “Advisor”. En Spring, los Advisors son las unidades de modularización y reutilización, similares a los aspectos de AspectJ. El intercalado de los elementos AOP en Spring, está inherentemente relacionado al módulo de inversión de control que provee. Como la instanciación de los componentes está controlada por el framework, para poder intercalar elementos foráneos a la clase definida, se utiliza una clase provista por el módulo AOP que actúa de Proxy y realiza los intercalados necesarios. De esta forma, el usuario del componente puede utilizar la implementación del mismo de forma transparente y al mismo tiempo se pueden intercalar dinámicamente los elementos AOP. Una de las grandes falencias de Spring AOP es la falta de un verdadero lenguaje de pointcuts. Es por esto que en la nueva versión 2.0, actualmente en fase beta, se incorporará la implementación de AspectJ dentro del framework. 3.6.4. CaesarJ CaesarJ [Mez 03] es un proyecto nacido en la Universidad Alemana de Darmstadt. Actualmente se encuentra en la fase de desarrollo (versión 0.8) y esa fue una de las grandes desventajas al momento de seleccionar la implementación AOP para este trabajo. 38 Programación Orientada a Aspectos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Al igual que AspectJ, CaesarJ es una extensión al lenguaje Java cuyo compilador genera código intermedio compatible con cualquier JVM tradicional. CaesarJ también utiliza el mismo modelo de pointcuts propuesto por AspectJ, es decir que soporta los mismos pointcuts primitivos y la misma sintaxis de declaración tanto de pointcuts como de advices. Si bien los diseñadores de CaesarJ reconocen la importancia del modelo de pointcuts dentro de una implementación AOP, también consideran importante una clara separación entre la implementación de un aspecto y su vinculación dentro de una aplicación. Implementaciones AOP como AspectJ no cuentan con un mecanismo explícito para dicha tarea. Para solucionar este problema es que se plantea el concepto de “Interfaces de Colaboración de Aspectos” (ACI, del inglés Aspectual Collaboration Interfaces). Las ACI son interfaces cuyas declaraciones de métodos se dividen en “provistos” (provided) y “requeridos” (required), es decir lo que el aspecto brinda al contexto en donde es aplicado y lo que espera obtener de dicho contexto. La implementación (o implementaciones) de la interface deberá definir los métodos “provistos”, aunque puede también referenciar métodos “requeridos” como si se trataran de métodos abstractos. Los métodos “requeridos” deben ser definidos en módulos especiales denominados “bindings”. En dichos módulos es también donde se definen detalladamente los pointcuts utilizados por el aspecto. A través de las ACIs y su implementación dual, se permite desacoplar de una manera clara, la lógica propia del aspecto de la lógica específica a la aplicación dentro del contexto. Existen clases especiales denominadas “weavelets” que son las encargadas de definir la implementación y el binding que se utilizará con un ACI determinado. El mecanismo es similar a una clase con una plantilla de 2 argumentos de Java 5. Es en este punto en donde se observa claramente la flexibilidad que se logra al poder combinar una implementación con cualquier binding y viceversa. Tener un “weavelet” dentro del grupo de compilación no es suficiente para que el aspecto sea debidamente intercalado. CaesarJ provee dos mecanismos para el intercalado de los aspectos. La opción tradicional es realizar el intercalado en tiempo de compilación, definiendo un atributo con los modificadores „static‟ y „final‟ en cualquier clase, del tipo del weavelet que se quiere desplegar. La segunda opción consta de una cláusula llamada “deploy” que permite activar a un determinado aspecto en tiempo de ejecución, y dentro de un bloque perfectamente delimitado. A pesar de que CaesarJ no provee cláusulas para especificar la política de instanciación que se aplicará a un aspecto, todas las políticas provistas por AspectJ pueden ser emuladas mediante construcciones particulares. Un weavelet en CaesarJ, por defecto es instanciado en modo “singleton”. Si se quisiera instanciar en modo “perTarget” se debería utilizar un wrapper dentro de la implementación de la interface. Esto tiene como desventaja un mayor trabajo al momento de querer aplicar una política no soportada directamente por el lenguaje, lo que trae aparejado una dificultad adicional al momento de leer la implementación de un aspecto. Por otro lado, es una ventaja el hecho de que las políticas de instanciación puedan ser expresadas en términos de elementos primitivos y no de cláusulas artificiales. Diego M.S. Erdödy Programación Orientada a Aspectos 39 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 3.6.5. Conclusiones Una de las características requeridas de la herramienta de AOP para este trabajo es la mayor expresividad posible en la definición de pointcuts. Es requisito que la herramienta brinde la mayor cantidad de recursos posibles. Esto se debe a que se necesita explorar cada una de las opciones que brinda AOP al momento de abstraer y modularizar un patrón de diseño. En esta categoría, AspectJ es el más apropiado, seguido por CaesarJ, ya que este último utiliza el mismo modelo de pointcuts. Las razones de la elección de AspectJ por sobre CaesarJ, son la madurez de la implementación y el soporte que brinda la herramienta. En cuanto a soporte para el desarrollo, ambas cuentan con plug-ins que facilitan la tarea. El plug-in de AspectJ tiene considerable ventaja en cuanto a la estabilidad y el testeo ya que fue uno de los primeros y más completos plug-ins producidos. Las siguientes tablas contienen un resumen de la comparación entre las herramientas AOP analizadas en este trabajo. La primera tabla resume las características de la implementación y cantidad de elementos que soporta mientras que la segunda muestra el detalle de dichos elementos. 40 Programación Orientada a Aspectos Diego M.S. Erdödy AspectWerkz 2.0 Configuración XML / Anotaciones de Java 5 Configuración XML / Anotaciones de Java 5 JBossAOP 1.5 SpringAOP 1.2.8 Configuración XML CaesarJ 0.8.5 Extensión de Java Dinámico Sí 13 3 5 4 2 4 Una de las herramientas más maduras y con mejor documentación Mejor integración a herramientas de desarrollo Mayor flexibilidad en cuanto a características AOP En tiempo de carga (javaagent) Sí 11 3 6 3 0 0 No admite chequeo sintáctico de expresiones Mejor integración a herramientas de desarrollo Sí 11 - 1 4 1 3 No admite chequeo sintáctico de expresiones Introducción de interfaces a través de Mixins No 5 - 4 2 1 0 Integración al contenedor IoC Orientado a componentes Modelo de pointcuts rudimentario No 13 3 5 2 - 0 Implementación relativamente reciente, falta de madurez Incorpora el modelo de pointcuts de AspectJ Resuelve ciertos problemas de AspectJ En tiempo de carga (classloader) En tiempo de carga (proxies dinámicos) Tiempo de compilación y ejecución DD OD AspectJ Conclusiones TI 1.5 Extensión de Java Intercalado AD Lenguaje PE Ver. PP Impl. AOP Anot. TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Referencias: PP: Pointcuts Primitivos PE: Pointcuts de entorno AD: Tipos de Advices TI: Tipos de instanciación DD: Declaraciones de derivación OD: Otras declaraciones Diego M.S. Erdödy Programación Orientada a Aspectos 41 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Detalle de la cantidad de elementos AOP soportado por cada implementación AspectJ AspectWerkz JBoss AOP Spring AOP CaesarJ call x x x x execution get set handler adviceexecution within withincode cflow cflowbelow staticinitialization initialization preinitialization x x x x x x x x (Field Interc.) x (Field Interc.) x x x x x x x x (Constr.) hasmethod hasfield 13 this target args 3 before after after throwing after returning around 5 issingleton perthis pertarget percflow 4 parents implements parents extends 2 error warning soft precedence 4 Diego M.S. Erdödy 11 x x x 3 x x x x x after finally 6 perJVM perInstance perClass 3 0 0 all (any member) has (Method or Constr.) hasfield field (get or set) 11 Acceso Reflexivo Acceso Reflexivo Acceso Reflexivo 3 x x (Constr. Interc.) 5 Acceso Reflexivo Acceso Reflexivo Acceso Reflexivo 3 x x x x (Method Interc.) x x x x x x x x x x x x 13 x x x 3 x x x x x 1 PER_VM PER_INSTANCE PER_JOINPOINT 4 5 x PER_CLASS 4 per class 2 x x (Introd. Interc.) 1 x x 1 0 0 0 x x 3 Programación Orientada a Aspectos 1 43 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 4. Patrones de diseño distribuidos 4.1. Patrones de diseño Un patrón de diseño es la descripción de una solución repetible a un problema frecuente dentro del diseño de software. Sin embargo no se puede considerar que sea una solución final aplicable directamente. Se puede ver como una plantilla con una solución efectiva y probada a una clase específica de problemas dentro de un entorno determinado. Un patrón tiene cuatro componentes principales, según Gamma et. al [Gam 93], que son: I. Nombre: Es el identificador utilizado para describir la solución propuesta por el patrón. Conocer el nombre de los patrones tradicionales es importante para crear un vocabulario común. Dicho vocabulario puede ser utilizado en la documentación de un framework o incluso en la interacción entre colegas. II. Problema: Describe el entorno al cual el patrón está dirigido. Dicho entorno puede incluir prerrequisitos para que la aplicación del patrón tenga sentido. III. Solución: Conjunto de elementos que forman el patrón de diseño. Estos elementos incluyen los elementos del patrón con sus roles, sus responsabilidades y las relaciones entre ellos. IV. Consecuencias: Describe los beneficios y las desventajas de aplicar el patrón. Documenta las alternativas que presenta el patrón y como afectan las decisiones de compromiso que deben tomarse. Los 23 patrones tradicionales documentados están divididos en tres grupos, según el propósito. Los patrones “de creación” tratan con los problemas de instanciación de objetos. Los patrones “estructurales” están relacionados con la composición de clases u objetos. Por último, los patrones “de comportamiento” describen los problemas asociados a la vinculación y distribución de responsabilidades entre objetos. Los micropatrones de diseño [Gil 05], a diferencia de los patrones de diseño, corresponden a un menor nivel de abstracción. Los micropatrones pertenecen al grupo de los patrones “traceables”, es decir, los patrones que pueden ser identificados automáticamente y de una manera simple, dentro del código fuente de un sistema. Esto es posible, ya que los micropatrones se relacionan con la cantidad y tipo de elementos que posee una clase. Por ejemplo, uno de los 27 micropatrones identificados se denomina “Designator” y se define como una interface sin ningún miembro. Esta interface solamente sirve para marcar roles dentro de un sistema. Este mecanismo es utilizado por diferentes patrones del grupo GoF. Los micropatrones ayudan en la implementación en los patrones de diseño y es por eso que se dice que los micropatrones se encuentran en un nivel intermedio entre la codificación y los patrones de diseño. 4.1.1. Beneficios y problemas de los patrones de diseño Diego M.S. Erdödy Patrones de diseño distribuidos 45 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Los patrones de diseño permiten la reutilización del conocimiento aplicado al diseño de una solución particular. Es por eso que la documentación de dichas soluciones permite un vocabulario universal que facilita la comprensión de arquitecturas complejas. Los patrones pueden ser utilizados de forma “reactiva” o “proactiva” [Cli 96]. El primer caso corresponde a la documentación de un diseño terminado, con un formato estándar para poder almacenar el trabajo de análisis y resolución del problema en cuestión. El segundo implica la aplicación de los patrones previamente conocidos en un problema en particular, adaptándolos al entorno específico según los lineamientos que provee. Uno de los grandes inconvenientes asociados a la mayoría de los patrones de diseño es la imposibilidad de realizar una implementación reutilizable utilizando POO. Esto quiere decir que cada vez que se desee implementar un patrón en un entorno distinto, se deberá repetir el código utilizado en otros entornos. Si bien el problema tiene varios motivos que dependen del patrón en particular, se ha hallado que muchos de esos casos tienen que ver con la falta de un mecanismo de abstracción de intereses transversales. En este trabajo se analizará el grado de avance en este sentido con la aplicación de la POA como técnica de abstracción transversal. 4.1.2. Descripción de patrones de diseño Para la descripción de los patrones de diseño se analizaron las metodologías utilizadas por Gamma [Gam 95], Schmidt [Sch 00] y Powel Douglas [Dou 02]. Para este trabajo se eligió una combinación de las metodologías antes mencionadas dado que el objetivo principal no es analizar los beneficios del patrón en sí, sino explicar el uso del patrón y analizar los beneficios o desventajas de la implementación orientada a aspectos. Las secciones que se describen por cada patrón son las siguientes: Resumen: Breve descripción del patrón que tiene como objetivo dar una idea resumida del propósito del mismo. Otras denominaciones: Otros nombres bajo los cuales se puede conocer el patrón, en caso de existir. Problema: Descripción del conjunto de problemas que busca resolver el patrón de diseño. Solución: Descripción de la solución que propone el patrón. Caso de estudio: Descripción del caso de estudio a analizar para el patrón. Este caso de estudio es un ejemplo práctico de la aplicación del patrón, útil para describir el patrón de manera más sencilla y para poder desarrollar una implementación que será el elemento de análisis principal. Estructura: Diagrama UML de estructura en donde se describen los elementos intervinientes en el patrón. Se da una breve explicación general del diagrama en su totalidad. También se describen en detalle los participantes involucrados en el diagrama de estructura. Estrategia de implementación orientada a aspectos: Se describen la estructura general de la implementación orientada a aspectos junto con el detalle de cada elemento interviniente en dicha implementación. La implementación estará en 46 Patrones de diseño distribuidos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA inglés porque actualmente es un estándar y para poder compartir dicha implementación con otros grupos de estudio. Análisis de la implementación: Se verifica en qué grado se cumple con las características a analizar en cada patrón. Para cada una de las características se da una justificación del nivel encontrado. Componente gráfico: Detalle y descripción del componente gráfico desarrollado con la ayuda del framework construido y explicación de su funcionamiento y características. 4.2. Uso en sistemas distribuidos y concurrentes Se dice que un sistema es concurrente cuando está compuesto por tareas que deben interactuar y que son ejecutadas simultáneamente. Estas tareas pueden ser, por ejemplo, procesos o hilos de ejecución. Cuando dichas tareas se ejecutan en más de una máquina, las cuales se encuentran conectadas a través de una red, se dice que el sistema es distribuido. Si el sistema está sujeto a restricciones temporales, se lo llama de tiempo real. Es decir que las operaciones tienen límites de tiempo claramente especificados. Los sistemas de tiempo real se dividir en dos tipos. En el primer caso, si las operaciones deben realizarse indefectiblemente dentro de un límite de tiempo prefijado, entonces se dice que es un sistema de tiempo real duro. Un ejemplo de este tipo de sistemas es el cálculo de la presión de frenado en un automóvil. La presión debe ser calculada a tiempo, para evitar el peligro de colisión. En el segundo caso, si las restricciones, aunque son altamente deseadas, tienen un grado de flexibilidad, se dice que es un sistema de tiempo real blando. En dicha categoría se encuentran los algoritmos de codificación y decodificación de audio y video. Si se pierde una muestra, es probable que resulte imperceptible a la vista o audición humanas. El objeto de este trabajo es analizar los patrones de diseño aplicado a entornos concurrentes y distribuidos, sin tener en cuenta el estudio de las restricciones temporales. Dichos patrones se pueden clasificar en los siguientes grupos: Patrones de Concurrencia y Sincronización: Son los patrones destinados a resolver problemas que surgen en la sincronización de recursos compartidos y tareas que interactúan concurrentemente. En estos ambientes se debe asegurar que la integridad de los datos de los recursos compartidos se garantice y que las tareas que acceden o interactúan entre si no entren en deadlock o starvation. El primero de los problemas ocurre cuando todas las tareas están esperando por algún evento sin que ninguna de las partes pueda proseguir. El segundo inconveniente se produce cuando una tarea espera por un recurso, que si bien es liberado, nunca le es asignado. Patrones de Manejo de Eventos: Son los patrones que permiten la comunicación distribuida entre tareas utilizando el modelo de eventos. Los eventos permiten independizar el comportamiento y las responsabilidades de los participantes de un sistema distribuido. Se utiliza especialmente al momento de interactuar con una capa de transporte sincrónica. En el análisis previo de los patrones se detectó una Diego M.S. Erdödy Patrones de diseño distribuidos 47 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA similitud en el esquema de trabajo de los distintos patrones por lo que se eligió analizar en profundidad el patrón más representativo del grupo. Patrones de Seguridad y Fiabilidad: Son los patrones encargados de la integridad de un sistema distribuido. Proveen soluciones para mantener la integridad del sistema ante fallos, tanto aleatorios como sistemáticos. Los fallos aleatorios se basan generalmente en mal funcionamiento de hardware o condiciones externas. Los sistemáticos en cambio, se deben habitualmente a errores en el diseño o construcción del sistema. Para poder mantener la integridad, estos patrones se basan en proveer redundancia para actuar en caso de que ocurran los fallos. Dado que el esquema que utilizan estos patrones es muy similar en todos los casos, se tomó un patrón en representación del grupo. Algunos de los patrones que se han elegido, son representantes de un grupo de patrones que poseen características similares. Los patrones analizados en este trabajo son: Categoría Concurrencia y sincronización Patrón Rendezvous Observer Optimistic Locking Balking Manejo de Eventos Reactor Seguridad y Fiabilidad Watchdog 48 Notas Versión distribuida del patrón descripto por GoF Patrones similares: Guarded Suspension, Single Threaded Execution Patrones similares: Proactor, Acceptor, Half Synch/ Half Asynch, Leader/Followers Patrones similares: Monitor Actuator, Sanity Check, Safety Executive Patrones de diseño distribuidos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 5. Metodología de Análisis de patrones 5.1. Objetivos El objetivo principal de este trabajo consta en analizar características específicas de las implementaciones orientadas a aspectos de los distintos patrones de diseño distribuidos y concurrentes seleccionados y determinar las ventajas y desventajas de estas implementaciones. En este capítulo se describen los elementos a analizar en cada patrón de diseño. También se especifica el lenguaje de modelado que se utilizará para describir la implementación orientada a aspectos de cada caso de estudio, el cual estará basado en UML [UML 06]. 5.2. Descripción de las características a analizar Siguiendo el trabajo de Hannemann [Han 02], las características que se analizarán en cada caso son las que se describen a continuación. 5.2.1. Reusabilidad Indica la posibilidad de reutilizar en otro entorno, el código fuente que compone el patrón de diseño. En el patrón de diseño GoF “Facade” [Gam 95], por ejemplo, que consta de una clase que encapsula la complejidad interna de un componente para un usuario, la reutilización es nula. Esto se debe a que la clase de fachada, tiene siempre lógica atada al entorno en donde se está aplicando. 5.2.2. Transparencia de composición La transparencia de composición se define como la habilidad de combinar patrones de diseño sin que ello afecte el comportamiento que presenta cada uno por separado. Por ejemplo, el patrón “Observer”, puede ser aplicado más de una vez sobre el mismo sujeto, para exponer cambios sobre distintas propiedades. La posibilidad de aplicar el patrón varias veces sobre el mismo sujeto, prueba que cumple con la propiedad de transparencia de composición. 5.2.3. Independencia El nivel de acoplamiento que hay entre los roles que componen el patrón de diseño y las probabilidades de supervivencia que tiene cada rol fuera del patrón. Para el caso del patrón “Singleton”, el objeto sobre el cual se aplica el patrón sigue teniendo sentido fuera de éste. Si la implementación del patrón puede lograr dicho desacoplamiento de forma transparente, para el objeto sobre el cual se aplicó, se dice que es independiente. 5.2.4. Localidad del código Diego M.S. Erdödy Metodología de Análisis de patrones 49 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La proximidad física del código fuente que implementa el patrón de diseño. Si el patrón de diseño puede ser implementado con una sola unidad de implementación dentro del lenguaje elegido (por ejemplo un aspecto en el caso de AspectJ o una clase en Java) entonces se dice que la localidad es absoluta. Si en cambio, el código se encuentra diseminado en varias unidades de implementación entonces la localidad es menor. Mientras más unidades de implementación se encuentren involucradas, menor será el grado de localidad de la implementación del patrón. Para el caso de la implementación clásica del patrón “Observer”, no existe localidad ya que la implementación se encuentra dividida en más de un módulo (sujeto y observador). 5.3. Notación UML La notación de diseño que se utiliza es una extensión compatible con UML 1.2 basada en el trabajo hecho por Stein [Ste 02]. La extensión definida en dicho trabajo se basa en utilizar las herramientas de personalización del lenguaje UML, es decir, estereotipos, etiquetas y plantillas. La incorporación de comportamiento transversal, se lleva a cabo a través de colaboraciones que permiten vincular ambas partes. Para algunos elementos, como las declaraciones de derivación hubo que extender el trabajo y definir la forma de representarlos. Dichos elementos serán explicados en las siguientes secciones. Se elige adoptar una política basada en la notación UML existente principalmente por la disponibilidad de herramientas para trabajar y la estandarización lograda por el lenguaje de modelado. Para poder hacer más legibles los diagramas, se utilizó un color distinto de relleno para cada elemento que se encuentra al mismo nivel de una clase, dependiendo del estereotipo que posea. Los colores elegidos son los siguientes: Naranja para las clases. Esta es la coloración estándar. Verde para las interfaces. Celeste para los aspectos. Amarillo para las introducciones. A continuación se define la notación utilizada en este trabajo, para cada elemento de la POA. Los ejemplos han sido tomados de los patrones que serán analizados más adelante. 5.3.1. Aspect El aspecto se representa como el estereotipo « aspect », aplicable a elementos del tipo “clase”. Tanto el aspecto como la clase, comparten gran parte de los atributos ya que ambos pueden tener métodos, atributos y clases internas. Sin embargo, un aspecto posee elementos adicionales tales como el pointcut, advice o introduction. Otra diferencia es que a un aspecto se le puede indicar la política de instanciación requerida por medio de una etiqueta de UML, la cual llamaremos “instantiation”. El valor de dicha etiqueta contiene la instrucción en AspectJ que indica el método de instanciación especificado. Para 50 Metodología de Análisis de patrones Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA hacer más visible la diferencia con una clase regular en un diagrama complejo, se utiliza el color celeste de relleno. El siguiente diagrama muestra un ejemplo de un aspecto llamado PacemakerWatchDogSignal con una política de instanciación prefijada (pertarget). <<instantiation>> <<aspect>> PacemakerWatchDogSignal {instantiation = pertarget(initCall(Resetable))} <<base>> <<pointcut>>+initCall( resetable : Resetable ){base = execution (void Pacemaker+.init()) && target(r)} <<base>> <<pointcut>>+watchableCall( r : Resetable ){base = execution (void Pacemaker+.markBeat()) && target(r)} Figura 5-1: Ejemplo de notación UML para un Aspecto 5.3.2. Pointcut El aspecto es una clase de UML estereotipada. Por esa razón los puntos de corte se modelarán como métodos de un aspecto, indicándolo por medio del estereotipo « pointcut ». Este estereotipo sólo se puede aplicar a métodos y el nombre del método deberá coincidir con el nombre del punto de corte. Para puntos de corte concretos, el join point se indicará por medio de la etiqueta de UML “base”. El siguiente diagrama contiene un aspecto llamado BalkingHandler, con un ejemplo de un punto de corte concreto: <<aspect>> BalkingHandler <<pointcut>> <<base>>+executionCall(){base = call (void Flusher.flush())} Figura 5-2: Ejemplo de notación UML para un Pointcut El punto de corte corresponde a la siguiente línea en AspectJ: public pointcut executionCall() : call (void Flusher.flush()); Los puntos de corte abstractos se representarán de la misma forma que un método abstracto (en UML se escriben en letra itálica) y sin utilizar la etiqueta “base”. 5.3.3. Advice Los elementos del tipo advice se representarán también con estereotipos aplicados a métodos. El nombre del método en este caso corresponderá con el tipo de advice (after, before, etc.) precedido opcionalmente por un identificador del advice. El identificador surge de la necesidad de que no haya dos métodos con el mismo nombre. El punto de Diego M.S. Erdödy Metodología de Análisis de patrones 51 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA corte o la expresión compuesta por puntos de corte, será especificado como valor de la etiqueta “base” del método. El siguiente diagrama representa un ejemplo para este caso: <<aspect>> BalkingProtocol -busy : boolean <<pointcut>>+executionCall() <<base>> <<advice>>+around(){base = executionCall()} Figura 5-3: Ejemplo de notación UML para un Advice simple La última línea indica que el aspecto BalkingProtocol posee un advice alrededor del punto de corte executionCall(), sin especificar ningún parámetro. Esto corresponde a la siguiente línea de AspectJ: void around() : executionCall() { … } Si es necesario, se pueden agregar los argumentos del advice de la misma manera que se haría con un método. En ese caso, se deben usar dichos argumentos dentro de la expresión indicada en la etiqueta base. El siguiente diagrama representa un ejemplo de este caso: <<aspect>> RendezvousProtocol -rvs : Map<String, Rendezvous> <<pointcut>>+synchronizingCall( memb er : RendezvousMemb er ) <<getter>>+getRendezvous( groupName : String ) : Rendezvous <<setter>>+setRendezvous( groupName : String, rendezvous : Rendezvous ) : void <<advice>> <<base>>~before( member : RendezvousMember ){base = synchronizingCall(member)} Figura 5-4: Ejemplo de notación UML para un Advice con argumentos El advice del tipo “before” de la última línea, utiliza un argumento llamado “member” que luego es usado como parámetro para el punto de corte synchronizingCall(), sobre el cual se aplica el advice. Esto corresponde a la siguiente línea en AspectJ: before(RendezvousMember o) : synchronizingCall(o) { … } 5.3.4. Introduction 52 Metodología de Análisis de patrones Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Las introducciones de elementos en clases externas son los elementos con menor soporte en la especificación actual de UML. Para poder representarlas se usan “colaboraciones” de UML. Por un lado se especifica sobre cuál de las clases se realiza la introducción, dentro del aspecto correspondiente, con una colaboración especializada con el estereotipo « introduction ». Dicha colaboración sólo contiene el nombre de la clase afectada. Por otro lado se especifica una colaboración conteniendo el detalle de los elementos a introducir sobre la clase afectada. Esto se hace agregando una clase dentro de la colaboración con el nombre BaseType que contendrá los métodos o atributos a introducir. Esta colaboración debe estar asociada a la clase o interface que está afectando para dar más claridad al diagrama. El siguiente es un diagrama de estructura que contiene la especificación de una introducción: <<aspect>> RendezvousProtocol -rvs : Map<String, Rendezvous> <<pointcut>>+synchronizingCall( memb er : RendezvousMemb er ) <<getter>>+getRendezvous( groupName : String ) : Rendezvous <<setter>>+setRendezvous( groupName : String, rendezvous : Rendezvous ) : void <<advice>> <<base>>~before( member : RendezvousMember ){base = synchronizingCall(member)} <<introduction>> RendezvousMember BaseType <<introduction>> RendezvousMember -group : String <<getter>>+getGroup() : String <<setter>>+setGroup( groupName : String ) : void <<crosscut>> RendezvousMember <<getter>>+getGroup() : String Figura 5-5: Ejemplo de notación UML para una Introducción En este ejemplo se está indicando que el aspecto RendezvousProtocol realiza una introducción sobre la interface RendezvousMember. A esta interface, que sólo contiene el método getGroup(), se le introduce un atributo privado llamado group y dos métodos, getGroup() y setGroup(). Nótese que originalmente la interface sólo contenía la declaración del método getGroup() pero en la introducción se le está agregando la implementación del método. 5.3.5. Declare Para representar un elemento del tipo declare, también se usa una colaboración pero en este caso especializada con el estereotipo « declare » y se agrega el tipo de declaración que se esté utilizando (parents, warning, error, soft y precedence). Dentro de la colaboración, se introduce el cuerpo de la declaración. Dicha colaboración deberá ir dentro del aspecto que especifique la declaración. Diego M.S. Erdödy Metodología de Análisis de patrones 53 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <<aspect>> RendezvousHandler <<pointcut>> <<base>>+synchronizingCall( member : RendezvousMember ){base = call (void RoboArm.executeAction()) && target(o)} <<declare parents>> RoboArm implements RendezvousMember Figura 5-6: Ejemplo de notación UML para una declaración de implementación El ejemplo anterior indica que dentro del aspecto RendezvousHandler se especifica una declaración que indica que la clase RoboArm implementa la interface RendezvousHandler. 54 Metodología de Análisis de patrones Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 6. Framework de Visualización 6.1. Introducción Junto con el análisis de los distintos patrones de diseño distribuidos y concurrentes, se ha desarrollado un framework de visualización de componentes para la mejor comprensión de los casos de estudio. Este framework permite visualizar, configurar y relacionar los componentes pertenecientes a cada patrón y al mismo tiempo analizar los resultados de su ejecución. 6.2. Frameworks Dada la dificultad que existe para definir un framework, se han tomado las siguientes definiciones complementarias [Joh 97] [Sch 03]: I. “diseño reutilizable completo o parcial de un sistema representado por un conjunto de clases abstractas y la forma en que interactúan” II. “esqueleto de una aplicación que puede ser personalizado para cumplir con los requerimientos de un sistema” Un framework es una de las técnicas de reutilización que existen en la POO. La diferencia con otras técnicas, como los patrones de diseño, los componentes o un middleware, es el contenido que permite reutilizar y la forma de llevarlo a cabo. La primera definición indica que un framework permite reutilizar el diseño. También nos indica que se puede llevar a cabo a partir de clases abstractas, interfaces y/o la interacción que existe entre los distintos componentes o unidades funcionales. Un framework permite reutilizar el diseño aplicado a un dominio en particular, utilizando las técnicas básicas de la POO (abstracción, polimorfismo y herencia) y aplicando también las unidades de diseño que le siguen en complejidad, o sea, los patrones de diseño. Una de las herramientas principales que presenta un framework para la reutilización de diseño, es la “inversión de control”. El mecanismo consiste en que los componentes provistos por el usuario del framework, sean invocados por éste cuando sea necesario, en vez de producirse el flujo inverso. Dicha invocación se puede producir ante un evento externo, como por ejemplo, la llegada de una petición HTTP en un framework de aplicaciones Web. De esta forma, el modo en que el evento es manejado y redireccionado, es decir, el diseño del manejo de peticiones, puede ser reutilizado y el usuario puede independizarse de su problemática. La segunda definición, por otro lado, cubre intuitivamente el otro tipo de contenido que un framework permite reutilizar, o sea, la funcionalidad o código. Esta funcionalidad base o primaria es la que se implementa en el “esqueleto” que representa al framework, junto con las bases de la interacción que debe existir entre los componentes y la forma en que se deben distribuir los métodos. Diego M.S. Erdödy Framework de Visualización 55 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA En tercer lugar, un framework permite la reutilización del análisis del dominio, ya que permite identificar rápidamente cuáles son los objetos importantes y provee una terminología para enfocar el problema. Desde el punto de vista de la reutilización, existe el concepto clásico de que los patrones de diseño solamente permiten la reutilización del diseño de software. Con la introducción de la POA [Han 02], se ha demostrado que la reutilización de código a nivel de patrón de diseño, es posible en la mayoría de los casos, por lo menos en los patrones tradicionales (GoF). Dado que una implementación AOP de un patrón de diseño se modulariza en una biblioteca totalmente reutilizable, es necesario marcar una diferencia entre dicha implementación y un framework. En este sentido, los elementos que componen la implementación abstracta de un patrón de diseño, difieren de un framework principalmente en tamaño (un framework está compuesto por muchos patrones de diseño) y en la carencia de dominio específico. Si bien un framework permite la reutilización de diversos elementos, el problema típico que padece es la alta complejidad en el diseño e implementación. Esto es fácil de comprender si se tiene en cuenta que el objetivo es que, tanto el diseño como la implementación, puedan ser reutilizados por diversos sistemas. Es por eso que a menudo existe una fuerte decisión de compromiso entre la flexibilidad o generalidad y la verticalidad en los problemas que soluciona. Es decir, que mientras el framework intente resolver más detalles de un dominio, más difícil será hacerlo lo suficientemente flexible para que sea adecuadamente reutilizado por distintas aplicaciones. Los frameworks pueden ser categorizados según la forma en que se deben utilizar. Si se pueden utilizar los componentes que brinda el framework sin necesidad de conocer su implementación, entonces se los denomina del tipo “caja negra”. Si en cambio, el framework requiere que el usuario conozca y utilice elementos de la implementación, se los conoce como del tipo “caja blanca”. En el primer caso, por ejemplo, solo sería necesario instanciar clases y utilizar los objetos resultantes. Un ejemplo clásico del segundo son las extensiones de clases abstractas por parte del usuario, en donde debe conocer cuál es el mecanismo en la implementación de dicha clase, para poder insertar la nueva funcionalidad correctamente. Muchos de los frameworks se encuentran en un punto intermedio entre ambas categorías. En resumen, los siguientes elementos han sido reconocidos como característicos de un framework: Orientado a ayudar en un dominio en particular Representa una solución parcialmente completa para un dominio específico que puede ser personalizada Permite la reutilización de diseño a partir de técnicas básicas de POO y patrones de diseño Permite la reutilización de funcionalidad Permite la reutilización de análisis del dominio Una vez definido y analizado el concepto de framework, se describe el framework desarrollado para este trabajo. 56 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 6.3. Características de ACVF El framework de visualización está implementado en forma de plug-in (o extensión) de Eclipse 3.1+ y en esta sección se describen sus características principales. 6.3.1. Orientación a Componentes El framework desarrollado, se denomina “orientado a componentes” ya que el componente es el elemento atómico fundamental, que representa visualmente un rol o un actor dentro del caso de estudio. El segundo elemento importante dentro del framework, es el diagrama, en donde los componentes pueden ser desplegados y asociados entre sí a través de relaciones. Una vez desplegados, relacionados y configurados apropiadamente, el diagrama puede ser “ejecutado” a través de una simulación, la cual mostrará a los componentes interactuar entre sí. El plug-in que se desarrolló como implementación del framework, posee una paleta principal asociada al diagrama, que contiene una lista de los componentes disponibles, agrupados por categorías, como muestra el ejemplo en la siguiente figura: Figura 6-1: Paleta de componentes Los componentes pueden ser arrastrados al diagrama para armar un entorno que represente el patrón a estudiar. Cada componente puede tener asociados los siguientes tipos de elementos: Diego M.S. Erdödy Framework de Visualización 57 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Propiedades comunes: Son atributos comunes para todos los componentes que controlan la visualización básica. Se pueden observar y modificar en la vista de propiedades de Eclipse. Nombre: Nombre identificador del componente Posición: Valores absolutos de las componentes X e Y dentro del diagrama. Tamaño: Valores de ancho y alto del componente donde corresponda. Color: Color de fondo del componente para hacer más sencilla la visualización. Modo Interactivo: Propiedad booleana que especifica si el componente va a ser ejecutado en modo interactivo o no. Más adelante se detalla el significado del modo interactivo. Figura 6-2: Propiedades comunes a todos los componentes Propiedades propias del componente: Son propiedades inherentes al componente. Estas se pueden dividir en dos tipos: Estáticas: Atributos que controlan el comportamiento del componente y son de escritura/lectura. Se visualizan dentro del panel de propiedades bajo una agrupación particular en forma de pestaña. Puede haber más de una agrupación. El siguiente es un ejemplo de las propiedades estáticas de un componente del tipo “RoboArm”. Figura 6-3: Propiedades particulares para el componente RoboArm 58 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Dinámicas: Propiedades de salida del componente cuya evolución se puede apreciar a lo largo de una simulación, interactiva o no, en tiempo real. Se visualizan dentro del componente mismo. La siguiente figura muestra un componente del tipo “OptimisticRecord” con tres propiedades dinámicas, denominadas “id”, “attrib” y “version”. Figura 6-4: Ejemplo de un Componente Acciones: Son pedidos que se le pueden hacer al componente en el modo de simulación interactiva. Están representados por un botón dentro del componente. La figura anterior muestra un componente con dos acciones denominadas “save” y “retrieve”. Vale la pena aclarar que solo los componentes que posean acciones, pueden ejecutarse en modo interactivo, ya que de lo contrario no habría forma de interactuar con el componente. Consola: Panel dentro del componente en donde se muestran mensajes informativos de su estado. La siguiente figura muestra un ejemplo de información mostrada en la consola de dos componentes: Figura 6-5: Relaciones entre Componentes Representación gráfica: Cada componente puede reflejar su estado también por medio de una representación gráfica. Dicha representación constituye un modelo gráfico del componente que provee una respuesta visual a los cambios de estado y hace al componente más intuitivo y didáctico. Diego M.S. Erdödy Framework de Visualización 59 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La siguiente es una vista de la edición de un diagrama con varios componentes interactuando entre sí. Figura 6-6: Ejemplo de un diagrama con varios componentes y relaciones 6.3.2. Relaciones Cada componente interactúa con otros por medio de relaciones. Las relaciones se indican con flechas dirigidas que unen componentes. Cada componente tiene habilitada la interacción con un conjunto de tipos de componentes en particular, por lo general son los que pertenecen al mismo grupo de la paleta de componentes. Esto quiere decir que un componente puede relacionarse sólo con otro componente que sea de un tipo específico. Por ejemplo, un registro optimista se puede relacionar con una tabla, pero no con un brazo robótico. Por otro lado, hay casos en que un componente puede relacionarse con más de un componente. En otros casos la relación puede ser sólo uno a uno. 6.3.3. Simulación 60 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA El objetivo del diagrama es que los componentes puedan ejecutarse, interactúen entre si y que dicha interacción sea visible. La simulación se inicia con el botón “Run” de la barra de herramientas, que se habilita cuando se está editando un diagrama. Una vez iniciada la simulación, se puede detener con el botón “Stop”. Cuando la simulación se está ejecutando, cada componente se comporta de la manera preestablecida a partir de los valores proporcionados para sus propiedades específicas. Esto quiere decir que es en este momento en donde entra en juego la lógica propia de cada componente. Ciertos componentes tienen dos modos de actuar frente a una simulación: preestablecido o interactivo. En el modo preestablecido, el componente ejecuta la rutina que tiene prefijada y el usuario sólo puede ver su comportamiento pero no puede interactuar con el componente. En el modo interactivo el componente espera a que el usuario ejecute alguna de sus acciones. Sólo los componentes que tienen definidas acciones, tienen la propiedad “Interactive” en donde se puede definir el modo de ejecución. Por ejemplo, para el caso de estudio del patrón “optimistic locking”, se dispone de dos componentes: la tabla y el registro. Un registro se puede asociar a una tabla. La tabla no posee modo de simulación interactiva dado que no posee acciones, sólo se puede visualizar el contenido. En cambio, el registro posee un modo interactivo ya que se puede interactuar con el componente, pidiéndole por ejemplo que almacene su valor local en la tabla asociado que lo obtenga de la tabla. 6.3.4. Vista de resumen El panel de vista de resumen (“outline” en inglés) provee un listado de los componentes existentes en el diagrama. La lista contiene el nombre de cada componente precedido por el tipo de componente. De esta forma se puede acceder rápidamente a un componente en particular haciendo doble click en el elemento deseado. Figura 6-7: Vista de resumen Diego M.S. Erdödy Framework de Visualización 61 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 6.4. Arquitectura y Diseño El framework provee interfaces y clases de ayuda para la creación de componentes. Las clases abstractas provistas encapsulan el comportamiento por defecto, con el objetivo de minimizar la cantidad de código necesario para implementar un componente relativamente sencillo. El framework está implementado sobre la base del subproyecto de Eclipse denominado GEF (Graphical Editing Framework). GEF es un framework que ayuda al desarrollo de editores gráficos y a su vez, está basado sobre Draw2D. Este último es un estándar en el modelado en dos dimensiones y representa una capa de abstracción sobre la biblioteca de Eclipse denominada SWT (Standard Widget Toolkit). La siguiente figura muestra un resumen de la interacción de las distintas capas y sus funcionalidades principales: CVF GEF Draw2D SWT Contiene el modelo de componentes e interacciones Soporta el uso de simulaciones Incorpora figuras para visualización y manejo de componentes Implementa los patrones de diseño distribuídos y concurrentes estudiados Implementa MVC de editores gráficos Se integra con el Workbench de Eclipse (acciones, menúes, barra de herramientas, etc). Permite la interacción entre el usuario y la representación visual del modelo Responsable del renderizado de figuras y conexiones Permite el control de presentación de figuras Posee un sistema de coordenadas flexible Permite el ajuste de tamaño Biblioteca nativa para interfaces gráficas de la plataforma Eclipse Utiliza los controles nativos del sistema operativo siempre que sea posible Figura 6-8: Capas de la arquitectura del Framework y sus características principales GEF implementa el patrón Model-View-Controller para el desarrollo de editores gráficos y por ende permite una clara separación de responsabilidades. También permite 62 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA una integración con el Workbench de Eclipse lo que posibilita el manejo de diagramas dentro de un entorno integrado. 6.4.1. GEF Framework GEF pone énfasis en desacoplar las distintas capas de un editor gráfico y brinda especial soporte para la implementación del controlador. En GEF, el controlador es el encargado, entre otras cosas, de hacer de intermediario entre la vista y el modelo. El esquema de trabajo de alto nivel es el siguiente: Vista (IFigure) GUI Usuario Notificaciones Modelo (BaseElement) Comandos Controlador (EditPart) Figura 6-9: Interacción entre las distintas capas MVC del framework A continuación se detalla cada una de las capas: Modelo El Modelo consta de la información de base que interesa almacenar en un editor. La información que contenga el Modelo es la que va a ser persistida al momento de guardar o cerrar un editor y con la que se contará al momento de abrirlo. Si bien GEF no pone restricciones en cuanto a las interfaces o clases abstractas que debe extender el Modelo, es necesario que dicho Modelo cuente con un mecanismo para notificar adecuadamente sus cambios a terceras partes. Este mecanismo es necesario para que el Controlador pueda actuar al momento de registrar un cambio en el Modelo. Dado que GEF no suministra ninguna clase base para el armado del modelo, se confeccionó una clase abstracta denominada BaseElement, la cual brinda las características principales comunes para todos los elementos del modelo. Una de las características más importantes, es la posibilidad de registrarse para ser notificado ante el cambio de cualquiera de las propiedades del elemento, como se verá más adelante. Diego M.S. Erdödy Framework de Visualización 63 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Los cambios realizados sobre el modelo por parte del Controlador (generalmente a pedido del usuario) son representados por Comandos. Los comandos, sólo deben conocer la existencia del modelo (es por eso que se pueden considerar una extensión de éste), y deben contener la lógica para efectuar o deshacer los cambios requeridos por el Controlador. Vista La Vista compone la capa visual del editor. Dada la gran separación entre capas que propone GEF, la vista no puede conocer o hacer referencia a ningún objeto del modelo, ni tampoco tener lógica asociada al editor. Sí debe contener información propia de la representación visual de los elementos que se pretenden graficar. GEF utiliza Draw2D como capa visual base y en ella, el elemento atómico es la figura, representada por la interface IFigure. Draw2D provee una amplia gama de figuras elementales prediseñadas y la composición entre ellas permite un amplio espectro de posibilidades con un mínimo esfuerzo. Por lo general, un elemento del modelo tiene una asociado una figura de la Vista, aunque no es una limitación del framework. Controlador El Controlador tiene el papel de mediador entre el usuario, la vista y el modelo. En GEF, el controlador es llamado EditPart. Principalmente, actúa de observador del modelo, reflejando los cambios en la vista cuando es necesario. Por lo general existe un EditPart por cada elemento del modelo (aunque no es una limitación), y entre los distintos EditParts se forma una estructura de árbol, siendo el nodo raíz el correspondiente al diagrama. Los EditParts también son los encargados de administrar los pedidos de cambio del usuario, denominados Requests, y transformarlos en Comandos. Por ejemplo para el tipo de pedido “delete” (borrar), la política de edición deberá identificar el tipo elemento a borrar y crear el comando de borrado, correspondientemente. La administración también consta de aceptar, ignorar o rechazar los Requests, a través de clases que encapsulan las políticas de edición. 6.4.2. ACVF Framework El ACVF está basado en GEF, y por lo tanto provee extensiones de cada una de las capas MVC. Modelo El modelo de ACVF consta de una clase abstracta base que contiene el comportamiento común a todos los elementos, como el soporte de propiedades, ser “serializable” y la habilidad de ser “observable”. Luego existe un elemento raíz, que representa el diagrama y que contiene una lista de los elementos que lo componen. Estos elementos pueden ser Componentes o Conexiones. Vale la pena aclarar que el framework deja abierta la 64 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA posibilidad de incorporar nuevos tipos de elementos en caso de querer extender el editor, como por ejemplo, etiquetas de texto, figuras para dibujo libre, etc. El siguiente es el diagrama de estructura con una descripción de los elementos principales: BaseElement ... +addPropertyChangeListener( l : PropertyChangeListener ) : void{guarded} <<getter>>+getEditableValue() : Object +removePropertyChangeListener( l : PropertyChangeListener ) : void{guarded} <<setter>>+setPropertyValue( id : Object, value : Object ) : void <<getter>>+getPropertyDescriptors() : IPropertyDescriptor"[]" <<getter>>+getPropertyValue( id : Object ) : Object +resetPropertyValue( id : Object ) : void <<getter>>+isPropertySet( id : Object ) : boolean ... Connection Element PatternWorkbenchDiagram <<constructor>>+Connection( source : Element, target : Element ) +disconnect() : void <<getter>>+getLineStyle() : int <<getter>>+getSource() : Element <<getter>>+getTarget() : Element +reconnect() : void +reconnect( newSource : Element, newTarget : Element ) : void <<setter>>+setLineStyle( lineStyle : int ) : void <<setter>>+setPropertyValue( id : Object, value : Object ) : void <<getter>>+getPropertyValue( id : Object ) : Object <<getter>>+getIcon() : Image <<getter>>+getLocation() : Point <<getter>>+getPropertyDescriptors() : IPropertyDescriptor"[]" <<getter>>+getPropertyValue( propertyId : Object ) : Object <<getter>>+getSize() : Dimension <<getter>>+getSourceConnections() : List<E->Connection> <<getter>>+getTargetConnections() : List<E->Connection> <<setter>>+setPropertyValue( propertyId : Object, value : Object ) : void <<getter>>+getName() : String <<setter>>+setName( name_ : String ) : void <<setter>>+setColor( newColor : RGB ) : void <<getter>>+getColor() : RGB <<setter>>+setLocation( newLocation : Point ) : void <<setter>>+setSize( newSize : Dimension ) : void +addChild( elem : Element ) : boolean <<getter>>+getChildren() : List<E->Element> +removeChild( s : Element ) : boolean ... ComponentElement <<constructor>>+ComponentElement( pc : PatternComponent ) <<getter>>+getThreadGroup() : ThreadGroup <<JavaElement>> <<getter>>+getPropertyDescriptors() : IPropertyDescriptor"[]"{JavaAnnotations = @Override} <<getter>>+getIcon() : Image +preStart() : void +start() : void +stop() : void <<getter>>+getTypeName() : String <<getter>>+getAllowedSources() <<getter>>+getComponent() : PatternComponent <<getter>>+getRunnable() : ComponentRunnable +toString() : String <<JavaElement>> <<getter>>+getPropertyValue( propertyId : Object ) : Object{JavaAnnotations = @Override} <<JavaElement>> <<setter>>+setPropertyValue( propertyId : Object, value : Object ) : void{JavaAnnotations = @Override} <<setter>>+setRunning( value : boolean ) : void <<getter>>+getRunning() : boolean <<setter>>+setStatus( value : String ) : void <<getter>>+getStatus() : String <<setter>>+setInteractive( value : boolean ) : void <<getter>>+getInteractive() : boolean ... Figura 6-10: Capa de Modelo de ACVF BaseElement: Clase abstracta que contiene la funcionalidad común de todos los elementos del modelo como el soporte de propiedades, de ser observable y la capacidad de ser serializable para poder mantener un estado persistente. Los métodos que provee la clase son: addPropertyChangeListener(), removePropertyChangeListener(): Permiten agregar o quitar un “Observador” de los cambios de valores de las propiedades del elemento, a través de la interface PropertyChangeListener, que contiene un método para informar dicho tipo de eventos. getPropertyValue(), setPropertyValue(), getPropertyDescriptors(), isPropertySet(), resetPropertyValue(): Métodos asociado al manejo de propiedades de los elemen- Diego M.S. Erdödy Framework de Visualización 65 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA tos (obtener el valor de una propiedad determinada, cambiar el valor, obtener la lista de descriptores de propiedades, consultar si la propiedad tiene algún valor y limpiar el valor de una propiedad, respectivamente). Si bien esta clase no define ningún tipo de propiedades, implementa los métodos con valores por defecto para poder ser referenciados por las clases derivadas que componen el modelo. PatternWorkbenchDiagram: Elemento raíz del modelo que actúa de contenedor del resto de los elementos. Los métodos provistos son: addChild(), removeChild(), getChildren(): Métodos de acceso a los elementos hijo del modelo. Permiten agregar, quitar u obtener los hijos, respectivamente. Connection: Representa una conexión dirigida entre dos elementos del modelo, el origen (source) y el destino (target). Posee además una propiedad que representa el tipo de conexión que se visualizará (sólida, punteada, etc.). Los métodos principales son: Connection(): La conexión debe construirse indicando los elementos origen y destino. disconnect(), reconnect(): Permiten indicar a los elementos que están asociados por esta conexión, que dicho vínculo debe eliminarse o volverse a establecer. getSource(), getTarget(): Métodos de acceso a los elementos origen y destino. Element: Clase abstracta que incorpora el comportamiento de un elemento del modelo genérico, visible gráficamente. Cuenta con referencias a las conexiones de las que participa y métodos para acceder a dichas conexiones. Además posee propiedades y métodos de acceso para los siguientes atributos: height, width: Dimensiones del elemento, en alto y ancho. xpos, ypos: Posición del elemento dentro del diagrama (coordenadas X e Y). color: Color distintivo del elemento (para distinguir los elementos del mismo tipo de una manera sencilla y visual). instanceName: Nombre del elemento que lo identificará en el diagrama. ComponentElement: Elemento del modelo principal de ACVF que representa un componente. Cuenta con las siguientes propiedades: running: Indica que el componente se encuentra ejecutando la simulación. status: Texto que muestra el estado en el componente. interactive: Modo en que se ejecutará la simulación. Además, posee los siguientes métodos: 66 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA getComponent(): Devuelve la instancia de la clase PatternComponent asociada a este componente. Dentro de dicha instancia se almacena la información del componente, provista por el usuario del framework. getRunnable(): Devuelve el objeto de simulación que se ejecutará cuando se inicie la simulación del diagrama. getTypeName(): Indentificador único del componente. getAllowedSources(): Devuelve una lista con los tipos de componentes que se pueden conectar con este componente. getIcon(): Obtiene el icono a utilizar en las representaciones visuales del componente. start(): Inicia el flujo de simulación para este componente. preStart(): Preinicialización del componente, inmediatamente previo al inicio de la simulación. La tarea típica de preinicialización de un componente es crear la instancia del objeto de simulación. stop(): Detiene la ejecución de la simulación para este componente. Más adelante se explica en detalle el ciclo de vida de una simulación, resumido en un diagrama de secuencia. Vista La vista es la capa responsable de representar visualmente el estado del modelo. Debe ser actualizada cada vez que el modelo cambie, ya sea por acción de la interacción con el usuario o por cambios internos. La vista no debe almacenar información sobre el modelo y solamente debe poseer el comportamiento asociado a la visualización. Para la implementación del framework, la vista se compone de dos elementos principales: los componentes y las conexiones entre ellos. Los componentes se representan con figuras similares a las de una clase en UML y se dividen en secciones denominadas compartimientos. Los compartimientos disponibles para un componente son: Encabezado del componente (Color, Tipo y nombre de instancia) Figura personalizada del componente Estado del componente componente Propiedades dinámicas del componente componente Acciones del componente Figura 6-11: Compartimientos del componente Diego M.S. Erdödy Framework de Visualización 67 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA El siguiente es un diagrama de estructura que representa el diseño de la vista utilizado y una descripción de su composición: IFigure (org.eclipse.draw2d) Figure (org.eclipse.draw2d) ComponentFigure CompartmentFigure ~layout : ToolbarLayout = new ToolbarLayout() <<constructor>>+CompartmentFigure() <<setter>>+setAlign( al : int ) : void <<setter>>+setMsgEnabled( value : boolean ) : void <<setter>>+setStatus( st : String ) : void +addProperty( msg : Method ) : void <<setter>>+setPropertyValue( prop : String, newValue : Object, oldValue : Object ) : void <<JavaElement>> <<setter>>+setBackgroundColor( bg : Color ) : void{JavaAnnotations = @Override} <<constructor>>+ComponentFigure( name : Label, custFig_ : IFigure ) +addMessage( msg : Method, actList : ActionListener ) : void ... Figura 6-12: Capa de Vista de ACVF IFigure, Figure: Interface que define el comportamiento de una figura en Draw2D y su implementación abstracta. CompartmentFigure: Figura que representa un compartimiento dentro del rectángulo principal. Es muy similar a la representación en UML de una clase, en la cual un compartimiento agrupa elementos de naturaleza semejante, como ser atributos o acciones del componente. ComponentFigure: Figura que representa un componente. Posee los siguientes métodos: ComponentFigure(): Constructor de la figura. Debe recibir como parámetros, el nombre del componente junto con la figura personalizada que lo representa. setMsgEnabled(): Indica el estado en que deben estar los botones que representan las acciones. El estado puede ser “habilitado” o “deshabilitado”. setStatus(): Especifica un mensaje de estado pasado como parámetro de texto, que se debe mostrar dentro del componente. addProperty(): Agrega una propiedad al compartimiento correspondiente. setPropertyValue(): Especifica el valor que debe tener una propiedad previamente adicionada. addMessage(): Agrega una acción al componente. Los parámetros que requiere son el método a ejecutar y el observador que se encargará de ejecutar dicho método cuando se dispare la acción. setBackgroundColor(): Establece el color de fondo del componente. Controlador 68 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La capa del controlador de ACVF consta básicamente de un EditPart por cada elemento del modelo. Para poder actuar en caso de que alguna de las propiedades de los elementos del modelo cambie, los EditParts implementan la interface PropertyChangeListener para poder registrarse como observadores de dichas propiedades. La interface PropertyChangeListener forma parte de la biblioteca integrada de Java y posee un método “propertyChange” que es llamado por el objeto observable cada vez que se produce un cambio sobre los atributos observados, para poder notificar a los observadores. El diagrama de estructura y el detalle de los elementos es el siguiente: AbstractGraphicalEditPart ElementEditPart AbstractConnectionEditPart DiagramEditPart +activate() : void #createEditPolicies() : void #createFigure() : IFigure +deactivate() : void <<getter>>-getCastedModel() : PatternWorkbenchDiagram <<getter>>#getModelChildren() : List +propertyChange( evt : PropertyChangeEvent ) : void ConnectionEditPart +activate() : void #createEditPolicies() : void #createFigure() : IFigure +deactivate() : void <<getter>>-getCastedModel() : Connection +propertyChange( event : PropertyChangeEvent ) : void <<getter>>#getConnectionAnchor() : ConnectionAnchor +activate() : void #createEditPolicies() : void #createFigure() : IFigure -createFigureForModel() : IFigure +deactivate() : void <<getter>>-getCastedModel() : ComponentElement <<getter>>#getModelSourceConnections() : List <<getter>>#getModelTargetConnections() : List <<getter>>+getSourceConnectionAnchor( connection : ConnectionEditPart ) : ConnectionAnchor <<getter>>+getSourceConnectionAnchor( request : Request ) : ConnectionAnchor <<getter>>+getTargetConnectionAnchor( connection : ConnectionEditPart ) : ConnectionAnchor <<getter>>+getTargetConnectionAnchor( request : Request ) : ConnectionAnchor +propertyChange( evt : PropertyChangeEvent ) : void -refreshProperty( prop : String, newValue : Object, oldValue : Object ) : void <<getter>>-getCastedFigure() : ComponentFigure #refreshRunning() : void #refreshStatus() : void #refreshVisuals() : void <<getter>>+getColor( rgb : RGB ) : Color +actionPerformed( event : ActionEvent ) : void PropertyChangeListener +propertyChange( event : PropertyChangeEvent ) : void Figura 6-13: Capa de Controlador de ACVF AbstractGraphicalEditPart, AbstractConnectionEditPart: Clases abstractas que implementan la funcionalidad común de un EditPart gráfico en general y de conexión en particular, respectivamente. Los métodos principales que provee son: activate(): Indica que el controlador ha sido activado y permite llevar a cabo tareas de inicialización como la registración en calidad de observador al elemento del modelo correspondiente. deactivate(): Marca la finalización del ciclo de vida del controlador, para poder realizar tareas de limpieza. createEditPolicies(): Se crean las políticas de edición, que son básicamente las encargadas de convertir los pedidos del usuario (requests) en comandos entendibles por el modelo. createFigure(): Método destinado a la construcción de la figura que representará gráficamente al componente. Diego M.S. Erdödy Framework de Visualización 69 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA DiagramEditPart: Controlador que representa al diagrama en su totalidad. Juega el papel de nodo raíz en la jerarquía de controladores y brinda la política de edición que permite resolver los distintos tipos de elementos del diagrama. Agrega los siguientes métodos, aparte de la personalización de los provistos por las clases base: getModelChildren(): Permite acceder a los elementos del modelo del primer nivel, por debajo del nodo raíz. getCastedModel(): Obtiene la referencia al elemento raíz del modelo transformado al tipo correcto. ElementEditPart: Controlador principal del framework, encargado de la mediación entre la capa de vista y modelo de cada componente del diagrama. Los métodos adicionales provistos son: getModelSourceConnections(), getModelTargetConnections(): Métodos para acceder a la lista de conexiones entrantes o salientes del componente, respectivamente. getSourceConnectionAnchor() y derivados: Especifica el tipo de terminación que tendrá la conexión. propertyChange(), refresh*(): Método que indica que una propiedad ha cambiado en el componente asociado. Para actualizar la capa visual de los tipos de propiedades conocidos se invoca al método refresh correspondiente. actionPerformed(): Ejecuta la acción especificada sobre el componente. ConnectionEditPart: Controlador para las conexiones entre componentes. Componentes La capa de componentes brinda la funcionalidad común a la administración de los componentes. Algunos ejemplos de las dicha funcionalidad son: el descubrimiento de propiedades dinámicas y acciones, vinculación automática de las propiedades, manejo del ciclo de vida de la simulación y ayuda para la representación gráfica. También establece una interface con la información que necesita de cada componente adicional que se desee agregar al framework. El diagrama de estructura y la descripción de los elementos principales es el siguiente: 70 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA PatternComponent <<getter>>+getType() : String <<getter>>+getRunnab leClass() : Class<T->ComponentRunnab le> +createRunnab le() : ComponentRunnab le +postCreate( runnab le : ComponentRunnab le ) : void <<getter>>+getLinkSources() <<getter>>+getLinkDestinations() <<getter>>+getActions() : List<E->Method> <<getter>>+getPropertyDescriptors() : IPropertyDescriptor"[]" <<getter>>+getPropertyValue( propertyId : Ob ject ) : Ob ject <<setter>>+setPropertyValue( propertyId : Ob ject, value : Ob ject ) : void <<getter>>+getFigure() : IFigure <<getter>>+getIconSmall() : ImageDescriptor <<getter>>+getIconLarge() : ImageDescriptor <<setter>>+setElement( element : ComponentElement ) : void <<introduction>> ComponentRunnable BaseType -display : Display -element : ComponentElement +getDisplay() : Display +setDisplay( display : Display ) +getElement() : ComponentElement +setElement( element : ComponentElement ) +writeAsync( text : String ) AbstractComponent ComponentRunnable <<getter>>+getLinkSources() <<getter>>+getLinkDestinations() <<setter>>+setElement( element_ : ComponentElement ) : void <<getter>>+getElement() : ComponentElement <<getter>>+getFigure() : IFigure <<getter>>#getTargets( type : String ) : Set<E->ComponentElement> <<getter>>#getSources( type : String ) : Set<E->ComponentElement> +postCreate( runnable : ComponentRunnable ) : void +propertyChange( arg0 : PropertyChangeEvent ) : void +createRunnable() : ComponentRunnable <<getter>>+getActions() : List<E->Method> Component1 Component2 ... Component N +stopComponent() : void <<setter>>+setDisplay( display : Display ) : void <<getter>>+getDisplay() : Display <<setter>>+setElement( element : ComponentElement ) : void <<getter>>+getElement() : ComponentElement <<aspect>> ComponentRunnableImpl ... <<introduction>> ComponentRunnable Component1 UI Component2 UI ComponentN UI Figura 6-14: Capa de Componentes de ACVF PatternComponent: Es la interface que define las operaciones que debe implementar un componente para cumplir con los requerimientos del framework. Los métodos necesarios son: getType(): Debe proveer la identificación única del tipo de componente, en forma de texto. getRunnableClass(): Especifica la clase que hará de objeto ejecutable durante la simulación y se encargará de representar visualmente el estado a lo largo del tiempo. Dicha clase debe implementar la interface ComponentRunnable. createRunnable(): Método encargado de crear el objeto de ejecución del componente. postCreate(): Método que se ejecuta una vez que ha sido creado el objeto ejecutable e inmediatamente posterior a iniciar la simulación. getLinkSources(): Provee un grupo de tipos de componentes que pueden conectarse como origen a este componente. getLinkDestinations(): Provee un grupo de tipos de componentes que pueden conectarse como destino a este componente. getActions(): Especifica el conjunto de acciones que se pueden ejecutar sobre este componente. getPropertyDescriptors(): A través de este método se especifican las propiedades que publicará el componente. getPropertyValue(): Método para obtener el valor de una propiedad publicada. setPropertyValue(): Método para establecer el valor de una propiedad publicada. getFigure(): Especifica la figura que representará visualmente al componente. Diego M.S. Erdödy Framework de Visualización 71 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA getIconSmall(): Representación iconográfica del componente, en su versión pequeña. Se utiliza para mostrar en las listas como, por ejemplo, la paleta o el sumario. getIconLarge():Representación iconográfica del componente, en su versión aumentada. Se utiliza para mostrar en el diagrama. setElement(): Le asigna el elemento de la capa de modelo, para poder obtener información como, por ejemplo, los componentes conectados. AbstractComponent: Implementa las operaciones básicas de un componente haciendo mucho más sencilla la tarea de crear un nuevo componente. getLinkSources(): Devuelve un grupo vacío. getLinkDestinations(): Devuelve un grupo vacío. setElement(): Asigna el elemento de modelo a un atributo protegido para poder ser usado por las clases derivadas. getElement(): Acceso al elemento de modelo. getFigure(): Devuelve la figura por defecto. getTargets(),getSources(): Devuelve una lista de los elementos del modelo que actúan en una conexión como destino u origen, respectivamente,. postCreate(): Por defecto, especifica el atributo “element” en el objeto de simulación. También asigna el “display” activo, para poder utilizarlo dentro de la simulación al hacer llamadas asíncronas, ya que se deben correr en un hilo de ejecución distinto al principal. propertyChange(): Implementación vacía del método, en el caso que no se requieran propiedades. createRunnable():Instancia un nuevo objeto de la clase devuelta por getRunnableClass() llamando al constructor por defecto (sin argumentos). getActions(): Por defecto, devuelve la lista de métodos de la clase definida por getRunnableClass() que están modificados con la anotación “Action”. ComponentRunnable: Es la interface que especifica la interacción con los objetos que implementarán el comportamiento dinámico del componente, utilizado en el momento de la simulación. stopComponent(): Método para poder detener el objeto de simulación. setElement(), getElement(): Permite especificar u obtener el objeto del modelo asociado al componente. setDisplay(),getDisplay(): Permite especificar u obtener el “display” a utilizar para realizar las operaciones que impliquen cambios visuales. ComponentRunnableImpl: Es el aspecto que implementa la interface ComponentRunnable. De esta forma se utiliza el mecanismo que provee AOP para poder asociar comportamiento directamente a una interface. Es un mecanismo similar a la herencia múltiple pero implementado de forma más natural. 72 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Anotaciones Utilizadas El ACVF utiliza anotaciones de Java 1.5 para identificar de forma clara, ciertos atributos de los componentes. Las anotaciones introducidas por ACVF son: Action: Se aplica a los métodos de los objetos de simulación (los que implementan ComponentRunnable) que se deseen publicar como acciones del componente. El método getActions() de la clase AbstractComponent, obtiene los métodos que estén marcados con esta anotación y los devuelve para ser incluidos en el compartimiento correspondiente de la representación gráfica. DynamicProperty: Se aplica a los métodos del tipo “setter” de los objetos de simulación cuyo atributo se desee publicar dinámicamente. Esto implica que el atributo aparecerá en el compartimiento de propiedades dinámicas de la vista y su valor podrá ser seguido visualmente en tiempo de simulación. Property: Diseñado para ser aplicado a los atributos del componente que se deseen mostrar en el panel de propiedades. Dichos atributos componen la configuración del componente. Posee los siguientes argumentos: o name: identificador único interno de la propiedad. o description: descripción a mostrar en el panel de propiedades o caption: nombre de la propiedad o category: nombre de la categoría del panel de propiedades en donde se ubicará la propiedad. Interacción de las distintas capas en la ejecución de un Componente En el siguiente diagrama se muestra la interacción entre los distintos elementos de cada capa MVC, para el movimiento de un componente dentro de un diagrama. MODELO GEF : ElementXYLayoutEditPolicy CONTROLADOR GEF : Element VISTA : ElementEditPart : ComponentFigure 1: createChangeConstraintCommand(child=, constraint=) 2: (Element, ) : ElementSetConstraintCommand 3: execute() 4: redo() 5: setSize(newSize=) 6: firePropertyChange(property=, oldValue=, newValue=) 7: propertyChange(evt=) 8: refreshVisuals() 9: setBounds(rectangle=) 10: setLocation(newLocation=) Figura 6-15: Diagrama de secuencia para el movimiento de un componente Diego M.S. Erdödy Framework de Visualización 73 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Los pasos se pueden dividir en dos grupos, por un lado la creación del comando, que se realiza al iniciar el movimiento del componente dentro del diagrama (pasos 1 y 2), y por otro lado la ejecución del comando, realizada al “soltar” el componente (pasos del 3 al 10). A su vez, los elementos que intervienen en la interacción se dividen en tres grupos correspondientes a las distintas capas MVC, como lo indica el diagrama. 1) El framework GEF, invoca a la política de edición (EditPolicy) provista por ACVF, requiriéndole la creación de un comando del tipo “ChangeConstraint”, correspondiente al pedido del usuario de cambiar la posición del componente. 2) El EditPolicy crea el comando correspondiente, pasándole como parámetro una referencia del elemento en cuestión. 3) Un vez establecido el nuevo lugar del componente (cuando el usuario lo “suelta” dentro del diagrama, el framework GEF envía un mensaje al comando para que se ejecute. 4) Internamente, el comando invoca al método “redo” que contiene la lógica de ejecución del comando. 5) El comando le informa el nuevo tamaño al elemento, a través del mensaje setSize(). 6) El cambio en el valor de la propiedad del elemento, dispara la notificación de todos los observadores registrados al elemento. 7) El EditPart asociado al elemento, al ser uno de los observadores, es notificado del nuevo valor a través del mensaje propertyChange(). 8) El EditPart, al enterarse del cambio de valor, invoca al método que informará a la capa visual de dicho cambio, llamado refreshVisuals(). 9) El controlador informa el cambio a la figura que representa el componente, enviando el mensaje setBounds() 10) El proceso se repite para el atributo “location”. A continuación se describe la interacción que se produce al momento de iniciar una simulación dentro del diagrama ACVF: 74 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA user : User action : RunAction editor : PWEditor model : ComponentElement Para todos los componentes del diagrama component : PatternComponent 1: run() 2: startCycle() loop [] 3: preStart() 4: createRunnable() 5: getRunnableClass() 6: runnable : ComponentRunnable Un thread por cada componente 7: start() 8: postCreate(runnable=) par 9: runnableThread : Thread [] 10: start() 11: run() Figura 6-16: Diagrama de secuencia para el inicio de la simulación 1) El usuario hace click en el botón de “Run” de la barra de herramientas para iniciar la simulación. 2) La acción asociada al botón “Run” obtiene una referencia al editor y le envía un pedido de inicio de simulación. 3) El editor, itera sobre todos los elementos hijos del modelo del tipo ComponentElement y les envía un mensaje de preinicialización. 4) En este paso, el elemento del modelo invoca al componente para crear el objeto de simulación. 5) Internamente, la instancia del componente, obtiene la clase que va a representar al objeto de simulación (patrón Template Method). 6) El objeto de simulación es instanciado, utilizando el mecanismo de reflexión. 7) Nuevamente el editor itera sobre todos los elementos hijos del modelo del tipo ComponentElement y les envía un mensaje de inicio de simulación. 8) El componente envía un mensaje de postCreate() al objeto de simulación para que pueda inicializar sus valores, por ejemplo, a partir de componentes conectados. 9) 10) 11) Por último, se crea un nuevo hilo de ejecución para correr la simulación para ese componente y se inicia. De esta forma cada componente del diagrama, se corre simultáneamente, en un hilo de ejecución distinto. Internamente, el hilo llama al método run() de la interface Runnable que implementa el objeto de simulación. Diego M.S. Erdödy Framework de Visualización 75 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 6.5. Otros Frameworks de Visualización JHotDraw [JHD 06] es un framework de visualización implementado en Java y orientado a diagramas técnicos y estructurados. Fue desarrollado originalmente por Erich Gamma y Thomas Eggenschwiler como un caso de estudio para mostrar la aplicación de los patrones de diseño. Si bien la versión original estaba basada en la biblioteca de gráficos AWT, las nuevas versiones incorporaron la biblioteca más moderna Swing. JHotDraw está basado en dos proyectos, HotDraw, un framework de visualización implementado en SmallTalk, y E++, un framework de aplicaciones hecho en C++. El framework soporta el uso de una barra de herramientas, diferentes vistas, figuras personalizadas y manejo de persistencia de diagramas (guardado, carga e impresión). La arquitectura del framework, al igual que GEF y ACVF está basada en el patrón MVC brindado especial soporte en la capa de control. La estructura básica del framework es la siguiente: Figura 6-17: Diagrama de estructura básico del framework de visualización JHotDraw En JHotDraw las aplicaciones están compuestas por un diagrama (Drawing), una vista del diagrama (DrawingView), un conjunto de figuras (Figure) y al menos una herramienta (Tool). Las herramientas permiten ejecutar acciones sobre el diagrama, como por ejemplo, crear una figura. Por último, los Handles son porciones de la figura que permiten interactuar con la misma, por ejemplo para agregar una conexión. Si bien técnicamente es un producto bien construido y las características son similares a las provistas por GEF, las desventajas que se le encontraron, por las cuales no fue utilizado para la implementación de ACVF son: JHotDraw está diseñado para trabajar como una aplicación independiente. No posee integración con un entorno de desarrollo como GEF y Eclipse, en don- 76 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA de se puede optar por desarrollar un plug-in integrable o transformar dicho plug-in en una aplicación cliente, con un pequeño cambio en configuración. La documentación existente no es muy detallada y se basa en la autodocumentación de las clases dentro de los patrones de diseño utilizados. El estado actual y futuro del proyecto es incierto. Se encontraron distintos emprendimientos cuyo objetivo es extender y continuar con el framework, pero no existe un plan de soporte a largo plazo. 6.6. Conclusión Para verificar que ACVF es un framework, a continuación se analiza si cumple con las propiedades que lo definen como tal: Orientado a ayudar en un dominio en particular: ACVF está diseñado para proveer una infraestructura que soporte la visualización de componentes tanto de una forma estática como dinámica, posibilitando el mejor entendimiento de las interacciones intervinientes. De esta forma cumple con la propiedad por estar específicamente construido con tal propósito. Representa una solución parcialmente completa para un dominio específico que puede ser personalizada: El dominio de ACVF es el descripto en el punto anterior. ACVF provee comportamiento de base para la implementación de representaciones visuales de componentes, compuesto por clases abstractas y por implementación de elementos comunes como la vista de “outline”. La personalización consiste de los distintos componentes “acoplables” (pluggable) y de la extensión del comportamiento de base. Permite la reutilización de diseño a partir de técnicas básicas de POO y patrones de diseño: El diseño de ACVF provee una separación en capas bien definidas y desacopladas, la interacción definida entre ellas y la definición de la interface con los servicios que se espera de cada componente. Este es el mayor aporte que este framework provee a los usuarios. En él se utiliza una gran variedad de patrones, entre los que se encuentran: o Abstract Factory: Este patrón se utiliza en la creación de los controladores, a partir de los elementos del modelo. o Template Method: Utilizado, por ejemplo, por la clase AbstractComponent, para poder crear el objeto de simulación a partir de la clase provista por el método getRunnableClass(). Cada componente debe sobrescribir el método para especificar la clase adecuada. o Mediador: La capa del controlador está basada en este concepto, en donde coordina y desacopla la interacción entre las capas del modelo y de la vista. o Composite: El modelo está implementado bajo este patrón, ya que posee una naturaleza inherentemente jerárquica. o Command: Cada pedido del usuario es transformado en un comando que luego es aplicado al modelo. De esta forma se puede almacenar Diego M.S. Erdödy Framework de Visualización 77 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA un historial de comandos ejecutados e implementar un mecanismo de “deshacer” y “rehacer” de manera simple. o Obervador: Implementado, por ejemplo, por el controlador para actuar frente a cambios en el modelo. Permite la reutilización de funcionalidad: La funcionalidad o comportamiento que provee el framework se reparte entre las clases abstractas, que deberán implementar los usuarios, y los elementos comunes que resuelven problemas específicos del dominio y no necesitan modificaciones adicionales. Un ejemplo del primer caso es la clase AbstractComponent, mientras que la clase de modelo Connection se encuentra en el segundo grupo. Permite la reutilización de análisis del dominio: Tal vez no es muy evidente en este caso, pero las posibilidades que brinda el framework, como armar un diagrama, editar componentes, conectarlos entre si y visualizar su interacción en una simulación, son todos casos de uso que se tuvieron que definir antes de desarrollar el framework, y es un valor agregado que éste le provee al usuario. 78 Framework de Visualización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7. Patrones de concurrencia y sincronización 7.1. 7.1.1. Rendezvous Resumen El patrón de diseño “Rendezvous” tiene como objetivo modelar las precondiciones para la sincronización o encuentro de distintos hilos de ejecución. Es un patrón general y fácil de aplicar para asegurar que las condiciones requeridas (las cuales pueden ser complejas) sean cumplidas en tiempo de ejecución. El modelo de comportamiento consiste en que a medida de que cada hilo está listo para “encontrarse” con el resto, éste se registra ante el Rendezvous y es bloqueado. Una vez que las restricciones de concurrencia se cumplen el Rendezvous libera a los participantes registrados, por ejemplo al llegar a un número predefinido de participantes registrados. 7.1.2. Otras denominaciones Punto de encuentro. 7.1.3. Problema El problema que soluciona el patrón de diseño es la abstracción de la aplicación de precondiciones en la ejecución de una acción dada que se encuentra compartida entre más de un componente. 7.1.4. Solución La solución consiste en delegar la sincronización en un objeto externo, llamado rendezvous, que administre en forma centralizada la información de cada una de las partes involucradas. Cada hilo de ejecución deberá registrarse con el rendezvous y requerir permiso para proseguir en el momento en que la sincronización sea necesaria. El rendezvous, con la ayuda de una política de sincronización encapsulada en otro componente, será el encargado de informarle a cada hilo de ejecución, cuándo es correcto proceder. 7.1.5. Caso de estudio Como ejemplo ilustrativo del patrón se utilizó una línea de montaje en donde el procesamiento de cada etapa se encuentra a cargo de un brazo robótico. En dicho entorno existe la limitación de que cada etapa no puede ser iniciada hasta no tener la confirmación que el resto de las etapas han terminado. En este caso, el punto de encuentro está indicado por la finalización de las tareas de cada etapa, en cada ciclo de procesamiento. De esta forma todos los brazos empezarán su tarea simultáneamente, sin importar cuánto dure cada tarea. Diego M.S. Erdödy Patrones de concurrencia y sincronización 79 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7.1.6. Estructura La estructura clásica del patrón es la siguiente: Synch Policy 1 1 Rendezvous Client Thread +reset() +register(in callback : Callback) +release() 1 1..* +notify() 1 * Callback Figura 7-1: Diagrama de Estructura del Patrón “Rendezvous” Este diagrama, sugerido por Powell Douglass [Dou 02], muestra a una clase central llamada Rendezvous que representa el punto de encuentro de los objetos que necesiten sincronizarse. Es el Rendezvous el que verifica que en cada registración de un participante se cumpla o no la política de sincronización y en caso afirmativo libera a dichos participantes. Los participantes están representados por la clase ClientThread y el mecanismo para liberarlos por la interface Callback. A continuación se detallan las funciones de cada uno de los participantes de la estructura del patrón. Client Thread: Representa los hilos de ejecución, por lo menos dos, que necesitan un mecanismo de sincronización. Al llegar al punto de sincronización se registran con el Rendezvous pasando como argumento su referencia. Este mecanismo recibe el nombre de Callback. Al cumplirse con las precondiciones, los hilos son liberados para poder proseguir con su ejecución. Rendezvous: Administra la sincronización entre los hilos participantes. Posee un método de registración el cual es invocado por los hilos de ejecución para indicar que han llegado al punto de sincronización. Synch Policy: Es una abstracción del conjunto de precondiciones que necesita verificar el Rendezvous para poder liberar a los integrantes. La política más sencilla es simplemente contar los integrantes registrados y liberarlos al llegar al número preestablecido de miembros. Callback: Interface utilizada para la reactivación de los hilos de ejecución. 7.1.7. Estrategia de implementación Orientada a Aspectos 80 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La implementación realizada, posee dos modalidades: la versión local o simple, en donde todos los brazos robóticos se encuentran en la misma máquina; y por otro lado la versión distribuida, que puede sincronizar brazos robóticos en distintas máquinas. En el trabajo realizado por Jiménez-Peris et al. [Jim 98] se analiza una implementación distribuida y orientada a objetos del patrón de diseño rendezvous, en ADA95. En ese trabajo se optó por estudiar las ventajas de implementar el servidor con un hilo de ejecución por cliente (multithreaded). En el presente trabajó se utilizó un solo hilo para implementar el servidor ya que el tiempo de procesamiento de cada petición de un cliente no es significativo y el objetivo no es realizar una implementación de alta performance. NÚCLEO El núcleo del patrón de diseño se encuentra en el aspecto abstracto RendezvousProtocol junto con las interfaces Rendezvous y RendezvousMember. El siguiente es el diagrama de estructura de dichos elementos: Rendezvous +register( ob j : Ob ject ) : void <<setter>>+setParticipants( part : int ) : void +reset() : void +release() : void +close() : void <<aspect>> RendezvousProtocol -rvs : Map<String, Rendezvous> <<pointcut>>+synchronizingCall( memb er : RendezvousMemb er ) <<getter>>+getRendezvous( groupName : String ) : Rendezvous <<setter>>+setRendezvous( groupName : String, rendezvous : Rendezvous ) : void <<advice>> <<base>>~before( member : RendezvousMember ){base = synchronizingCall(member)} <<introduction>> RendezvousMember BaseType <<introduction>> RendezvousMember -group : String <<getter>>+getGroup() : String <<setter>>+setGroup( groupName : String ) : void RendezvousMember <<crosscut>> <<getter>>+getGroup() : String Figura 7-2: Núcleo de la estructura Orientada a Aspectos del Patrón “Rendezvous” La idea principal de la implementación consta en desacoplar la lógica de sincronización del objeto a sincronizar. Esto se logra por medio del punto de corte syncronizingCall() definido en el aspecto abstracto, que representa el conjunto de sitios donde la sincronización es requerida. De esta forma, el objeto a sincronizar, en nuestro caso el brazo robótico, no necesita contener lógica asociada a la sincronización con el resto de los brazos, ni tampoco saber de su existencia. El Rendezvous es el objeto encargado de aplicar la política de sincronización a los participantes que se registran en el mediante el método register(). Los participantes deben implementar la interface RendezvousMember para poder identificar a qué grupo de sincronización pertenecen. Un grupo de sincronización es una entidad lógica Diego M.S. Erdödy Patrones de concurrencia y sincronización 81 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA que permite al aspecto poder correlacionar un participante con un Rendezvous específico y de esta forma poder servir a más de un Rendezvous a la vez. Como se ve en el diagrama, el aspecto abstracto implementa el método que la interface requiere, llamado getGroup() y provee el método setGroup() para poder especificar el grupo de sincronización sobre un participante. A continuación se detalla cada elemento: Rendezvous: Interface que representa un objeto de sincronización en donde distintos componentes pueden registrarse y expresar su necesidad de suspender su ejecución hasta que le sea dicho que es correcto proseguir. Esto se logra a través del método register() cuyo argumento es el componente registrado. El método reset() brinda la posibilidad de volver al rendezvous a su estado original mientras que release() es usado cuando se necesite liberar a los participantes registrados. Por último, el método close() realiza todas las operaciones de limpieza necesarias para finalizar el ciclo de vida del rendezvous. RendezvousMember: Interface que identifica a un miembro capaz de registrarse ante un rendezvous. Los miembros setGroup() y getGroup() son los que permiten obtener y especificar a qué grupo lógico de encuentro pertenece este miembro. Este nombre de grupo permite tener más de un conjunto de miembros capaces de registrarse simultáneamente. La implementación de esta interface, que para aspectos se denomina “introducción”, se efectúa en el aspecto abstracto que implementa el protocolo. La realización, en este caso por parte de la clase RoboArm, se lleva a cabo en el aspecto concreto permitiendo así indicar qué clase podrá ser miembro del Rendezvous sin que ésta tenga que ser alterada con código específico del patrón de diseño. RendezvousProtocol: Aspecto abstracto encargado de definir las interacciones comunes del patrón de diseño. En primer lugar se define el punto de corte abstracto llamado synchronizingCall() que representa el punto de sincronización del patrón. De esta forma, en vez de agregar código adicional en el miembro para registrarse en el Rendezvous y agregar un nivel de dependencia entre ambos objetos, la llamada queda a cargo del aspecto permitiendo la abstracción del mecanismo. Por otro lado se definen los métodos getRendezvous() y setRendezvous(), encargados de especificar qué Rendezvous manejará la sincronización del grupo cuyo nombre es pasado como argumento. El advice que se agrega sobre el punto de corte es el encargado de obtener el Rendezvous especificado para el miembro que se está procesando. Una vez obtenido el Rendezvous llama al método register(). Por último, se implementa la interface RendezvousMember que posee métodos para administrar a qué grupo pertenece el miembro. APLICACIÓN del NÚCLEO sobre EL CASO DE ESTUDIO Si bien se logró encapsular la lógica general del patrón en el aspecto abstracto previamente explicado, es necesario algún elemento que vincule esa lógica con el problema específico a atacar, en este caso la sincronización de brazos robóticos. El aspecto concreto RendezvousHandler se encarga de dicha tarea, es decir que aplica la lógica del rendezvousProtocol a objetos del tipo RoboArm, clase que representa el brazo robótico. Las funciones del aspecto concreto son: Transformar la clase RoboArm en un participante de un Rendezvous haciendo que dicha clase implemente la interface RendezvousMember. 82 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Indicar que el punto de corte syncronizingCall() incluya las llamadas al método executeAction() del brazo robótico. En el siguiente diagrama de estructura se representan dichos elementos con las asociaciones correspondientes: RoboArm <<aspect>> RendezvousProtocol -id : String ~log : Logger = Logger.getLogger(RoboArm.class) -maxTaskTime : long = 5000 -minTaskTime : long = 1000 -random : Random -stopped : boolean = false -rvs : Map<String, Rendezvous> #calculateTime() : long -createRandom() : Random +executeAction() : void <<getter>>+getMaxTaskTime() : long <<getter>>+getMinTaskTime() : long <<getter>>+getRAId() : String <<constructor>>+RoboArm( id_ : String ) +run() : void <<setter>>+setMaxTaskTime( maxTaskTime_ : long ) : void <<setter>>+setMinTaskTime( minTaskTime_ : long ) : void +stop() : void +toString() : String <<pointcut>>+synchronizingCall( memb er : RendezvousMemb er ) <<getter>>+getRendezvous( groupName : String ) : Rendezvous <<setter>>+setRendezvous( groupName : String, rendezvous : Rendezvous ) : void <<advice>> <<base>>~before( member : RendezvousMember ){base = synchronizingCall(member)} <<introduction>> RendezvousMember <<aspect>> RendezvousHandler <<pointcut>> <<base>>+synchronizingCall( member : RendezvousMember ){base = call (void RoboArm.executeAction()) && target(o)} RendezvousMember <<getter>>+getGroup() : String <<declare parents>> RoboArm implements RendezvousMember Figura 7-3: Estructura de la aplicación del patrón “Rendezvous” RoboArm: Representa el brazo robótico. Se le asigna un identificador (atributo id) y se puede configurar el límite superior e inferior del tiempo de su acción principal (atributos minTaskTime y maxTaskTime). Por defecto estos valores están especificados en 1 seg. y 5 seg., respectivamente. Cada vez que se ejecute, el tiempo real de duración de la tarea será un número aleatorio comprendido entre dichos límites. Posee un método run() para iniciar la simulación y otro stop() para detenerla. RendezvousHandler: Aspecto concreto que agrega los detalles necesarios para aplicar el patrón definido en el aspecto abstracto. Aquí se determina qué clase se utilizará como miembro del Rendezvous y cuál será el punto de sincronización. En nuestro caso es la clase RoboArm y su método execute(). IMPLEMENTACIONES DEL RENDEZVOUS Hasta ahora se ha mostrado solo la interface que debe implementar un Rendezvous. Para este caso de estudio se han analizado y desarrollado dos implementaciones distintas. La primera, llamada SimpleRendezvous, es una versión local que permite la sincronización de hilos de ejecución dentro de una misma máquina. La segunda, llamada RemoteRendezvous, consiste en una versión remota que permite sincronizar hilos que se ejecuten en distintas máquinas. Para el caso remoto, el objeto RemoteRendezvous se deberá conectar con un servidor detallado en la siguiente sección, que coordinará las acciones de las distintas Diego M.S. Erdödy Patrones de concurrencia y sincronización 83 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA instancias de RemoteRendezvous. A diferencia de la versión simple, el Rendezvous remoto implementa la interface Callback, dado que se necesita que sea llamado por el servidor de Rendezvous una vez que la política de sincronización ha sido cumplida y los participantes del Rendezvous pueden ser liberados. Esto se logra a través de una llamada remota utilizando RMI desde el servidor al método finished() de la instancia de RemoteRendezvous. Téngase en cuenta que en el caso del Rendezvous simple este mecanismo no era necesario ya que el Rendezvous directamente bloqueaba el hilo de ejecución al registrarse el participante y luego cuando debía ser liberado, lo despertaba. El siguiente es el diagrama de estructura: RemoteRendezvous Callback -objs : Set<E->Object> = new HashSet<Object>() -maxParticipants : int = 2 ~log : Logger = Logger.getLogger(RemoteRendezvous.class) -server : RendezvousServer +finished() : void Rendezvous +register( ob j : Ob ject ) : void <<setter>>+setParticipants( part : int ) : void +reset() : void +release() : void +close() : void +register( obj : Object ) : void +release() : void +reset() : void <<setter>>+setParticipants( part : int ) : void <<constructor>>+RemoteRendezvous( serverAddress : String ) +finished() : void +close() : void SimpleRendezvous #objs : Set<E->Object> = new HashSet<Object>() -maxParticipants : int = -1 <<constructor>>+SimpleRendezvous( maxParticipants_ : int ) <<constructor>>+SimpleRendezvous() +register( obj : Object ) : void{guarded} +release() : void +reset() : void <<setter>>+setParticipants( part : int ) : void +close() : void -checkParticipants() : void Figura 7-4: Estructura de la distintas implementaciones del Rendezvous Callback: Interface de retro-llamada que declara el método finished(). Cuando en un mensaje se pasa como argumento una referencia del objeto iniciador de la llamada, esta interface permite que el objeto al cual se está llamando pueda informarle al objeto iniciador en una manera asincrónica que el procesamiento asociado a la llamada ha sido finalizado. En este caso el objeto que necesita ser notificado es el RemoteRendezvous y el notificador es el servidor que a través del método finished() le indicará al Rendezvous que sus participantes pueden ser liberados. SimpleRendezvous: Versión sencilla o local del rendezvous. Simplemente bloquea al hilo de ejecución de cada miembro registrado hasta que las precondiciones sean cumplidas, usando una llamada al método wait(). Cuando se cumplen las precondiciones llama al método notifyAll() sobre el monitor liberando así todos los hilos. RemoteRendezvous: Versión remota del rendezvous. En este caso las llamadas se delegan a un servidor de rendezvous via RMI el cual es el encargado de coordinar las acciones. El constructor recibe como argumento la dirección de la máquina donde 84 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA reside el servidor de Rendezvous. Implementa la interface Callback para permitir ser llamado por el servidor al momento de ser liberado, a través del método finished(). IMPLEMENTACIÓN REMOTA Para la implementación remota del Rendezvous, es necesario especificar un servidor que pueda coordinar los Rendezvous sobre distintas máquinas de una forma centralizada. Dicho servidor es accesible via RMI y permite que los RemoteRendezvous actúen de proxy entre los participantes del rendezvous y él. De esta forma al servidor le llega la información las registraciones de participantes en cada una de las máquinas y él puede distribuir la orden de liberación una vez que la política de sincronización ha sido cumplida. El diagrama de estructura es el siguiente: RendezvousServer +register( client : Callb ack ) : void +addClient( client : Callb ack, part : int ) : void +removeClient( client : Callb ack ) : void RendezvousServerImpl ~serialVersionUID : long = 1{readOnly} ~log : Logger = Logger.getLogger(RemoteRendezvous.class) -maxParticipants : int -registered : int -participantsByClient : Map<Publisher, List<Subscriber>><K->Callback, V->Integer> = new HashMap<Callback,Integer>() -name : String <<constructor>>+RendezvousServerImpl( name_ : String ) #register() : void #unregister() : void +register( client : Callback ) : void{guarded} -release() : void +addClient( client : Callback, part : int ) : void +removeClient( client : Callback ) : void Figura 7-5: Estructura de la implementación remota del Rendezvous RendezvousServer: Es la interface que define a un servidor de rendezvous. Esta se aplica en los casos en que los miembros pueden encontrarse en máquinas distintas y por lo tanto es necesario coordinar hilos de ejecución remotos. Al igual que un rendezvous tiene un método register() para indicar que un miembro ha llegado al punto de sincronización. Los otros dos métodos, addClient() y removeClient(), agregan o quitan clientes activos, es decir instancias de RemoteRendezvous, a este servidor. Para poder agregar un cliente, es necesario indicarle al servidor cuántos participantes tiene bajo su control para que pueda calcular el número total de participantes y de esta forma saber en qué momento están todos registrados. RendezvousServerImpl: Implementación del servidor de rendezvous utilizando RMI. El diagrama de estructura completo es el siguiente: Diego M.S. Erdödy Patrones de concurrencia y sincronización 85 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA RendezvousServer +register( client : Callb ack ) : void +addClient( client : Callb ack, part : int ) : void +removeClient( client : Callb ack ) : void RemoteRendezvous Callback +finished() : void RendezvousServerImpl ~serialVersionUID : long = 1{readOnly} ~log : Logger = Logger.getLogger(RemoteRendezvous.class) -maxParticipants : int -registered : int -participantsByClient : Map<Publisher, List<Subscriber>><K->Callback, V->Integer> = new HashMap<Callback,Integer>() -name : String <<constructor>>+RendezvousServerImpl( name_ : String ) #register() : void #unregister() : void +register( client : Callback ) : void{guarded} -release() : void +addClient( client : Callback, part : int ) : void +removeClient( client : Callback ) : void Rendezvous +register( ob j : Ob ject ) : void <<setter>>+setParticipants( part : int ) : void +reset() : void +release() : void +close() : void -objs : Set<E->Object> = new HashSet<Object>() -maxParticipants : int = 2 ~log : Logger = Logger.getLogger(RemoteRendezvous.class) -server : RendezvousServer +register( obj : Object ) : void +release() : void +reset() : void <<setter>>+setParticipants( part : int ) : void <<constructor>>+RemoteRendezvous( serverAddress : String ) +finished() : void +close() : void SimpleRendezvous #objs : Set<E->Object> = new HashSet<Object>() -maxParticipants : int = -1 RoboArm <<aspect>> RendezvousProtocol -id : String ~log : Logger = Logger.getLogger(RoboArm.class) -maxTaskTime : long = 5000 -minTaskTime : long = 1000 -random : Random -stopped : boolean = false -rvs : Map<String, Rendezvous> #calculateTime() : long -createRandom() : Random +executeAction() : void <<getter>>+getMaxTaskTime() : long <<getter>>+getMinTaskTime() : long <<getter>>+getRAId() : String <<constructor>>+RoboArm( id_ : String ) +run() : void <<setter>>+setMaxTaskTime( maxTaskTime_ : long ) : void <<setter>>+setMinTaskTime( minTaskTime_ : long ) : void +stop() : void +toString() : String <<pointcut>>+synchronizingCall( memb er : RendezvousMemb er ) <<getter>>+getRendezvous( groupName : String ) : Rendezvous <<setter>>+setRendezvous( groupName : String, rendezvous : Rendezvous ) : void <<advice>> <<base>>~before( member : RendezvousMember ){base = synchronizingCall(member)} <<constructor>>+SimpleRendezvous( maxParticipants_ : int ) <<constructor>>+SimpleRendezvous() +register( obj : Object ) : void{guarded} +release() : void +reset() : void <<setter>>+setParticipants( part : int ) : void +close() : void -checkParticipants() : void <<introduction>> RendezvousMember <<introduction>> RendezvousMember BaseType -group : String <<getter>>+getGroup() : String <<setter>>+setGroup( groupName : String ) : void <<aspect>> RendezvousHandler <<pointcut>> <<base>>+synchronizingCall( member : RendezvousMember ){base = call (void RoboArm.executeAction()) && target(o)} RendezvousMember <<getter>>+getGroup() : String <<declare parents>> RoboArm implements RendezvousMember <<crosscut>> Figura 7-6: Estructura completa para el Rendezvous 7.1.8. Análisis Como se vio en la descripción de la implementación, la utilización de un aspecto abstracto para encapsular la lógica general del patrón ha hecho factible tener un elemento reutilizable y localizado. Esto implica que al momento de aplicar el patrón solo haga falta indicar sobre qué lugares del sistema actuarán los puntos de corte definidos en el aspecto abstracto y distribuir los roles especificados por el mismo. El detalle de cada una de las características es el siguiente: Localidad del código: Todo el código relacionado al manejo del Rendezvous se encuentra concentrado en el protocolo así como también en las implementaciones específicas de la interface. Reusabilidad: Dada la abstracción que se logró del protocolo principal del Rendezvous, el aspecto abstracto es totalmente reutilizable e incluso configurable con una implementación particular de la interface. Independencia: Desde el momento que el brazo robótico no tiene ningún conocimiento de las restricciones que se le está aplicando, se puede aseverar que es totalmente independiente del patrón. Si se necesitara que funcione un brazo independientemente, no haría falta ningún cambio en absoluto en el código de la clase RoboArm. Transparencia de composición: Un brazo puede participar en más de un Rendevous sin necesidad de agregar restricciones adicionales ni afectar el comportamiento del mismo. 86 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7.1.9. Componente gráfico Existen tres componentes implementados: RoboArm: Brazo robótico. Se puede configurar los límites de tiempo de la duración de su tarea así como también el nombre de la instancia. Rendezvous: Representación del rendezvous. Si no se especifica una dirección de servidor actúa en modo “simple”, es decir en forma local. De lo contrario se conecta con el servidor especificado y pasa a actuar en forma remota pudiendo sincronizarse con todos los rendezvous conectados a ese servidor. Es necesario que se le especifique un nombre de grupo en caso de que exista más de un rendezvous. RendezvousServer: Servidor coordinador y aceptor de llamadas de rendezvous remotos. Cada roboArm que se quiera sincronizar debe ser conectado con el correspondiente rendezvous. El nombre del grupo para el brazo robótico se obtiene automáticamente del Rendezvous, por eso es importante especificar un nombre distinto para cada Rendezvous en caso de que haya más de uno en el diagrama. Al momento de iniciar la simulación, cada brazo robótico comenzará a ejecutar su acción y se representa dicho estado con el color rojo. Una vez finalizada la acción, que tardará un número aleatorio de milisegundos acotado por los límites impuestos, vuelve al estado libre, representado por el color verde. Es en este momento actúa la sincronización y no permite que el brazo vuelva a ejecutar su acción hasta que todos los otros brazos participantes del rendezvous hayan finalizado. Si el brazo no formara parte de un rendezvous, se lo vería constantemente ocupado, es decir, en color rojo. A continuación se puede observar una captura de una configuración con un RendezvousServer por un lado (diagrama superior) y un Rendezvous que sincroniza a tres brazos robóticos por otro lado (diagrama inferior). Diego M.S. Erdödy Patrones de concurrencia y sincronización 87 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 7-7: Diagrama ACVF del caso de estudio para el patrón “Rendezvous” 7.2. 7.2.1. Balking Resumen Si se llama a un método sobre un objeto dado, cuando éste se encuentra en un estado inapropiado, entonces la llamada al método no se ejecutará. 7.2.2. Otras denominaciones Barrera de llamada. 7.2.3. Problema Un objeto puede encontrarse en un estado inapropiado para atender una llamada a un método determinado. Una posible solución sería dejar en espera a la llamada hasta que llegue el momento en que el estado del objeto sea el adecuado, pero esto no siempre cumple los requerimientos deseados (por ejemplo si hay restricciones en el tiempo de respuesta). 7.2.4. Solución 88 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La solución que plantea este patrón de diseño, es simplemente ignorar la llamada y es por eso que sólo se puede aplicar en entornos donde la ejecución no sea crítica dentro del sistema. No podría aplicarse en una situación tan crítica como es, por ejemplo, el latido de un marcapasos. 7.2.5. Caso de Estudio El ejemplo que se eligió para analizar este patrón, es la interacción entre un retrete y las distintas formas que pueden existir de activar su vaciado. Ejemplos de formas de activación son un botón o un sensor de proximidad. Evidentemente, no tiene sentido iniciar un vaciado una vez que otro vaciado se encuentra en curso. Una solución posible es verificar el estado del retrete antes de realizar el pedido de vaciado. El problema con esta solución es que se necesita introducir lógica que no pertenece al interés principal del cliente, que es simplemente realizar el vaciado del retrete. Es por eso que se propone encapsular en un aspecto dicho mecanismo para poder aplicarlo de una forma transparente al cliente. 7.2.6. Estructura Veamos el diagrama de colaboración de la estructura típica: Figura 7-8: Diagrama de estructura del patrón “Balking” El patrón se basa sobre una estructura en la cual un componente es accedido, ya sea en forma remota o local, por un objeto cliente. A continuación se describen los roles de los participantes involucrados en el patrón. Component: Es el objeto sobre el cual se va a aplicar la llamada y el cual necesita una política de manejo de las llamadas en momentos inapropiados. Client: Objeto que actúa como cliente del componente y por ende el responsable de efectuar la llamada al método en cuestión. Según el patrón de diseño puede esperar que el resultado sea nulo o hasta recibir una excepción en el caso de que el estado del componente no sea el adecuado para procesar la llamada. 7.2.7. Estrategia de implementación Orientada a Aspectos La implementación se basa en extraer el mecanismo básico del patrón en un aspecto abstracto llamado BalkingProtocol. La implementación además incluye los objetos representantes del retrete y sus activadores, junto con el aspecto concreto que indica sobre cuales elementos debe aplicarse el mecanismo abstraído en el protocolo. Diego M.S. Erdödy Patrones de concurrencia y sincronización 89 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA NÚCLEO Para la implementación del núcleo del patrón se ha optado por ignorar la llamada en cuestión si es que ya se encuentra en ejecución al momento de iniciar el pedido. Una opción más compleja hubiese sido abstraer el comportamiento que se ejecuta en dicho caso y permitir al usuario aplicar otras políticas alternativas, como por ejemplo registrar el pedido o llamar a otro componente. Se ha elegido el camino simple ya que de cualquiera de las dos maneras se pueden observar los mecanismos básicos de la implementación AOP, que se pretenden analizar. El diagrama de estructura es el siguiente: <<aspect>> BalkingProtocol -busy : boolean <<pointcut>>+executionCall() <<base>> <<advice>>+around(){base = executionCall()} Figura 7-9: Estructura del Núcleo del patrón “Balking” BalkingProtocol: Aspecto abstracto que define el mecanismo principal del patrón. Posee una definición abstracta de pointcut llamada executionCall() la cual representa la llamada que se quiere controlar. Asimismo cuenta con un advice aplicado alrededor de dicho pointcut. El pointcut controla que la llamada sea ejecutada si no hay otra llamada del mismo tipo en proceso, y que sea ignorada en cualquier otro caso. APLICACIÓN CONCRETA al CASO DE ESTUDIO En la aplicación del patrón de diseño se implementó un objeto simulador del retrete, en el cual se pueden configurar características tales como la duración del vaciado. El retrete puede contener uno o más tipos de activación, representado por la interface FlusherActivator al cual se le han implementado dos modos, FlashButton y LightSensor, es decir, a través de un botón mecánico o un sensor de proximidad. Por último se cuenta con la implementación concreta del aspecto que indica sobre cuales elementos se debe aplicar el patrón. El diagrama de estructura es el siguiente: 90 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <<aspect>> FlusherActivator BalkingProtocol ~log : Logger = Logger.getLogger(FlusherActivator.class) #flushers : Set<E->Flusher> = new HashSet<Flusher>() #meanTime : int -stopRequested : boolean = false <<aspect>> BalkingHandler <<pointcut>> <<base>>+executionCall(){base = call (void Flusher.flush())} +actionPerformed() : void +run() : void <<constructor>>+FlusherActivator() <<constructor>>+FlusherActivator( flusher : Flusher ) +addFlusher( fl : Flusher ) : void +removeFlusher( fl : Flusher ) : void <<setter>>+setMeanTime( ms : int ) : void +stopActivator() : void Flusher ~log : Logger = Logger.getLogger(Flusher.class) -flushTime : int = 3000 +flush() : void <<setter>>+setFlushTime( ms : int ) : void FlashButton LightSensor <<constructor>>+FlashButton( flusher : Flusher ) <<constructor>>+LightSensor( flusher : Flusher ) Figura 7-10: Estructura de la aplicación del patrón “Balking” Clases Flusher: Clase que representa el funcionamiento de un retrete. El mensaje flush() es el que realiza el proceso de vaciado del mismo. Es posible configurar el tiempo que toma este proceso por medio del método setFlushTime(). FlusherActivator: Clase abstracta que representa una entidad capaz de accionar la cadena del retrete. Permite vincular (y desvincular) Flushers a los cuales se quieren controlar, a través del método addFlusher(). Implementa también la funcionalidad básica de simulación de la entidad en la cual cada cierto tiempo (especificado con el método setMeanTime()), se acciona la cadena de los retretes vinculados. La simulación se puede iniciar o parar con los métodos run() (de la interface Runnable) y stopActivator(). FlusherButton: Especialización de la clase FlusherActivator que representa un botón para el accionamiento de la cadena del retrete. LightSensor: Especialización de la clase FlusherActivator que representa un sensor de movimiento para el accionamiento de la cadena del retrete. Aspectos BalkingHandler: Aspecto concreto que representa la aplicación del patrón a nuestro ejemplo particular. Para ello lo único que necesita especificar es cuál será la llamada a controlar. Por eso es que define el pointcut executionCall() y lo asigna al método flush() de la clase Flusher. 7.2.8. Análisis Diego M.S. Erdödy Patrones de concurrencia y sincronización 91 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA La lógica asociada al patrón se ha podido abstraer utilizando el mismo mecanismo del aspecto abstracto. A continuación se detalla el análisis de la implementación: Localidad del código: Todo el código relacionado al manejo del Balking se encuentra concentrado en el aspecto abstracto sin necesidad de complicar la lógica propia del Flusher ni otro componente. Reusabilidad: Dada la abstracción que se logró del protocolo principal del BalkingProtocol, el aspecto abstracto es totalmente reutilizable e incluso configurable con una implementación particular de la interface. Independencia: El código que maneja el procedimiento a seguir en caso de que el retrete se encuentre en un estado inadecuado para procesar el pedido de vaciado, se encuentra desacoplado de la lógica de dicho procedimiento. Es por eso que cada interés logra el nivel de independencia necesario para poder evolucionar por separado. Transparencia de composición: Si fuera necesario aplicar el patrón a otro método o aplicarlo al mismo método para manejar otros estados, no habría interferencia entre cada uno, ya que se encuentran modularizados y el único punto de encuentro es la invocación al método. 7.2.9. Componente gráfico Los componentes que fueron implementados para este caso son: Flusher: Representación del retrete. Se puede configurar la duración del vaciado. FlusherActivator: Activador de retrete. Cuenta con dos modos de activación, uno interactivo en donde el usuario puede activar el componente a través de una acción y otro automático en donde el componente se activa aleatoriamente cada cierto tiempo. Es posible conectar un activador con uno o varios retretes y de la misma forma cada retrete puede ser activado por uno o más activadores. Se puede comprobar, dada la aplicación del patrón de diseño en cuestión, que cualquier pedido de vaciado a un retrete que se encuentra en proceso de vaciamiento, es ignorado. El siguiente es un ejemplo que muestra todas las posibles combinaciones de enlace: 92 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 7-11: Diagrama ACVF del caso de estudio para el patrón “Balking” El diagrama corresponde a una simulación en el modo interactivo. El primer activador actúa sobre ambos retretes simultáneamente, mientras que el segundo retrete es activado por cualquiera de los dos activadores. 7.3. 7.3.1. Observador Resumen El concepto central de este patrón de diseño es que uno o más objetos llamados observadores puedan registrarse con otro objeto llamado „sujeto‟, el cual notificará un determinado evento a dichos observadores. 7.3.2. Otras denominaciones Publisher – Subscriber 7.3.3. Problema El problema consiste en hallar una forma de poder notificar a un grupo de objetos, en el momento en que ocurre un cierto evento asociado a otro objeto dado. Dicho evento puede ser el cambio de valor de uno de sus atributos, la llamada a un método o cualquier acción que pueda llegar a afectar su estado. Una alternativa es que cada objeto cliente trate de averiguar el estado de dicho evento activamente, lo cual trae aparejado un gasto de recursos innecesario. Diego M.S. Erdödy Patrones de concurrencia y sincronización 93 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7.3.4. Solución La solución que propone el patrón consta de dos fases. Por un lado, los objetos que necesitan ser notificados de los cambios (observadores), deben “registrarse” al objeto fuente de dichos eventos (sujeto). De esta forma, solicitan que se les “avise” cuando un cambio pertinente tenga lugar. Por otro lado, el sujeto debe “notificar” a los observadores cuando dicho cambio ocurre. Para ello, invocará el método prefijado de la referencia que le hayan provisto los observadores al momento de la registración. En dicha invocación, se provee información acerca del evento que originó la llamada. 7.3.5. Caso de estudio Se eligió como caso de estudio, la interacción entre un sensor de velocidad de un auto y un medidor que actúa de interface con el usuario. El problema es que el medidor debe ser informado cada vez que el sensor detecta un cambio. 7.3.6. Estructura La estructura que sugiere el patrón propone un desacoplamiento entre los observadores y el sujeto, al incorporar un mecanismo de suscripción basado en abstracciones de cada rol. Al mismo tiempo, permite notificar a los observadores en el momento preciso en que el evento es capturado, o poco tiempo después. El diagrama de estructura es el siguiente: Puede ser una referencia o una copia del dato 1 Data -value 1 AbstractSubject AbstractClient +accept(in data) * ConcreteClient 1 +subscribe(in client : AbstractClient) +unsubscribe(in client : AbstractClient) ConcreteSubject Figura 7-12: Diagrama de estructura del patrón “Observador” Los participantes del patrón son: 94 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA AbstractSubject: Actúa como servidor de información para los Observadores 7.3.7. (AbstractClient) que consumen dicha información. Permite incorporar o eliminar asociaciones con clientes mediante los métodos subscribe() y unsubscribe(). Es su tarea informar a los clientes cuando está disponible la información. Un ejemplo típico de dicha información es el cambio en el valor de uno de sus atributos. AbstractClient: Representa un objeto que desea conocer la información que un sujeto (AbstractSubject) tiene para brindar. Contiene el método accept() que es llamado para proveerle la información deseada. ConcreteClient: Subclase específica de la aplicación de la clase abstracta AbtractClient. Contiene la funcionalidad específica de la aplicación del patrón. ConcreteSubject: Subclase especifica de la aplicación de la clase abstracta AbstractSubject. Contiene la funcionalidad específica de la aplicación del patrón. Data: Contiene la información que el Sujeto conoce y le transmitirá a todos los Observadores subscriptos. Estrategia de implementación Orientada a Aspectos La implementación orientada a aspectos, se divide en dos partes. Por un lado, el núcleo del patrón, con la lógica que se abstrae y modulariza. Por otro lado, la aplicación de dicho núcleo y los objetos propios del caso de estudio. NÚCLEO La implementación del núcleo, consta de un aspecto abstracto que contiene el protocolo del patrón y por otro lado dos interfaces de “marca” que define los roles definidos por el patrón. Una interface de marca es aquella que no contiene métodos ni atributos, y sólo sirve para identificar tipos de objetos. El diagrama de estructura es el siguiente: <<aspect>> ObserverProtocol -perPublisherSubscribers : Map<Publisher, List<Subscriber>> #getSubscribers( publisher ) : List<Subscriber> #updateSub scrib er( pub lisher : Pub lisher, ob server : Sub scrib er ) : void +subscribe( publisher : Publisher, subscriber : Subscriber ) : void +unsubscribe( publisher : Publisher, observer : Subscriber ) : void <<pointcut>>+pub lisherChanges( pub lisher : Pub lisher ) <<base>> <<advice>>+after( publisher : Publisher ){base = publisherChanges(publisher)} Publisher Subscriber Figura 7-13: Estructura del Núcleo del patrón “Observador” Interfaces Diego M.S. Erdödy Patrones de concurrencia y sincronización 95 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Publisher: Indica que el objeto será observado frente a determinados cambios. Subscriber: Indica que el objeto actuará de suscriptor, es decir, podrá ser notificado cuando el objeto del tipo Publisher al cual se suscribió cambie en alguna de las formas predeterminadas. Aspectos ObserverProtocol: Aspecto abstracto que define el mecanismo principal del patrón. Posee un atributo, perPublisherSubscriber, que almacena las relaciones entre Publishers y Subscribers para las suscripciones realizadas. Para acceder a dichas suscripciones desde un aspecto derivado, posee un método llamado getSubscribers() el cual recibe el Publisher del cual se quieren consultar suscriptores, como parámetro. Posee también dos métodos para el manejo de suscripciones, suscribe() y unsuscribe() los cuales reciben el par Publisher, Subscriber como parámetros. Nótese que ambos métodos son parte del aspecto, por lo que el manejo de suscripciones es ahora desacoplado tanto del sujeto como del suscriptor para pasar a formar parte de una entidad localizada que maneje el vínculo entre ellos. El único pointcut abstracto definido en el aspecto, publisherChanges(Publisher), representa los puntos indicadores de un cambio en la clase (o las clases) definida como Publisher. Por último se define e implementa un advice sobre este pointcut, el cual simplemente llama al método protegido abstracto (Template Method [Gam 95]) updateSubscriber (Publisher, Subscriber) para cada subscriptor asociado al Publisher en cuestión. APLICACIÓN CONCRETA al CASO DE ESTUDIO La aplicación del patrón al caso de uso, se realiza con un aspecto concreto que especifica que join points debe capturar el pointcut y como notificará a los suscriptores del evento esperado. También se indica, a través del aspecto concreto, quien cumple cada rol especificado en el núcleo del patrón. Para el modelado del caso de estudio, se cuenta con dos objetos que cumplen con las funciones de velocímetro y sensor de velocidad. Estos objetos cumplirán el papel de observador y sujeto, respectivamente. El diagrama de estructura es el siguiente: 96 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA <<aspect>> ObserverProtocol Publisher Subscriber <<aspect>> ObserverHandler <<base>> <<pointcut>>+publisherChanges( publisher : Publisher ){base = execution (void SpeedSensor.speedChanged()) && target(publisher)} #updateSubscriber( publisher : Publisher, observer : Subscriber ) : void <<declare parents>> SpeedSensor implements Publisher <<declare parents>> SpeedMeter implements Subscriber SpeedSensor ~log : Logger = Logger.getLogger(SpeedSensor.class) -_speed : int -stopped : boolean = false SpeedMeter ~log : Logger = Logger.getLogger(SpeedMeter.class) +speedChanged( speed : int ) : void +run() : void +speedChanged() : void <<getter>>+getSpeed() : int +stop() : void Figura 7-14: Estructura de la aplicación del patrón “Observador” Clases SpeedSensor: Clase que representa un sensor de velocidad. Es este caso el atributo que nos interesa monitorear es precisamente la velocidad (speed). El método run() provee la simulación del cambio de velocidad a períodos regulares y el método speedChanged() indica que dicha velocidad se ha modificado. El método getSpeed() permite obtener la velocidad actual y por último el método stop() detiene la simulación. SpeedMeter: Clase que representa un medidor de velocidad el cual actúa como interface hacia el usuario y requiere información externa, en nuestro caso un sensor. Posee un método llamado speedChanged(int) el cual provee la información externa sobre cambios en la velocidad a mostrar. Aspectos ObserverHandler: Aspecto concreto derivado de ObserverProtocol. Por un lado específica las clases que cumplen los roles de Publisher y Subscriber, es decir, SpeedSensor y SpeedMeter. Por otro lado determina que el join point para el pointcut publisherChanges será la llamada al método speedChanged() de la clase SpeedSensor e im- Diego M.S. Erdödy Patrones de concurrencia y sincronización 97 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA plementa el método updateSubscriber() con una llamada al método speedChanged(int) de la clase SpeedMeter. Este aspecto actúa de “mediador” entre el aspecto abstracto que implementa el mecanismo general del patrón y las clases a las cuales se lo quiere aplicar. 7.3.8. Análisis Este patrón es un caso de abstracción total del comportamiento del aspecto y se ha podido modularizar en un aspecto reutilizable, como se demuestra en la aplicación al caso de estudio. A continuación se analizan los distintos puntos de estudio. Localidad del código: Este patrón es un claro ejemplo de localidad de código generada de forma inherente a las características de la programación orientada a aspectos. El interés transversal (mecanismo de observación) ha sido abstraído y materializado en un único aspecto, ObserverProtocol. Reusabilidad: El aspecto abstraído, además de estar localizado, es totalmente reutilizable dado que para una nueva aplicación sólo basta con crear un aspecto concreto derivado que indique cuales van a ser los participantes de la observación y que se va a observar. Independencia: Al no tener ninguno de los componentes conocimiento alguno sobre las interacciones involucradas en la implementación del patrón, cada uno de ellos puede evolucionar de manera independiente sin afectar el comportamiento de ninguna de las partes. Transparencia de composición: La forma de manejo implementada de las referencias suscriptas garantiza que no haya interferencia entre distintas aplicaciones del patrón. Por otro lado, al mantener los niveles de invasión por parte del aspecto sobre los componentes que actúan en cada rol (observador y sujeto), la composición con otros patrones no traería inconvenientes. 7.3.9. Componente gráfico Los siguientes componentes fueron implementados para este patrón: SpeedSensor: Dispositivo sensor de velocidad. Obtiene una lectura periódica de velocidad cuyo valor es el atributo “observable” del componente. SpeedMeter: Representación del velocímetro que mostrará la información provista por el sensor. El medidor de velocidad mostrará el registro obtenido del sensor que tenga asociado, de manera casi simultánea a la obtención de dicha medición por parte del sensor. Nótese que cada sensor puede tener más de un medidor asociado en cuyo caso todos mostrarán el mismo registro simultáneamente. El siguiente es un ejemplo que muestra todas las posibles combinaciones de enlace: 98 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 7-15: Diagrama ACVF del caso de estudio del patrón “Watchdog” La simulación sólo puede correrse en modo no-interactivo, y consta de valores aleatorios que genera el sensor y muestra en el compartimiento de estado. Los velocímetros, simplemente reproducen el valor del sensor que tengan asociado, también en el compartimiento de estado. 7.4. 7.4.1. Optimistic Locking Resumen El acceso y modificación concurrente del contenido de un recurso compartido (por ejemplo una tabla de una base de datos), puede generar inconsistencia en el contenido de dicho recurso, si no se toman las precauciones pertinentes. El bloqueo optimista es una solución intermedia que supone el caso más optimista (nadie modificó el elemento desde la última vez que se accedió) y se toman acciones en caso de que dicho supuesto sea incorrecto. 7.4.2. Otras denominaciones Control Optimista de Concurrencia 7.4.3. Problema El acceso a un recurso de lectura-escritura compartido trae aparejado problemas de consistencia si no se establece una política de sincronización adecuada. 7.4.4. Solución Diego M.S. Erdödy Patrones de concurrencia y sincronización 99 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Cuando es necesario tener acceso de lectura y escritura concurrente sobre elementos compartidos en un entorno distribuido, existen 3 políticas posibles a aplicar. La política simplista consiste en no tomar ninguna acción si el recurso es modificado por dos o más clientes a la vez. Se obtiene como resultado un esquema en donde “el último en modificar es el que gana”. Es decir, un cliente puede acceder a la información, manipularla, luego almacenarla y estar sobrescribiendo una actualización que se hizo sobre la misma información en el transcurso de la manipulación. Esta política trae aparejada pérdida de modificaciones por manipulación de información local desactualizada. En el otro extremo del espectro, se sitúa la política pesimista. Esta política consiste en bloquear el elemento a acceder y no liberarlo hasta no haber terminado de manipularlo y almacenarlo. Por un lado, esta política asegura la consistencia en todo momento pero tiene como gran desventaja una disminución considerable de la performance en los casos donde accesos de lectura concurrentes al mismo elemento son altamente probables. En un punto intermedio se encuentra la política optimista. El concepto principal es suponer que el recurso no cambiará mientras se está actualizando. Por consiguiente, para mantener la consistencia, es necesario detectar cuando no se cumple. Para realizar dicha verificación, al momento de almacenar el nuevo valor, se debe constatar que no haya sido modificado por otro desde la última lectura. Para ello se debe agregar un indicador al recurso a modo de meta-información, como por ejemplo, el número de versión o la fecha de última modificación. Este indicador sirve para verificar que no hubo una operación de actualización simultánea. Si ninguna modificación ha existido, entonces se procede de manera normal (la suposición optimista se ha cumplido). En caso contrario, se debe informar al usuario (por ej. a través de una excepción o código de error) que debe repetir la operación, lo cual implica obtener nuevamente el recurso. La política optimista se debe aplicar en un entorno en el cual los cambios sobre un mismo recurso no sean muy frecuentes. En caso contrario, se corre el riesgo que una actualización requiera demasiados intentos para poder concretarse. 7.4.5. Caso de estudio El caso de estudio que se ha elegido para este patrón es el acceso concurrente a un registro genérico en una tabla de una base de datos. Dicho registro solo contiene un atributo a modo de ejemplo y una clave primaria. 7.4.6. Estructura Los participantes del patrón no tienen una relación estructural entre sí, por lo cual se eligió un diagrama de interacción para representar al patrón. En este caso, la interacción de los participantes es más relevante que la estructura de los mismos. El diagrama de interacción es el siguiente: 100 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Cliente Recurso Controlador de Concurrencia Optimista 1: obtener() 2: modificar 3: validar 4: controlar 5: almacenar Figura 7-16: Interacción del patrón de concurrencia optimista El diagrama muestra a los siguientes participantes que componen el patrón: Cliente: Usuario del recurso compartido. Recurso: Recurso compartido que se pretende sincronizar. Se parte de la base de que no hay restricciones sobre el acceso al mismo. Administrador de Concurrencia Optimista: Encargado de controlar que la suposición optimista se cumpla, y en consecuencia mantener la consistencia de los datos. Esto quiere decir que al momento de realizar una operación de almacenamiento sobre el recurso, el controlador debe asegurarse que no ha habido una modificación desde la última vez que ese cliente accedió al mismo. La interacción que representa al patrón posee los siguientes pasos: 1) El cliente accede a los datos del recurso, con una operación de lectura. 2) El cliente procesa y modifica el valor local de los datos obtenidos del recurso. 3) Antes de poder almacenar el valor, el cliente le pide al controlador que valide el estado del recurso. 4) El controlador debe obtener meta-información sobre el recurso, para controlar que no se haya modificado desde que se realizó el paso 1. 5) En el caso de que la suposición optimista se cumple, el cliente procede a almacenar los nuevos valores en el recurso compartido. La interacción previamente descripta corresponde al caso exitoso. En cambio, si la suposición optimista no se cumpla en el paso 4, el controlador debe informar la situación al cliente y el recurso debe ser leído nuevamente para retomar la secuencia desde el paso 2. Diego M.S. Erdödy Patrones de concurrencia y sincronización 101 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 7.4.7. Estrategia de implementación Orientada a Aspectos La implementación se divide en un núcleo reusable y la aplicación del mismo al caso de estudio. NÚCLEO El núcleo consta de un aspecto abstracto que contiene los pointcuts que deben ser especificados y la lógica reusable del controlador optimista de concurrencia. Por otro lado, se definen las interfaces para el recurso (BaseRecord) y se provee implementación para dichas interfaces. A continuación se muestra el diagrama de interacción y la descripción de los componentes: <<aspect>> OptimisticProtocol ~log : Logger = Logger.getLogger(OptimisticProtocol.class) <<pointcut>>+update( record : OptimisticRecord ) +getCurrentVersion( id : OptimisticRecord ) : int #updateVersion( record : OptimisticRecord ) : void <<pointcut>>+ implementationLoad( record : OptimisticRecord, result : ResultSet ) <<pointcut>>+ implementationCreate( record : OptimisticRecord ) <<pointcut>>+ implementationUpdate( record : OptimisticRecord ) <<advice>> <<base>>+before( record : OptimisticRecord ){base = update(record)} <<advice>> <<base>>+after( record : OptimisticRecord, rs : ResultSet ){base = implementationLoad(record, rs)} <<advice>> <<base>>+after( record : OptimisticRecord ){base = implementationUpdate(record)} <<advice>> <<base>>+after ( record : OptimisticRecord ){base = implementationCreate(record)} <<introduction>> OptimisticRecord BaseRecord <<getter>>+getId() : int <<introduction>> OptimisticRecord BaseType -version : int -increaseVersion() : void -setVersion( version : int ) : void <<getter>>+getVersion() : int OptimisticRecord <<getter>>+getVersion() : int Figura 7-17: Estructura del Núcleo del patrón “Optimistic Locking” Interfaces BaseRecord: Define los atributos comunes a todos los tipos de registros a definir en el sistema. El único atributo identificado es el “id” que contiene la clave del registro, con un acceso de sólo-lectura (solamente posee un método getter). 102 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA OptimisticRecord: Identifica a los registros que van a ser tratados de forma optimista, adicionándole un atributo de sólo-lectura que identificará la versión del registro (“version”). Aspectos OptimisticProtocol: Aspecto abstracto que posee el comportamiento común del patrón. Define un pointcut llamado update que sirve para especificar el momento en el cual se guarda el registro sobre el cual se aplica el patrón. Sobre este pointcut se especifica un advice, que es la parte principal del patrón, en donde se verifica que la versión actual del registro sea igual a la de la información a guardar, sino, se lanza una excepción para informar al usuario que el registro fue actualizado por un tercero y que debe obtener la última versión y volver a procesarlo antes de poder guardarlo. En este aspecto también se realiza la implementación (o introducción) del atributo version de la interface OptimisticRecord. Por último, para el manejo automático de la versión, se definen tres pointcuts en donde se definirán los momentos de carga, creación y actualización de los registros (implementationLoad, implementationCreate e implementationUpdate). Para cada uno se especifica un advice que agrega la lógica de manejo de la versión. Para ello se agregan dos métodos abstractos getCurrentVersion() y updateVersion() que serán invocados cuando se necesite obtener la versión actual o actualizar dicha versión, respectivamente. APLICACIÓN CONCRETA al CASO DE ESTUDIO La aplicación al caso de estudio consiste en la definición del registro y el aspecto concreto que especifica los pointcuts y comportamiento necesario para el manejo de la meta-información del recurso. Como se verá más adelante, el manejo de la metainformación hace que la implementación del patrón no sea completamente localizada. Diego M.S. Erdödy Patrones de concurrencia y sincronización 103 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA BaseRecord <<getter>>+getId() : int Record <<getter>>+getAttrib () : String <<setter>>+setAttrib ( attrib : String ) : void OptimisticRecord <<getter>>+getVersion() : int RecordManager <<constructor>>-RecordManager() +newRecord( id : int ) : Record +saveRecord( record : Record ) : void +deleteRecord( id : String ) : void RecordImpl ~log : Logger = Logger.getLogger(RecordImpl.class) -id : int -attrib : String -isDirty : boolean = false -isNew : boolean = true <<constructor>>+RecordImpl( id : int ) <<getter>>+getAttrib() : String <<setter>>+setAttrib( attrib : String ) : void <<getter>>+getId() : int +load() : boolean #fillAttributes( rs : ResultSet ) : void #update() : void #create() : void +save() : void +delete() : void <<aspect>> OptimisticProtocol <<aspect>> OptimisticRecordAspect +getCurrentVersion( id : OptimisticRecord ) : int #updateVersion( record : OptimisticRecord ) : void <<pointcut>> <<base>>+update( record : OptimisticRecord ){base = execution (public void RecordManager+.saveRecord(Record)) && args(record)} <<pointcut>> <<base>>+ implementationLoad( record : OptimisticRecord, result : ResultSet ){base = execution(void RecordImpl.fillAttributes(ResultSet)) && args(rs) && target(record)} <<pointcut>> <<base>>+ implementationUpdate( record : OptimisticRecord ){base = execution(void RecordImpl.update()) && target(record)} <<pointcut>> <<base>>+ implementationCreate( record : OptimisticRecord ){base = execution(void RecordImpl.create()) && target(record);} <<declare parents>> Record implements OptimisticRecord Figura 7-18: Estructura de la aplicación del patrón “Optimistic Locking” Interfaces Record: Define los atributos correspondientes al registro. En el ejemplo sólo posee un atributo llamado “attrib”. Clases RecordManager: Responsable de administrar los registros encapsulando así el acceso a la base de datos. Posee un método por cada operación posible sobre un registro. El método newRecord() tiene una doble función, en el caso de que el id ya exista, se obtiene el registro; si por el contrario el id no existe, entonces se procede a crear un nuevo registro vacío. El método saveRecord() permite guardar el contenido de un registro y por último se puede borrar un registro llamando a deleteRecord() RecordImpl: Implementación del registro. Además de implementar el atributo requerido por la interface, posee la lógica protegida para obtener, crear o actualizar los datos del registro (load(), save() y update()). El método save() efectúa una creación o actualización del registro según el estado en el que se encuentre (si ya fue creado o no). Estos métodos son utilizados por el RecordManager cada vez que una operación es requerida sobre un registro. Por último, el método fillAttributes() se encarga de popular el registro con los datos que se obtienen de la base de datos. 104 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA ConnectionPool: Implementación para el soporte de un repositorio de conexiones de base de datos. Para obtener una nueva conexión basta con llamar al método estático getConnection(). Nótese que la implementación sólo soporta una base de datos y esto es intencional, para simplificar el caso de estudio. Aspectos OptimisticRecordAspect: Aspecto concreto que extiende de OptimisticProtocol. Este aspecto realiza la vinculación necesaria para aplicar el patrón al registro del caso de estudio. La primera acción que toma, es extender la interface Record de OptimisticRecord para poder hacer uso del atributo version. Luego, como definición del pointcut update, define la llamada al método save() de la clase RecordManager o cualquiera de sus derivados. También agrega la implementación específica de los métodos getCurrentVersion() y updateVersion(), conectándose directamente a la base de datos y obteniendo o actualizando el atributo que representa el número de versión. Por último, se mapean los pointcuts implementationLoad, implementationCreate e implementationUpdate con la llamada a los métodos fillAttributes(), create() y update() de la clase RecordImpl. 7.4.8. Análisis El patrón se ha podido modularizar y localizar, pero no en forma completa. Quedan implementaciones tales como el manejo de la meta-información del recurso que tiene que ser implementado para cada caso en particular, siendo parte central del patrón. El análisis de las características es el siguiente: Localidad del código: En este caso se puede catalogar el código de la implementación como „muy localizado‟ dado que ciertos métodos han tenido que ser implementados en el aspecto concreto, pero el comportamiento asociado a la sincronización optimista, se pudo abstraer en un modulo reusable. Reusabilidad: La localidad del código lograda, permite alcanzar un alto nivel de reusabilidad. La reusabilidad no es total, ya que los métodos getCurrentVersion() y updateVersion() del aspecto concreto deben ser implementados por cada aplicación. Independencia: El nivel de independencia es alto pero no es total. La independencia se pierde, por ejemplo, cuando la estructura del registro incorpora un nuevo atributo que utiliza la misma denominación que el atributo utilizado para identificar el número de versión. Transparencia de composición: En este caso, no tiene sentido aplicar más de una vez el patrón al mismo tipo de registro. 7.4.9. Componente gráfico En este caso se implementaron los siguientes componentes visuales: Table: Representa una tabla en la base de datos que contiene registros del tipo “Record”. Se puede configurar el nombre de la tabla y se visualiza el contenido de los registros. Diego M.S. Erdödy Patrones de concurrencia y sincronización 105 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA OptimisticRecord: Registro del tipo “Record” en su versión optimista el cual se puede asociar a un elemento del tipo Table. Posee las siguientes propiedades: o Record id: clave del registro con el que se desea trabajar. o Record value: valor del atributo del registro. En tiempo de simulación se pueden invocar las siguientes acciones: o Save: Intenta guardar el registro en la base de datos. o Retrieve: Sobrescribe la instancia del registro con el valor de la base de datos. En la siguiente figura se observa un diagrama con una tabla y dos registros, “rec1” y “rec2”, los cuales poseen el mismo id (0 en este caso): Figura 7-19: Diagrama del patrón “Optimistic Locking” Una vez iniciada la simulación, en caso de existir el id de algún registro, cada registro obtiene su información de la base de datos. De manera interactiva, se puede guardar cualquiera de los registros presionando el botón “save”. Como consecuencia, se incrementa en uno el número de versión almacenado en la base de datos. En el diagrama se muestra que “rec1” ha sido recientemente guardado y posee el número de versión 3. De la misma forma, se puede cargar el nuevo valor en “rec2” presionando el botón “retrieve” del componente. Si en lugar de esto se hubiese querido quiere guardar el valor de “rec2”, el mecanismo optimista se encargaría de avisar que se debe actualizar el registro antes de poder guardarlo, dado que el número de versión que contiene es menor al que se encuentra en la base de datos. Esta discrepancia indica que alguien ha hecho una modificación desde que fue cargado por última vez. Este caso es el que muestra la figura. 106 Patrones de concurrencia y sincronización Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 8. Patrones de manejo de eventos 8.1. 8.1.1. Reactor Resumen El patrón de diseño Reactor [Sch 95] separa las responsabilidades de un proveedor de servicios en un entorno cliente-servidor distribuido, con acceso concurrente a los distintos servicios. El patrón está compuesto por un demultiplexor sincrónico, que tiene como responsabilidad acceder a los distintos canales de comunicación de manera sincrónica e identificar y decodificar los eventos relevantes. También está compuesto por un notificador, es el encargado de recibir y administrar dichos eventos así como también de notificar a los procesadores correspondientes. Por último, el EventHandler es el que procesa un evento en particular, sin necesidad de intervenir en la administración de los eventos. 8.1.2. Otras denominaciones Notificador, Despachante (“Dispatcher”). 8.1.3. Problema El desarrollo de servidores concurrentes plantea ciertos problemas en cuanto al modo de atender los pedidos. Las necesidades a tener en cuenta son: El servidor debe brindar una separación clara de responsabilidades entre la recepción de pedidos, el manejo de los eventos y el procesamiento de los mismos por parte de los distintos servicios. El servidor debe tener una alta disponibilidad. Esto implica que no debe bloquear el ingreso de nuevos pedidos, al esperar el ingreso de un pedido en particular o el procesamiento de otro pedido en curso. El servidor se debe adaptar de manera sencilla a los cambios en el formato de los requerimientos o a la incorporación de nuevos servicios. El servidor debe brindar una alta performance. Se debe maximizar la cantidad de pedidos procesados por unidad de tiempo y al mismo tiempo minimizar la latencia y el consumo de procesador. Una de las implementaciones más simples consta de un componente que esté escuchando alternativamente en cada canal, por un período de tiempo prefijado, y que al momento de recibir un pedido, inicie un hilo de ejecución en donde se procese dicho evento. Un problema asociado a esta implementación es que la creación de nuevos hilos de ejecución es generalmente costosa en cuanto a recursos del sistema operativo (desde ya que dependerá de la implementación del SO que se esté utilizando) y limita la cantidad de pedidos que se pueden procesar concurrentemente. Diego M.S. Erdödy Patrones de manejo de eventos 107 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 8.1.4. Solución La solución propuesta por el patrón consiste en desacoplar los componentes de la siguiente manera. Los proveedores del servicio, responsables de procesar un tipo de evento específico, serán implementaciones de una interfaz común denominada EventHandler. Los EventHandler se registran a un componente llamado InitiationDispatcher, que será el encargado de interceptar los eventos y transmitirlos al EventHandler adecuado. Para esta tarea cuenta con un Event Demultiplexer cuyo objetivo es la recepción física y decodificación del evento de una manera sincrónica. Esta solución aplica el concepto de Inversión de Control (IoC) ya que los proveedores de los servicios no tienen que preguntar periódicamente por la llegada de un evento, sino que la responsabilidad es delegada en otro componente que lo notificará en el momento adecuado. El mecanismo es similar al patrón Observador. 8.1.5. Caso de estudio El caso de estudio elegido, basado en el trabajo original [Sch 95], es un servidor de bitácora (log) distribuido. El servidor recibe mensajes de clientes, a través del protocolo orientado a conexión TCP, y registra un archivo histórico de los mismos. 8.1.6. Estructura El patrón se basa en separar los roles de recepción e identificación de eventos y el procesamiento de los mismos. A continuación se muestra el diagrama de estructura correspondiente al patrón y la descripción de los participantes que cumplirán dichos roles: InitiationDispatcher +handleEvents() +registerHandler(in handler : EventHandler, in event : Event) +deregisterHandler(in handler : EventHandler, in event : Event) 1 * «interface» EventHandler +handleEvent(in event : Event) +getHandle() : Handle 1 1 1 1 «interface» Handle Synchronous Event Demultiplexer 1 * Figura 8-1: Estructura del patrón “Reactor” Los participantes del patrón son los siguientes: EventHandler: Encargado de procesar un determinado tipo de evento. El procesamiento es llevado a cabo en el método handleEvent(). 108 Patrones de manejo de eventos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Handle: Objeto encargado del acceso a un canal de comunicaciones de entrada (como por ejemplo un socket TCP), por el cual ingresan los eventos. Synchronous Event Demultiplexer: Espera sobre un grupo de Handles e informa cuando se recibió un evento relevante que puede ser procesado. InitiationDispatcher: Objeto mediador entre el canal de comunicación y los EventHandlers. Contiene un conjunto de pares “Event-EventHandler” que especifican cual EventHandler deberá atender cierto tipo de evento. Define los métodos registerHandler() y deregisterHandler() para agregar o quitar pares al conjunto, respectivamente. El método handleEvents() utiliza el demultiplexer para esperar la llegada de eventos y los entrega al EventHandler correspondiente. 8.1.7. Estrategia de implementación Orientada a Aspectos El núcleo del patrón se divide en dos partes. Por un lado, los elementos involucrados en el procesamiento de los eventos y por otro, los concernientes a la recepción y derivación de dichos eventos. NÚCLEO - Procesamiento de Eventos La parte de procesamiento de eventos del núcleo, contiene las interfaces que definirán los puntos de extensión del patrón. También define los elementos básicos como el evento a utilizar y la encapsulación de la conexión (Handle). El diagrama de estructura es el siguiente: Diego M.S. Erdödy Patrones de manejo de eventos 109 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Event EventHandlerImpl EventHandler +handleEvent( e : Event ) : void <<getter>>+getHandle() : Handle <<introduction>> EventHandler <<constructor>>+Event( code : byte ) <<getter>>+getCode() : byte <<JavaElement>>+toString() : String{JavaAnnotations = @Override} <<JavaElement>>+equals( ev : Object ) : boolean{JavaAnnotations = @Override} <<JavaElement>>+hashCode() : int{JavaAnnotations = @Override} <<crosscut>> Handle <<introduction>> <<constructor>>+Handle( host : String, port : int ) <<constructor>>+Handle( port : int ) <<constructor>>+Handle( s : Socket ) +waitForEvent( timeout : int, dispatcher : InitiationDispatcher ) : boolean +read( b : byte"[]" ) : void +read() : Object +write( e : Event ) : void +write( b : byte"[]" ) : void +write( o : Object ) : void <<setter>>+setInUse( b : boolean ) : void +close() : void +waitForDeath() : void <<getter>>+getLastEvent() : Event EventHandler BaseTypeEH -reactor : Reactor -handle : Handle <<getter>>+getReactor() : Reactor <<getter>>+getHandle() : Handle <<setter>>+setReactor( reactor : Reactor ) <<setter>>+setHandle( handle : Handle ) AcceptorImpl Acceptor <<setter>>+setPeerHandle( h : Handle ) : void <<introduction>> Acceptor <<crosscut>> <<introduction>> Acceptor BaseTypeA -peerHandle : Handle <<getter>>+getPeerHandle() : Handle <<setter>>+setPeerHandle( handle : Handle ) Figura 8-2: Estructura del Núcleo (sector Event Handling) del patrón “Reactor” Interfaces EventHandler: Procesador de eventos que implementará un servicio en particular. Posee un método handleEvent() dedicado al procesamiento propiamente dicho del evento al cual se registró. El método getHandle() permite obtener la referencia del canal asociado al evento, para poder acceder a los datos que formarán parte de la entrada del proceso. Acceptor: Tipo especial de EventHandler cuya responsabilidad es recibir nuevas conexiones. El Handle asociado debe ser de tipo servidor (para recibir conexiones) y cuando se recibe una nueva conexión, el nuevo Handle creado se almacena bajo el atributo peerHandle. Generalmente, se registra a eventos del tipo OPEN_SERVICE (nueva conexión) y el procesamiento implica la registración de un EventHandler específico del servicio que atenderá los eventos de datos de la conexión cliente. Clases Event: Clase que encapsula el evento que será transmitido. El evento se identifica por un código numérico. Ya que se trata de un objeto inmutable (que no puede cambiar de estado), dicho código se debe proveer al momento de construirlo y solo se puede acceder a través del método getCode(). Handle: Encargado del acceso a un canal TCP. El acceso al canal se puede iniciar en modo servidor, especificando solamente el puerto en el constructor, o en modo cliente, en 110 Patrones de manejo de eventos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA cuyo caso se debe proveer además la dirección de destino. Posee métodos de lectura/escritura (read() y write() y de recepción sincrónica de eventos (waitForEvent()). También se puede acceder al último evento recibido (getLastEvent()). Aspectos EventHandlerImpl: Aspecto encargado de la implementación de la interface EventHandler. Introduce los atributos reactor y handle, y los métodos getter y setter de dichos atributos. AcceptorImpl: Implementación de la interface Acceptor. Introduce el atributo peerHandle y los métodos getter y setter asociados. NÚCLEO – Direccionamiento de Eventos La parte de direccionamiento de eventos del núcleo tiene como responsabilidad la recepción y entrega de los eventos a los EventHandlers correspondientes. Para ello cuenta con un mecanismo de registración de EventHandlers a eventos de un tipo específico. El patrón original está pensado para lenguajes como C, que poseen mecanismos tales como el select, el cual permite capturar eventos sobre múltiples Handles de manera simultánea. Para reemplazar dicho mecanismo, se utilizó un esquema de round-robin sobre los handles con un tiempo de expiración ajustable. El diagrama de estructura es el siguiente: InitiationDispatcher HandleInfoManager HandleInfo +run() : void +kill() : void <<getter>>+getEventInfoManager() : HandleInfoManager <<getter>>+getCurrentHandleInfo() : HandleInfo +registerHandler( eh : EventHandler, e : Event ) : void +removeHandler( eh : EventHandler, e : Event ) : void <<getter>>+getCurrentHandleInfo() : HandleInfo <<getter>>+getNextHandleInfo() : HandleInfo <<constructor>>+HandleInfo( eh : EventHandler ) +addEvent( event : Event ) : void +removeEvent( event : Event ) : void <<getter>>+getEvents() : Collection <<getter>>+getHandle() : Handle <<getter>>+getEventHandler() : EventHandler Reactor +registerHandler( handler : EventHandler, type : Event ) : void +removeHandler( handler : EventHandler, type : Event ) : void +handleEvents( timeout : long ) : void +stopHandling() : void Handle Event EventHandler Figura 8-3: Estructura del Núcleo (sector Event Dispatching) del patrón “Reactor” Clases Reactor: Componente central del patrón. Actúa de fachada para el InitiationDispatcher que opera por detrás. Posee métodos para registrar y quitar EventHandlers al mecanismo de reacción. El método handleEvents() inicia dicho mecanismo. El método stopHandling() detiene el mecanismo. InitiationDispatcher: Encargado del mecanismo de reacción. Extiende de la clase Thread para poder correr en un hilo de ejecución independiente. El método start() inicia el mecanismo que consiste en recorrer los distintos EventHandlers y esperar un determinado tiempo a que ocurra algún evento. Ante la llegada de un evento se ejecuta el EventHandler registrado. De lo contrario se sigue con el siguiente. Diego M.S. Erdödy Patrones de manejo de eventos 111 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA HandleInfoManager: Encargado de administrar la información de registraciones de EventHandlers y el recorrido de los mismos. Los métodos getCurrentHandleInfo() y getNextHandleInfo() permiten recorrer los EventHandlers. HandleInfo: Contenedor de la asociación entre un EventHandler y los distintos eventos a los cuales se registró. Posee métodos para el acceso al EventHandler y agregar o quitar eventos. El método getHandle() devuelve el Handle asociado al EventHandler. APLICACIÓN CONCRETA al CASO DE ESTUDIO La aplicación del patrón consta de las implementaciones de las dos interfaces que definen los puntos de extensión y un objeto que representa a un cliente del servicio. El diagrama de estructura es el siguiente: LoggingClient EventHandler Acceptor <<constructor>>+LoggingClient( host : String, port : int ) <<constructor>>+LoggingClient() +log( s : String ) : void +close() : void +init() : void <<getter>>+getHostName() : String <<setter>>+setHostName( hostName : String ) : void <<getter>>+getPort() : int <<setter>>+setPort( port : int ) : void LoggingHandler LoggingAcceptor <<constructor>>+LoggingHandler( handle : Handle ) <<constructor>>+LogginHandler() +handleEvent( evt : Event ) : void +init() +logMessage( message : String ) <<constructor>>+LoggingAcceptor( port : int ) <<constructor>>+LoggingAcceptor() +handleEvent( e : Event ) : void +init() Figura 8-4: Estructura de la aplicación concreta del patrón “Reactor” Clases LoggingClient: Cliente encargado de enviar los pedidos de log al servidor. El constructor debe recibir la información necesaria para conectarse con el servidor remoto, es decir, el nombre de host y el puerto. El método log() envía el mensaje especificado mientras que el método close() cierra la conexión. LogginAcceptor: Aceptor de conexiones para el servicio de logging que implementa la interface Acceptor. El método handleEvent() crea y registra un nuevo handler del tipo LoggingHandler sobre el peerHandler. LoggingHandler: Procesador del servicio de logging. El método handleEvent() registra el mensaje recibido. 8.1.8. Análisis La implementación AOP de este patrón tiene poca relevancia ya que los aspectos solamente se utilizaron para la implementación de las interfaces (en vez de usar clases abstractas). Si bien existen características positivas en la implementación, como localidad y reusablidad moderadas, es mérito del diseño OOP y no es una contribución por parte de AOP. A continuación se detalla el análisis de la implementación: 112 Patrones de manejo de eventos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Localidad del código: La localidad lograda con OOP no se pudo mejorar con AOP. El manejo de eventos dentro del Handler y el Acceptor en su totalidad, son componentes propios del patrón que no han podido desacoplarse de la implementación del caso de estudio. Por lo tanto se considera que no hay localidad. Reusabilidad: De la misma forma, la reusabilidad no ha mejorado con la implementación AOP y existe código propio del patrón que debe implementarse cada vez que se aplique a un caso en particular. Independencia: No existe la independencia, ya que el código no asociado al patrón (el método log()) no tiene sentido sin los componentes que lo rodean. Transparencia de composición: El mismo EventHandler puede intervenir en más de un reactor de una manera transparente. 8.1.9. Componente gráfico Los siguientes componentes fueron implementados: LogClient: Cliente del servicio de logging. Permite enviar un mensaje de log a un server en particular. Posee las siguientes propiedades: o hostname: dirección del host donde reside el servidor, en caso de una conexión remota. o port: puerto TCP donde está instalado el servidor, para el caso de una conexión remota. o message: mensaje que se enviará al servidor. El componente posee una acción para enviar el mensaje, denominada log(). Ya que el componente no contiene lógica asociada a la ejecución de la simulación, sólo tiene sentido ejecutarlo en modo interactivo y poder activarlo por medio de la acción. El componente puede operar de forma local o remota. La primera opción se realiza a través de una conexión en el diagrama a un componente LogServer. Para la opción remota, se deben configurar las propiedades hostname y port del servidor remoto. LogServer: Servidor de logging. Permite conexiones con componentes LogClient, en cuyo caso se establece una conexión local. También permite conexiones remotas, via TCP. La historia de los mensajes recibidos la muestra en el compartimiento de estado. El siguiente es un ejemplo que muestra a un servidor conectado localmente a dos clientes: Diego M.S. Erdödy Patrones de manejo de eventos 113 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Figura 8-5: Diagrama ACVF del caso de estudio para el patrón “Reactor” El diagrama corresponde a una simulación en donde se ha invocado la acción del componente logclient dos veces y luego se ha repetido la operación sobre el componente logclient2. En el componente logserver se puede ver la historia de los mensajes recibidos. 114 Patrones de manejo de eventos Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 9. Patrones de seguridad y fiabilidad 9.1. 9.1.1. Watchdog Resumen Un Watchdog es un componente que supervisa el comportamiento de una característica determinada de otro componente. Dicha supervisión tiene como objetivo eliminar errores groseros que pueden ocurrir dentro del sistema, como por ejemplo, el orden de ejecución de diferentes pasos o restricciones de tiempo. 9.1.2. Otras denominaciones Sentinela 9.1.3. Problema Un sistema de tiempo real se podría simplificar en eventos que tienen que ocurrir dentro de un período específico de tiempo, fuera del cual dichos eventos no tienen relevancia o sentido. En estos sistemas, es necesario verificar que el funcionamiento básico del sistema sea el correcto (ya sea en el dominio del tiempo, en la sucesión de eventos o en algún otro criterio) y actuar, aunque sea de manera drástica, en consecuencia. 9.1.4. Solución El fundamento del patrón, consiste en contar con un participante llamado Watchdog, que se encargará de verificar periódicamente el estado de salud del sistema. En caso de detectar una anomalía, procederá con alguna acción predeterminada como puede ser la reinicialización del sistema. En sistemas de tiempo real, las verificaciones que se realizan sobre el sistema, generalmente están relacionadas a una base de tiempo que actúa de referencia. 9.1.5. Caso de estudio El caso de estudio que se ha elegido para este patrón es el de un marcapasos vigilado por un supervisor. El supervisor debe asegurar que los latidos que indique el marcapasos estén en un rango determinado de tiempo, es decir, la distancia entre dos latidos consecutivos no debe superar el límite superior ni estar por debajo del límite inferior. Diego M.S. Erdödy Patrones de seguridad y fiabilidad 115 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 9.1.6. Estructura El siguiente es un diagrama de componentes del patrón: Canal de actuación Transición de procesamiento Tansformación de datos Procesamiento de entrada Procesamiento de salida Origen de datos de actuación Señal de control Actuador Reportar anomalía Watchdog Base de tiempo Figura 9-1: Diagrama de estructura del patrón “Watchdog” El diagrama presenta un caso general de procesamiento en tiempo real a verificar, representado por el canal de actuación y sus componentes. El watchdog recibe por un lado, la información enviada por los componentes de transformación y procesamiento de salida, así como también, una referencia temporal independiente, para poder tomar decisiones en ese dominio. En caso de que el watchdog detecte una anomalía, inmediatamente informará al sistema para que este tome las acciones necesarias, que pueden ser registrar el incidente, apagar o efectuar una verificación de integridad del sistema. Los participantes del patrón son: Canal de Actuación: Canal a través del cual se comunican los distintos compo- 116 nentes que forman parte del circuito de actuación. Dicho circuito comprende tanto las señales de entrada de los sensores, el procesamiento de los datos como así también las señales de salida hacia los componentes de actuación. Actuation Data Source: Representa la entrada de datos al sistema desde los dispositivos de sensado. Procesador de Entrada: Transforma los datos enviados por los dispositivos de sensado en datos que puedan ser comprendidos por el procesador. Actuator: Dispositivo físico que ejecuta una acción sobre el sistema a partir de los datos provistos por el canal de actuación. Procesador de Salida: Encargado de transformar los datos de salida de forma tal que sean correctamente comprendidos por el dispositivo actuador. Procesador de Datos: Procesa los datos recibidos por los sensores, ya sea de manera secuencial (un solo dato permanece en el canal a la vez) o paralela (puede haber múltiples datos en diferente etapa de procesamiento en el canal para un determinado momento). Base de tiempo: Referencia temporal independiente que guía el funcionamiento del Watchdog. Patrones de seguridad y fiabilidad Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 9.1.7. Watchdog: Espera por una señal periódica enviada por los componentes del canal de actuación. Si dicha señal no es recibida dentro del tiempo esperado, debe indicar la anomalía al canal. Estrategia de implementación Orientada a Aspectos El patrón se divide en una capa reusable o núcleo y la aplicación al caso de estudio correspondiente. NÚCLEO El núcleo consta de un aspecto abstracto que contiene la lógica reusable del patrón y dos interfaces que definen los roles del patrón. El siguiente es el diagrama de estructura y la descripción de los elementos: <<aspect>> WatchDogProtocol #wd : WatchDog #sendSignal( r : Resetable ) : void #init( r : Resetable ) : void +setWatchDog( watchdog : WatchDog ) : void <<advice>> <<base>>+after returning( r : Resetable ){base = watchableCall(r)} <<advice>> <<base>>+before( r : Resetable ){base = initCall(r)} <<pointcut>>+initCall( resetab le : Resetab le ) <<pointcut>>+watchab leCall( r : Resetab le ) WatchDog +init() : void +processSignal( r : Resetab le ) : void Resetable +reset() : void Figura 9-2: Estructura del Núcleo del patrón “Watchdog” Interfaces WatchDog: Definición de la interface de un watchdog. Posee un método init() para realizar tareas de inicialización y el método principal processSignal() que será llamado cada vez que la señal que supervisa sea activada. Resetable: Indica que un componente puede ser reiniciado. Esto se realiza a través del método reset(). Aspectos WatchDogProtocol: Aspecto abstracto que posee el comportamiento generalizable del patrón. Posee un atributo que almacena el Watchdog a utilizar y un método setWatchdog Diego M.S. Erdödy Patrones de seguridad y fiabilidad 117 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA para asignarlo. Define dos pointcuts abstractos, initCall(Resetable) y watchableCall(Resetable). El primero permite especificar cuál será el momento de inicialización del componente a supervisar para que el watchdog pueda hacer alguna tarea de inicialización acorde. El segundo permite especificar cuál será el punto a supervisar dentro del componente. También posee un advice asociado a cada uno de los pointcuts abstractos. El primer advice indica que antes de llamar al pointcut initCall, se llame al método protegido init() del aspecto. Este llama al método init() del watchDog. El segundo advice indica que luego de terminar con el pointcut watchableCall(), se llame al método protegido sendSignal(), el cual llama al método processSignal() del watchDog. APLICACIÓN CONCRETA al CASO DE ESTUDIO La aplicación del patrón se lleva a cabo con la implementación de las interfaces provistas para la definición de los roles y la especialización del aspecto abstracto, que define los valores para los dos pointcuts. También se proveen los objetos que representan a los participantes del caso de estudio, el Watchdog y el Pacemaker. El diagrama de estructura y la descripción es el siguiente: WatchDog +init() : void +processSignal( r : Resetab le ) : void TimerWatchdog <<aspect>> <<constructor>>+TimerWatchdog() <<getter>>+getMaxLevel() : long <<setter>>+setMaxLevel( maxLevel : long ) : void <<getter>>+getMinLevel() : long <<setter>>+setMinLevel( minLevel : long ) : void <<getter>>+getLastInterval() : long +init() : void +processSignal( r : Resetable ) : void WatchDogProtocol <<instantiation>> <<aspect>> PacemakerWatchDogSignal {instantiation = pertarget(initCall(Resetable))} Pacemaker #markBeat() : void +reset() : void +run() : void +toString() : String +init() : void +stop() : void <<getter>>+getBeatInterval() : long <<setter>>+setBeatInterval( interval : long ) : void <<getter>>+getBeatLength() : long <<setter>>+setBeatLength( beatLength : long ) : void <<base>> <<pointcut>>+initCall( resetable : Resetable ){base = execution (void Pacemaker+.init()) && target(r)} <<base>> <<pointcut>>+watchableCall( r : Resetable ){base = execution (void Pacemaker+.markBeat()) && target(r)} <<declare parents>> Pacemaker implements Resetable Resetable +reset() : void Figura 9-3: Estructura de la aplicación del patrón “Watchdog” al caso de estudio Clases Pacemaker: Representación de un marcapasos. Posee un método para inicializar el marcapasos llamado init() y uno para reiniciarlo, en caso de alguna falla, llamado reset(). El método run() pone en funcionamiento la actividad del marcapasos, mientras que el método stop() lo detiene. Permite configurar tanto el tiempo medio de cada latido, como así también el intervalo entre latidos, a través de los getters y setters de las propiedades bea- 118 Patrones de seguridad y fiabilidad Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA tLength y beatInterval. Por último, el método markBeat() representa la funcionalidad del lati- do en sí mismo. TimerWatchdog: Implementación de la interface WatchDog. Se basa en la revisión del tiempo de los intervalos delimitados por cada señal. Si en algún caso, dicho intervalo se encuentra fuera de los límites prefijados, entonces se dispara la reinicialización del componente. Posee dos propiedades que representan el límite mínimo (minLevel) y el máximo (maxLevel) de tolerancia. También posee un método para obtener el intervalo medido entre las dos últimas señales llamado getLastInterval(). Aspectos PacemakerWatchDogSignal: Aspecto concreto que extiende de WatchDogProtocol y configura la aplicación del patrón. Por un lado hace que Pacemaker implemente la interface Resetable. Por otro lado, especifica que el pointcut a supervisar será la llamada al método markBeat() de Pacemaker o cualquier clase derivada y que el pointcut de inicialización será el método init() del mismo conjunto de clases. Por último, se especifica que el modo de instanciación del aspecto sea “por target” dado que el Watchdog a utilizar es único. De no ser así, el mismo watchdog se haría cargo de todas las aplicaciones del patrón dentro de la misma JVM, sin la posibilidad de configurar cada uno por separado. 9.1.8. Análisis De los diagramas de estructura, se desprende que el patrón ha podido modularizarse de forma completa, ya que en la aplicación del patrón sólo se definen puntos de inserción. A continuación se analizan las características de la implementación: Localidad del código: Hay localidad completa de la lógica inherente al patrón en el aspecto abstracto. Reusabilidad: Es total dado que sólo hace falta indicar los roles y pointcuts para poder aplicar el patrón. Independencia: El rol definido como elemento a observar sólo requiere que implemente la interface Resetable. Dado que este comportamiento puede ser insertado por medio de un aspecto, el objeto que debe implementar el rol se independiza de manera completa del patrón. Transparencia de composición: Varias aplicaciones del patrón pueden convivir transparentemente dentro del mismo componente ya que no tiene requerimiento alguno. 9.1.9. Componente gráfico Para este caso se han implementado dos componentes gráficos. El primero, llamado “Pacemaker” representa un marcapasos. Este tiene dos propiedades configurables, la duración de cada latido y el intervalo entre latidos, ambos expresados en milisegundos. El segundo, simula un Watchdog y se puede vincular al marcapasos que se quiera controlar. Tiene como propiedades configurables, los límites de tiempo máximo y mínimo tolera- Diego M.S. Erdödy Patrones de seguridad y fiabilidad 119 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA bles entre latidos. El siguiente es un diagrama que muestra a ambos funcionando en una simulación: Figura 9-4: Simulación de patrón Watchdog Los latidos del marcapasos se pueden visualizar en la zona de texto del componente mientras que el Watchdog indica el tiempo que ha pasado desde la detección del último latido. En caso de que este tiempo esté fuera de los límites prefijados, el Watchdog procede a reinicializar el componente controlado. Vale aclarar que un marcapasos puede ser controlado por más de un Watchdog a la vez, lo que deja en claro el alto nivel de transparencia de composición que presenta el patrón. Figura 9-5: Propiedades de los componentes gráficos Pacemaker y Watchdog La simulación permite visualizar la interacción entre ambos componentes. Para el caso del marcapasos, el compartimiento de estado muestra un mensaje cada vez que se marca un latido, por el tiempo que dure. Dichos valores dependerán de la configuración 120 Patrones de seguridad y fiabilidad Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA del componente. El Watchdog, por su parte, muestra en el compartimiento de estado, la duración entre los últimos dos latidos. También muestra si se ha salido de los parámetros configurados y qué acción se ha tomado en caso afirmativo. Diego M.S. Erdödy Patrones de seguridad y fiabilidad 121 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 10. Resultados y Conclusiones La siguiente es una tabla comparativa de los resultados obtenidos luego del análisis de los distintos patrones de diseño: Nombre del patrón Localidad Propiedades de modularidad TransparenIndeReusabilidad cia de com- pendenposición cia Tipos de Roles Únicos Balking Sí Sí Sí Sí - Observador Sí Sí Sí Sí - Rendezvous Sí Sí Sí Sí Rendezvous Watchdog Sí Sí Sí Sí Watchdog Optimistic Alta4 Alta Sí Alta No No Sí No Reactor OptimisticController Reactor, Acceptor, Handler Superpuestos BalkingClient, BalkingComponent Publisher, Subscriber RendezvousMember WatchdogComponent Client, Resource EventHandler En la primera columna se listan todos los patrones de diseño analizados. Luego le sigue el listado de las cuatro propiedades que han sido analizadas y por último una separación de los roles de cada patrón según su tipo. Al igual que en el trabajo hecho sobre los patrones GoF [Han 02], se han dividido los roles que componen cada patrón en dos tipos y a continuación se verá qué relación existe entre la cantidad de roles de cada tipo por patrón y la capacidad de cumplir con cada una de las características analizadas. Los roles únicos son los roles del patrón que son implementados por un componente que no tiene funcionalidad más allá del rol que le es asignado. Esto quiere decir que sólo existe para cumplir el rol dentro del patrón y, por lo tanto, es definido por él. En cambio, los roles superpuestos son los roles cuyos componentes comparten la funcionalidad con roles fuera del patrón. Por ejemplo, el rol de Sujeto, en el patrón Observador, es un rol superpuesto ya que quien lo implemente, siempre va a cumplir uno o más roles adicionales. Si no fuera así, no habría nada que observar. Por otro lado, la implementación del rol Rendezvous en el patrón del mismo nombre, sólo cumple esa función, no tiene ninguna responsabilidad fuera del patrón. En el trabajo de Hannemann [Han 02], se dividen los patrones en tres grupos según los tipos de roles que posean: (a) los que sólo poseen roles superpuestos, (b) los que poseen roles de ambos tipos y por último, (c) los que únicamente poseen roles únicos. En este trabajo no se han identificado patrones del grupo c, aunque el más cercano por cantidad de roles únicos, es el patrón Reactor. La clasificación “Alta” indica que faltaron algunos detalles para lograr el objetivo de la propiedad. No se intenta utilizar una escala de graduación para cada propiedad. Sólo si se cumplió o no, con la salvedad de la calificación “Alta” para identificar casos cercanos al Sí. 4 Diego M.S. Erdödy Resultados y Conclusiones 123 Generación automática de redes neuronales con ajuste de parámetros basado en algoritmos genéticos Los resultados obtenidos para los patrones analizados es similar al encontrado por Hannemann. Para los patrones del grupo (a), todas las propiedades se cumplen completamente. Esto indica que la modularización ha sido total. Para el grupo (b), el resultado varía levemente en algunos patrones, en nuestro caso para el patrón Optimista. Si bien el grado de modularidad es elevado, existen detalles que no han podido abstraerse. Por último, para el grupo (c), la utilidad de la aplicación de aspectos es limitada o nula. El patrón Reactor se puede tomar como ejemplo de este caso. 124 Resultados y Conclusiones Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA 11. Futuros trabajos El presente trabajo se realizó con una implementación de AOP “genérica” o de propósito general, como lo es AspectJ. Para la aplicación en patrones concurrentes y distribuidos especialmente, sería más adecuada la utilización de una implementación orientada específicamente a un entorno distribuido. Al comienzo del trabajo se analizó la herramienta DJCutter, pero fue descartada por no contar con el nivel de madurez, soporte y documentación suficiente. DJCutter [Mug 03], es una herramienta pensada para la aplicación de AOP en entornos distribuidos. DJCutter es una extensión al lenguaje AspectJ que permite declarar pointcuts distribuidos, es decir, que capture joinpoints de JVMs situados en máquinas diversas. Para dicha tarea provee de agentes que se encargan de la sincronización que deben correr en cada máquina que participe. En futuros trabajos, se pueden analizar los beneficios de implementar con DJCutter (u otra herramienta de características similares) los patrones revisados en el presente trabajo, para determinar si dicha implementación realmente provee ventajas sobre una implementación de propósito general. Diego M.S. Erdödy Futuros trabajos 125 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Referencias [Ajdt 06] www.eclipse.org/ajdt (2006) [Aks 92] Mehmet Aksit Lodewijk Bergmans and Sinan Vural (1992) An ObjectOriented Language-Database Integration Model: The Composition-Filters Approach, in Proceedings of ECOOP ‟92, Springer-Verlag [Ale 02] Roger Alexander and James Bieman (2002) Challenges of Aspect-oriented Technology, Proc. Internacional Conference of Software Engineering 2002 Workshop on Software Quality, Orlando, Florida, USA [Alt 04] Rubén Altman, Alan Cyment (2004) SetPoint: Un enfoque semántico para la resolución de pointcuts en AOP, Tésis de Licenciatura, Facultad de Ciencias Exactas, UBA [Asj 06] www.eclipse.org/aspectj (2006) [AWerkz 05] http://aspectwerkz.codehaus.org (Noviembre 2005) [Ber 01] Lodewijk Bergmans and Mehmet Aksit (2001) Composing Crosscutting Concerns Using Composition Filters, Communications of the ACM, Oct. 2001/Vol. 44 No. 10, pp. 51-57, ACM Press [Ber 94] Lodewijk Bergmans (1994) The Composition-Filters Object Model, Technical report, Department of Computer Science, University of Twente, Netherlands [Bur 03] Burke Bill, Brock Adrian (2003) Aspect-Oriented Programming and JBoss www.onjava.com/pub/a/onjava/2003/05/28/aop_jboss.html [Cli 96] Marshall P. Cline (1996) The Pros and Cons of Adopting and Applying Design Patterns in the Real World, Communications of the ACM, Oct. 1996/Vol. 39 No. 10, pp. 47-49, ACM Press [Coh 04] Tal Cohen and Joseph (Yossi) Gil (2004) AspectJ2EE = AOP + J2EE, in the 8th European Conference on Object-Oriented Programming (ECOOP 2004). [Dou 02] Bruce Powel Douglass (2002) Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems, Addison-Wesley [Fow 04] Fowler M. (2004) Inversion of Control Containers and the Dependency Injection pattern, www.martinfowler.com/articles/injection.html [Gam 93] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1993) Design Patterns: Abstraction and Reuse of Object-Oriented Design, in Proceedings of the 7th European Conference on Object-Oriented Programming (ECOOP 2003), pp. 406-431, Kaiserslautern, Alemania. [Gam 95] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1995) Design Patterns: Elements of reusable Object-Oriented Software, AddisonWesley [Gef 06] www.eclipse.org/gef/ (2006) [Gil 05] Joseph Gil, Itay Maman (2005) Micro Patterns in Java Code, in Proceedings of the 20th annual ACM SIGPLAN conference on Object oriented programming, systems, languages, and applications, San Diego, CA, USA, pp 97 – 116, ACM Press [Han 02] Hannemann J., -Kiczales G. (2002) Design Pattern Implementation in Java and AspectJ, Proceedings of the 17th ACM SIGPLAN conference on Object- Diego M.S. Erdödy Referencias 127 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA oriented programming, systems, languages, and applications, pp. 161-173, ACM Press [Hur 95] Walter L. Hürsch and Cristina Videira Lopes (1995) Separation of Concerns, Technical report by the College of Computer Science, Northeastern University, Boston MA [JBA 06] labs.jboss.com/portal/jbossaop (2006) [JBS 06] jboss.com (2006) [JHD 06] www.jhotdraw.org (2006) [Jim 98] Ricardo Jiménez-Peris, Marta Patiño-Martínez y Sergio Arévalo (2002) Multithreaded Rendezvous: A Design Pattern for Distributed Rendezvous, Universidad Politécnica de Madrid, Facultad de Informática, España [Joh 97] Ralph E. Jonson (1997) Components, Frameworks, Patterns, Proceedings of the 1997 Symposium on Software reusability, Boston, Massachusetts, United Status, pp 10-17, ACM Press [JSR 163] jcp.org/en/jsr/detail?id=163 (2006) [Ker 05] Mik Kersten (2005), AOP@Work: AOP tools comparison, part 1, www.ibm.com/developerworks/java/library/j-aopwork1 [Kic 01] Gregor Kiczales et al. (2001) An Overview of AspectJ, Lecture Notes In Computer Science; Vol. 2072, Proceedings of the 15th European Conference on Object-Oriented Programming, pp 327 – 353, Springer-Verlag [Kic 91] Gregor Kiczales, Jim Des Rivieres, Daniel G. Bobrow (1991) The Art of the Metaobject Protocol, MIT Press [Kic 92] Gregor Kiczales (1992) Towards a New Model of Abstraction in Software Engineering, en Proceedings of the IMSA'92 Workshop on Reflection and Metalevel Architectures [Kic 97] Gregor Kiczales et al. (1997) Aspect-Oriented Programming, in Proceedings of the European Conference on Object-Oriented Programming (ECOOP), Finland. Springer-Verlag [Koj 03] Sergei Kojarski, Karl Lieberherr, David H. Lorenz, Robert Hirschfeld (2003) Aspectual Reflection, Proceedings of the AOSD 2003 Workshop on Software-engineering Properties of Languages for Aspect Technologies, March 17-21, 2003, Boston, Massachusetts [Kop 04] Christian Koppen and Maximilian Stoerzer (2004) PCDiff: Attacking the Fragile Pointcut Problem, in European Interactive Workshop on Aspects in Software, Berlin, Germany, September 2004. [Kru 92] Charles W. Krueger (1992) Software reuse, ACM Computing Surveys, Junio 1992, pp. 131-183, ACM Press [Lie 96] Karl Lieberherr (1996) Adaptive Object-Oriented Software The Demeter Method, PWS Publishing Company [Mez 03] Mira Mezini, Klaus Ostermann (2003) Conquering aspects with Caesar, Proceedings of the 2nd international conference on Aspect-oriented software development, Boston, Massachusetts, pp. 90-99, ACM Press [Mug 03] Muga Nishizawa, Shigeru Chiba and Michiaki Tatsubori (2003) Remote Pointcut - A Language Construct for Distributed AOP, Proceedings of the 3rd international conference on Aspect-oriented software development, pp. 7-15, ACM Press 128 Referencias Diego M.S. Erdödy TESIS DE GRADO EN INGENIERÍA INFORMÁTICA [OWL 06] www.w3.org/2004/OWL/ (2006) [Ram 03] Ramnivas Laddad (2003), AspectJ in Action, Manning [Rus 04] Miles Russell (2004) An Introduction to Aspect-Oriented Programming with the Spring Framework www.onjava.com/pub/a/onjava/2004/07/14/springaop.html [Sch 00] Douglas C. Schmidt, Michael Stal, Hans Rohnert and Frank Buschmann (2000) Pattern-Oriented Software Architecture, Patterns for Concurrent and Networked Objects, Volume 2, Wiley & Sons [Sch 03] Douglas C. Schmidt, Frank Buschmann (2003) Patterns, frameworks, and middleware: their synergistic relationships, en Proceedings of the 25th International Conference on Software Engineering, Portland, Oregon, pp. 694 – 704, IEEE Computer Society [Sch 95] Douglas C. Schmidt (1995) Reactor: An Object Behavioral Pattern for Concurrent Event Demultiplexing and Dispatching, Pattern languages of program design, pp. 529 – 545, ACM Press/Addison-Wesley Publishing Co. [Ste 02] Dominik Stein, Stefan Hanenberg, and Rainer Unland (2002) Designing Aspect-Oriented Crosscutting in UML, In AOSD-UML Workshop at AOSD '02, Enschede, The Netherlands, Apr. 2002 [Sul 01] Gregory T. Sullivan (2001) Aspect-Oriented Programming Using Reflection and Metaobject Protocols, Communications of the ACM, Oct. 2001/Vol. 44 No. 10, pp. 95-97, ACM Press [UML 06] www.uml.org (2006) [XDoc 06] xdoclet.sourceforge.net (2006) Diego M.S. Erdödy Referencias 129 TESIS DE GRADO EN INGENIERÍA INFORMÁTICA Glosario AJDT: AspectJ Development Tool AOP: Aspect Oriented Programming (Programación Orientada a Aspectos) AOSD: Aspect Oriented Software Development (Desarrollo de Software Orientado a Aspectos) AWT: Abstract Window Toolkit. Biblioteca gráfica original de Java. C3: Cross-Cutting Concern (Interés Transversal) CJR: Core Java Reflection ACVF: Aspectual Component Visualization Framework EditPart: Elemento integrante de la capa de controlador perteneciente al modelo MVS de GEF GEF: Graphical Editing Framework GoF: Gang of Four. Acrónimo con el que se reconoce a los cuatro autores de [Gam 95]. GUI: Graphic User Interface (Interface Gráfica de Usuario) IoC: Inversion of Control (Inversión de Control) MOP: Meta-Object Protocols (Protocolo de Meta-objetos) MVC: Patrón de diseño Model-View-Controller POA: ver AOP POO: Programación Orientada a Objetos POP: Procedural Oriented Programming (Programación Orientada a Procedimientos) SoC: Separation of Concerns (Separación de Intereses) SWT: Standard Widget Toolkit UML: Unified Modeling Language (Lenguaje de Modelado Unificado) Diego M.S. Erdödy Glosario 131