Task Scheduler Se necesita modelar una aplicación que permita definir tareas y ejecutarlas en forma programada. Las tareas pueden ser: La ejecución de programa cualquiera o comando del sistema operativo, para lo cual se especifica la línea de comando y una colección de parámetros. Un Stored Procedure de cualquier base de datos, para lo que se necesita la información de conexión a la base (ip, puerto, usuario, password) y el comando sql a ejecutar. Un proceso Java escrito especialmente para este TaskScheduler, que permite extender el comportamiento arbitrariamente. Especificar las condiciones que debe cumplir este proceso para poder ser ejecutado por el TaskScheduler y también la información que debe contener la definición de la tarea. Las tareas se pueden programar individualmente para su ejecución u organizarlas en grupos que ejecutan varias tareas secuencialmente. Cada tarea dentro de un grupo puede ser una tarea simple u otro grupo. Por ejemplo: el proceso central de facturación se conforma de la siguiente manera: Para la ejecución programada de las tareas o grupos, periódicamente se corre un proceso que debe seleccionar y ejecutar las tareas o grupos que corresponda. Sólo se puede programar las tareas o grupos raíz, las tareas dependientes no es necesario programarlas porque se ejecutan dentro de su grupo.
Nota: Se pide modelar el proceso que selecciona y ejecuta las tareas y no el llamado periódico a ese proceso. Sólo indicar cuál es el proceso que debe llamarse periódicamente, como un caso de uso más. Las tareas se programan para su ejecución según diferentes criterios: Diariamente a una hora determinada. Cada una determinada cantidad de minutos. Una sola vez en un día y hora determinados. Al finalizar la tarea el resultado puede ser: Ok BONUS: Finalizado con mensaje de advertencia Error El usuario puede configurar el grupo de tareas de manera de que cuando ejecute el Proceso Central de Facturación: Se ejecuten todas las tareas independientemente de las fallas. Corte ante cualquier error o advertencia. BONUS: Corte ante cualquier error (pero no ante una advertencia) Corte si se produce un error en la primera tarea (ignorando los errores en las tareas subsiguientes). Si se corta la ejecución de un grupo por un error, el resultado del grupo será un error. Si es por una advertencia el grupo tendrá la misma advertencia como resultado. Se pide: ) Identificar los casos de uso de la aplicación (los requerimientos destacados). 2) Identificar qué objetos son los responsables de resolver cada caso de uso (debe quedar en claro los objetos, sus responsabilidades y sus relaciones con los otros objetos que componen la solución). Aclaración: no alcanza con decir qué objeto, tienen que decir qué mensaje recibe, y qué parámetros. 3) Codifique los casos de uso más importantes. 4) En base a lo desarrollado en el punto 3, indicar dónde ocurren situaciones de excepción y cómo se manejan (máximo 0 líneas, relacionarlo con el código escrito). 5) Explicar las principales decisiones de diseño, qué alternativas consideró y qué elementos tuvo en cuenta para llegar a la solución final (máximo 0 líneas). 6) Definir claramente cómo interactuar con otros sistemas o componentes (por ejemplo las tareas definidas por terceros, sólo debe indicarlo en el código escrito en el punto 3). 7) Mostrar un ejemplo de prueba que demuestre que la solución presentada resuelve adecuadamente el problema a través de un diagrama de objetos o un caso de prueba. Ejemplo: queremos eliminar todos los días a las 22 hs. el directorio public a través del comando rm rf del sistema operativo. 2
Solución propuesta Diagrama de clases general TaskScheduler + tic() : void + add() : void Programada UnicaVez Diaria CadaNMinutos + ultimaejecucion() : Date Compuesta ComandoSO StoredProcedure RuntimeException «interface» EstrategiaEjecucion Falla Error Advertencia CortarPorErrorOWarning CortarPorError EjecutarTodas CortarPorErrorPrimera 3
Requerimientos principales del enunciado A) Definir la interfaz de una tarea para un proceso Java B) Definir tareas / grupos de tareas C) Disparar la ejecución de tareas programadas en forma periódica D) Programar la ejecución de las tareas Diaria Cada n minutos Por única vez E) Manejar los eventos que ocurren cuando se ejecuta una tarea. Para esto tenemos que: configurar el comportamiento del grupo de tareas ante errores o advertencias (sólo al grupo de tareas). Cómo resolver cada requerimiento A) Para implementar un proceso ejecutable por el Task Scheduler extender de implementando el método ejecutar. En caso de ser necesario se podría modificar a: cd Alternativ a - Proceso Jav a Usuario «interface» Ejecutable <<Usuario>> this.ejecutable.ejecutar(); En ese caso aquel que quiera agregar una tarea propia debería implementar la interfaz Ejecutable. B) Las tareas agrupadas tienen como abstracción la clase Compuesta, que son polimórficas con las tareas simples en el caso de ejecutarse, conocer la última ejecución, etc. No obstante la tarea compuesta tiene comportamiento diferencial que no debería estar incluido en (no es una buena decisión que todas las tareas tengan una colección de subtareas, en especial para el punto E). 4
+ ultimaejecucion() : Date Compuesta C) Para la ejecución periódica se debe llamar al método tic() del TaskScheduler: public void tic() { for (Programada tareaprogramada : this.tareasprogramadas) { tareaprogramada.ejecutar(); D) La responsabilidad de saber si la tarea debe ejecutarse en este momento está delegada en la Programada): UnicaVez TaskScheduler + tic() : void + add() : void Programada Diaria CadaNMinutos if (this.correspondeejecutar()) { this.tarea.ejecutar(); Como ejemplo implementamos la tarea que corre cada n minutos: public boolean correspondeejecutar() { return (new Date().minutosDesde(this.tarea.ultimaEjecucion()) > this.minutos); Como necesitamos saber la última vez que se ejecutó una tarea, podemos poner esa responsabilidad: 5
En programada En Lo que hay que hacer entonces es: ) ejecutar la tarea, 2) registrar de alguna manera que esa tarea fue ejecutada. Dos opciones: Hacemos que Programada dispare la actualización en el método ejecutar: if (this.correspondeejecutar()) { this.tarea.ejecutar(); this.tarea.registrarejecucion(); O bien hacemos que el ejecutar de registre la ejecución. Para eso tenemos que separar a) la ejecución de la tarea propiamente dicha y doejecutar() b) disparar la ejecución + registrar ejecución. registrarejecucion() En this.doejecutar(); this.registrarejecucion(); lo implementan las subclases de Qué pasa si una tarea falla? Cómo nos enteramos? El método ejecutar() (o el doejecutar()) no devuelven int, sino una excepción. Tanto el error como la advertencia se modelan como excepciones que heredan de Falla (excepción no chequeada, para no tener que ensuciar el contrato de los métodos ejecutar() de las tareas, las tareas programadas y el task scheduler). + ultimaejecucion() : Date RuntimeException Error Falla Adv ertencia E) Manejo de eventos que ocurren al ejecutar un grupo de tareas 6
Recordemos el diagrama de clases: + ultimaejecucion() : Date Compuesta «interface» EstrategiaEjecucion CortarPorErrorOWarning CortarPorError EjecutarTodas CortarPorErrorPrimera En el caso de las tareas compuestas, tenemos que atrapar las excepciones por falla en base al comportamiento que va a tener el grupo. Implementamos el ejecutar: this.estrategia.ejecutar(this.tareas); El caso más sencillo es cuando queremos ejecutar todas las tareas independientemente de que den o no error: public void ejecutar(list tareas) { for ( tarea : tareas) { try { tarea.ejecutar(); catch (Falla f) { // Ignorar, uno de los pocos casos en el que está bien no hacer nada 7
Si hay que verificar que la primera tarea no falle: public void ejecutar(list tareas) { Iterator<> it = tareas.iterator(); it.next().ejecutar(); while (it.hasnext()) { try { it.next().ejecutar(); catch (Falla f) { // Ignorar Recordamos el diagrama de clases para mostrar la relación entre Falla, Error y Advertencia: RuntimeException Error Falla Adv ertencia El resto de los casos los dejamos para el lector. Casos de uso (Punto + 2) Agregar una tarea responsabilidad del TaskScheduler, método: add(programada tareaprogramada) Ejecutar las tareas que corresponden a un momento dado responsabilidad del TaskScheduler, método: tic(). Interfaces del sistema (Punto 6) s definidas por terceros: resuelta en punto A de Requerimientos También define interacciones con tareas que se ejecutan con línea de comando y stored procedures (se utilizan APIs de Java: Runtime y JDBC). Los parámetros se especifican en un paso anterior para poder definir la interfaz ejecutar() que es polimórfica con la definida por terceros. Ejecución de Rm (punto 7) rm = new ComandoSO("rm -rf /public"); Programada rm22hs = new ProgramadaDiaria(rm, 22, 0, 0); scheduler.add(rm00hs); 8