Documentacion Final
Documentacion Final
Autores
Ana Belén Pelegrina Ortiz
Carlos Rodríguez Domínguez
Índice de ilustraciones
Ilustración 1: Asociaciones Tiempo/Lugar para Groupware................................................................4
Ilustración 2: Modelo de la Metodología AMENITIES.....................................................................12
Ilustración 3: Implementación típica de una aplicación RMI. Desde Java 2 SDK, Standard Edition,
v1.2 no hay necesidad de Skeleton.....................................................................................................22
Ilustración 4: Capas en Jini.................................................................................................................25
Ilustración 5: Arquitectura de una aplicación que usa JavaSpaces....................................................34
Ilustración 6: Casos de uso para el subsistema de documentos..........................................................49
Ilustración 7: Diagrama de casos de uso para el subsistema Plugin...................................................49
Ilustración 8: Diagrama de casos de uso para el subsistema Usuarios...............................................49
Ilustración 9: Diagrama de clases estáticos........................................................................................53
Ilustración 10: Diagrama de colaboración para la operación abrir documento..................................60
Ilustración 11: Diagrama de colaboración para la operación realizarAnotacion................................60
Ilustración 12: Diagrama de colaboración para la operación eliminarAnotación..............................61
Ilustración 13: Diagrama de colaboración para la operación imprimir..............................................61
Ilustración 14: Diagrama de colaboración para la operación guardarDocumento.............................61
Ilustración 15: Diagrama de colaboración para la operación subirDocumento..................................62
Ilustración 16: Diagrama de colaboración para la operación crearCarpeta........................................62
Índice de tablas
Tabla 3.1.: Arquitectura en capas de RMI..........................................................................................24
Tabla 3.2: Operaciones básias en un espacio de tuplas......................................................................31
Tabla 4.1: Resumen de los casos de uso.............................................................................................48
Tablas 5.1: Contratos del sistema.......................................................................................................59
Tabla 7.1: Métodos públicos de la clase ClienteFicheros.................................................................115
Tabla 7.2: Métodos públicos de la clase abstracta Figura................................................................139
Tabla 8.1: Clases captadoras y eventos asociados............................................................................182
Tabla 8.2: Eventos disponibles para DJTreeEvent DJListEvent......................................................183
Tabla 8.3: Eventos disponibles para DJComboBoxEvent................................................................185
Tabla 8.4: Eventos disponibles para DJButtonEvent........................................................................187
Tabla 8.5: Eventos disponibles para DJToggleButtonEvent............................................................189
Tabla 8.6: Eventos disponibles para DJTreeEvent DJCheckBoxEvent............................................190
Tabla 8.7: Eventos disponibles para DJTextFieldEvent...................................................................192
Tabla 8.8: Eventos disponibles para DJTreeEvent...........................................................................194
Tabla 8.9: Tipos de evento para DFileEvent....................................................................................205
Tabla 8.10: Tipos de evento para DJViewerEvent............................................................................208
Tabla 8.11: Tipos de evento par DJLienzoEvent..............................................................................210
Tabla 8.12: Aspecto de los componentes gráficos según sus permisos............................................212
Tabla 10.1: Especificación detallada de los casos de uso.................................................................251
Veamos, punto por punto, y de forma muy general, la solución adoptada para cada uno de
los objetivos marcados para la obtención de las características anteriores.
Para solucionar el hecho de que el sistema sea extensible, y por tanto se pueda modificar
para la resolución de una tarea específica, siendo por tanto reutilizable, se implementa un
mecanismo de plug-ins. Mediante el marco de trabajo, se permite la implementación de
plug-ins con suma facilidad: Tan solo se deberán implementar tres métodos para que
cualquier aplicación existente o de nueva implementación sea un plug-in en el sistema.
Hay que destacar el hecho de que los plug-ins pueden interoperar entre sí mediante
eventos. De hecho, el plug-in dedicado al chat permite realizar videoconferencias,
haciendo uso para ello del plug-in de videoconferencia y de un conjunto de eventos que lo
inician.
Para completar el tema de los plug-ins, hay que destacar el hecho de que también se
proporcionan abstracciones para la realización de plug-ins que realicen cálculos paralelos
siguiendo el modelo maestro-esclavo. Se dan abstracciones sobre el maestro, los
esclavos, las tareas y los resultados.
Para que se pueda realizar un uso lo más sencillo posible de cara al desarrollador del
sistema de los eventos y demás abstracciones, se han implementado componentes
gráficos cuyos eventos son distribuidos. Se aportan componentes simples como el botón,
la lista, el cuadro de texto, la etiqueta, etc. y otros más complejos, creados a partir de los
anteriores, como un chat, un árbol de usuarios bajo un cierto rol, un árbol con los
documentos del espacio de trabajo, etc. Todos éstos componentes siguen el modelo de
desarrollo definido por JavaBeans, con lo que su uso se simplifica en entornos de
desarrollo con diseñadores gráficos de GUI's.
En cuanto a los documentos, se guardarán permisos tipo UNIX para cada uno de ellos. En
cada aplicación cliente se mostrarán solo los documentos a los que tenga acceso el
usuario que se haya identificado. El resto permanecerán ocultos. Cualquier cambio en los
permisos desde otra aplicación cliente en ejecución se verá reflejado de forma
instantánea en la interfaz gráfica: Aparecerán documentos si nos dan permisos de lectura,
desaparecerán si nos los quitan, si estamos anotando un documento y nos dan o nos
quitan permisos de escritura, se habilitará o deshabilitará un botón de guardado, o, si nos
quitan permisos de lectura, se nos cerrará el sistema de anotación, etc. En cuanto a los
permisos sobre los componentes gráficos, se nos habilitarán o deshabilitarán según los
permisos definidos para cada usuario y rol en la metainformación del sistema. Cualquier
cambio en ésta se verá reflejado instantáneamente en la visualización de los
componentes, al igual que ocurre con los documentos.
Hay que destacar que la implementación de todo el sistema ha seguido un modelo por
capas. Se han definido tres capas: Una física, otra de modelo y otra de interfaz gráfica de
usuario. Se definen interfaces entre la capa física y la de modelo, y entre ésta última y la
de interfaz gráfica, de tal modo que si deseamos cambiar la implementación de cualquier
capa, si mantenemos la interfaz, no deberemos cambiar la implementación del resto del
sistema. Siempre que se ha podido, en cada subsistema, se ha aplicado el patrón de
diseño fachada (Façade).
Para facilitar el uso de los usuarios finales ante un sistema tan complejo se han seguido
varias pautas: Crear una interfaz gráfica lo más simple y ágil posible, y crear scripts de
instalación e inicialización de aplicaciones y servicios (los necesarios para el correcto uso
de JavaSpaces). Éstos scripts hacen más “amigable” la instalación y operación del
sistema. Así mismo, se proveen una serie de scripts para la configuración automática de
la base de datos MySQL que guardará la metainformación del sistema.
Gracias a la combinación de las diversas tecnologías empleadas, por último, hay que
destacar el hecho de que la aplicación cliente actuará de una forma parecida a un
hardware “plug and play”: Abre la aplicación en una red concreta y automáticamente
obtendrás de ella sus plug-ins, usuarios, roles, documentos, etc. Además, aportarás las
datos que tuvieras con anterioridad a esa red.
Las interacciones colaborativas tienen como principal utilidad que los participantes alteren
el estado de una entidad compartida (Puede considerarse como lo inverso de una
interacción mediante transacción). La entidad compartida tiene una forma relativamente
inestable. Un ejemplo es el desarrollo de una idea, la creación de un diseño o, en general,
la solución de un determinado objetivo compartido. Las tecnologías que deseen
implementar sistemas colaborativos deberán ofertar funcionalidad suficiente como para
que una serie de participantes puedan alcanzar un objetivo común. El manejo o
almacenamiento de documentos, hebras de discusión, o cualquier mecanismo que sea
capaz de capturar los esfuerzos comunes de una serie de usuarios dentro de un sistema
tecnológico debidamente gestionado son tecnologías típicamente asociadas a la
colaboración.
• Awareness. Los usuarios deben tener cierto grado de conocimiento acerca de las
actividades y estado del resto de usuarios.
• Trabajo articulado. Los usuarios que cooperan deben de ser capaces de participar
el trabajo en unidades más pequeñas, distribuir éstas particiones entre ellos y, una
vez terminado el trabajo, reintregrarlo todo de nuevo.
Una de la formas más comunes de conceptualizar los sistemas CSCW es considerar sus
contextos de uso. La matriz CSCW, introducida por Johansen en 1988 [2], considera los
contextos de trabajo en dos dimensiones: La localización de los participantes y si éstos
actúan de forma síncrona o no. Una representación de ésta matriz se muestra a
continuación.
Las aplicaciones groupware, a menudo fracasan porque requieren que parte del personal
realice su trabajo adicional, y esas personas no perciben un beneficio directo del uso de
aplicaciones. En este punto, es importante destacar que el nuevo objetivo de las
aplicaciones groupware, es modelar las actividades organizacionales, por tanto, se debe
establecer el margen adecuado para que todos los participantes se vean involucrados en
el proceso y puedan ser partícipes activos de él. Para esto se debe incorporar, en los
casos que sea necesario, equipos, instrucción y coordinación, para evitar quiebres que
dificulten el término de alguna tarea o proceso. Las aplicaciones groupware fallarán si no
permiten manejar el amplio rango de excepciones que caracterizan gran parte de la
actividad grupal.
Esta observación parece ser muy ambiciosa, ya que trata de abarcar todos los casos
posibles de excepciones. Sin embargo, en la actividad diaria, nuestro quehacer está
sometido a un constante cambio que no se puede prever del todo.
Los principales hitos que se desean agregar al marco de trabajos son los siguientes:
• Por el otro, se pretende que sea el propio framework el que sea extensible.
De esta forma, se podrá ampliar incluyendo nuevos componentes o
agregando nuevas funcionalidades.
• Servicio de e-mail
• Servicio de Videoconferencia
• Usabilidad. Como todo sistema informático que hace uso de interfaces de usuario,
la usabilidad (atributo de calidad que mide lo fáciles que son de usar las interfaces)
en nuestro sistema es clave. Para ello, se pondrá un especial empeño en que los
componentes desarrollados cumplan con una serie de requisitos con un grado de
usabilidad alto.
2.2. Habanero
Habanero está escrito en Java. Esta plataforma o API está diseñada para dar a los
desarrolladores las herramientas necesarias para crear aplicaciones java colaborativas.
Usando el “Habanero Wizard”, los desarrolladores pueden fácilmente convertir applets por
medio de la selección de los objetos y eventos que quieren compartir. Entonces el
“Wizard” rescribe el código para beneficiarse de la API HABANERO.
Por otra parte, Habanero proporciona el entorno necesario para crear grupos de trabajo
colaborativos y comunidades virtuales.
Se compone de un servidor que almacena las sesiones y un cliente que interactúa con
sesiones haciendo uso de una variedad de aplicaciones llamadas Hablets. Las Sesiones
pueden grabarse persistentemente, accederse restringidamente e incluso pueden ser
anónimas.
El cliente de Habanero provee la interfaz para definir, listar, crear, unirse e interactuar con
una sesión:
Los navegadores Web que soportan lenguajes de programación de Internet como Java
permiten actividad en la interfaz del usuario como puede ser los applets. Sin embargo
Java necesita ser integrado con middleware específico para coordinar actividades entre
múltiples clientes distribuidos. Una solución típica consiste en centralizar la coordinación
en algún servidor al que todos los usuarios participantes en una aplicación tienen que
conectarse. De eso modo, la comunicación no será directamente entre los participantes
pero ello no quita que siga siendo una aplicación distribuida.
3.1. Swing
Java Además del paquete java.awt, pone a disposición del programador el paquete
javax.swing para el desarrollo de interfaces gráficas.
Swing ha sido totalmente escrito en Java utilizando el paquete awt, y pone a disposición
del usuario muchas clases que están también en awt, pero mucho mejores y más
potentes. Además introduce muchas más clases que no están en awt.
Los Componentes del interfaz gráfico son Beans y utilizan el modelo de Delegación de
Eventos de Java. Swing proporciona un conjunto completo de Componentes, todos ellos
lightweight, es decir, ya no se usan componentes "peer" dependientes del sistema
operativo, y además, Swing está totalmente escrito en Java. Todo ello redunda en una
mayor funcionalidad en manos del programador, y en la posibilidad de mejorar en gran
medida la cosmética de los interfaces gráficos de usuario.
Son muchas las ventajas que ofrece el uso de Swing. Por ejemplo, la navegación con el
teclado es automática, cualquier aplicación Swing se puede utilizar sin ratón, sin tener que
escribir ni una línea de código adicional. Las etiquetas de información, o "tool tips", se
pueden crear con una sola línea de código. Además, Swing aprovecha la circunstancia de
que sus Componentes no están renderizados sobre la pantalla por el sistema operativo
para soportar lo que llaman "pluggable look and feel" (del que hablaremos a
continuación), es decir, que la apariencia de la aplicación se adapta dinámicamente al
sistema operativo y plataforma en que esté corriendo.
Java utiliza el interfaz gráfico de la plataforma sobre la que se está ejecutando para
presentar los Componentes del AWT (o SWING) con el aspecto asociado a esa
plataforma, de este modo los programas que se ejecuten en Windows tendrán esa
apariencia y los que se ejecuten en Unix tendrán apariencia Motif. Pero Swing permite la
selección de esta apariencia gráfica, independientemente de la plataforma en que se esté
ejecutando; tanto es así que, la apariencia por defecto de los Componentes Swing se
denomina Metal, y es propia de Java. Teniendo siempre en cuenta las restricciones
impuestas por el control de seguridad, se puede seleccionar la apariencia, o Look-and-
Feel de los Componentes Swing invocando al método setLookAndFeel() del objeto
UIManager correspondiente al ejecutable. La forma en que se consigue esto es porque
cada objeto JComponent tiene un objeto ComponentUI correspondiente que realiza
todas las tareas de dibujo, manejo de eventos, control de tamaño, etc. para ese
Jcomponent.
Una de las ventajas que representan las capacidades de Look&Feel incorporadas a Swing
para las empresas, es el poder crear un interfaz gráfico estándar y corporativo. Con el
crecimiento de las intranets se están soportando muchas aplicaciones propias que deben
ejecutarse en varias plataformas, ya que lo más normal es que en una empresa haya
diferentes plataformas. Swing permite ahora que las aplicaciones propias diseñadas para
uso interno de la empresa tengan una apariencia exactamente igual, independientemente
de la plataforma en que se estén ejecutando.
RMI (Java Remote Method Invocation) es un mecanismo ofrecido en Java para invocar
un método remotamente. Al ser RMI parte estándar del entorno de ejecución Java, usarlo
provee un mecanismo simple en una aplicación distribuida que solamente necesita
comunicar servidores codificados para Java. Si se requiere comunicarse con otras
tecnologías debe usarse CORBA o SOAP en lugar de RMI.
Al estar específicamente diseñado para Java, RMI puede darse el lujo de ser muy
amigable para los programadores, proveyendo paso de objetos por referencia (cosa que
no hace SOAP), "recolección de basura" distribuida y pasaje de tipos arbitrarios
(funcionalidad no provista por CORBA).
Por medio de RMI, un programa Java puede exportar un objeto. A partir de esa operación
este objeto está disponible en la red, esperando conexiones en un puerto TCP. Un cliente
puede entonces conectarse e invocar métodos. La invocación consiste en el "marshalling"
de los parámetros (utilizando la funcionalidad de "serialización" que provee Java), luego
se sigue con la invocación del método (cosa que sucede en el servidor). Mientras esto
sucede el llamador se queda esperando por una respuesta. Una vez que termina la
ejecución el valor de retorno (si lo hay) es serializado y enviado al cliente. El código
cliente recibe este valor como si la invocación hubiera sido local.
Ilustración 3: Implementación típica de una aplicación RMI. Desde Java 2 SDK, Standard Edition,
v1.2 no hay necesidad de Skeleton.
Arquitectura
La arquitectura RMI sigue un modelo por capas de acuerdo con el modelo de referencia
de Interconexión de Sistemas Abiertos (OSI, Open System Interconnection). Los
Elementos
Toda aplicación RMI normalmente se descompone en 2 partes:
• Un servidor, que crea algunos objetos remotos, crea referencias para hacerlos
accesibles, y espera a que el cliente los invoque.
Sun Microsystems introduce Jini, basado en tecnología Java, en 1998. El corazón de Jini
es un trío de protocolos: discovery, join y lookup. Un par de estos protocolos, discovery y
join, ocurren cuando se conecta un dispositivo a la red; discovery ocurre cuando un
servicio busca un servicio lookup con quien pueda registrase, y join ocurre cuando un
servicio localiza un servicio lookup y desea suscribirse a este. Lookup ocurre cuando un
cliente o usuario localiza e invoca un servicio descrito por su tipo de interfaz (escrita en el
lenguaje de programación Java) y posiblemente otros atributos. Para que un cliente en
una comunidad Jini use un servicio:
El proveedor de servicio debe localizar un servicio lookup por una petición multicast en la
red local o por conocimiento a priori de un servicio lookup remoto.
Se puede visualizar el servicio Jini lookup como un servicio de directorio. Jini usa tres
protocolos de descubrimiento relacionados. Cuando una aplicación o servicio es recién
activada, el protocolo de petición multicast busca un servicio lookup en la vecindad. Los
servicios lookup utilizan el protocolo de anuncio multicast para anunciar su presencia a
servicios en la comunidad que pudieran estar interesados. El protocolo de descubrimiento
unicast entonces establece comunicación con un específico servicio lookup ya conocido.
Sin embargo, un servicio Jini lookup no es solamente un servidor de nombres. Este traza
las interfaces con que los clientes ven a los objetos proxy servicio. Este también mantiene
atributos de servicio y procesa consultas. Los clientes descargan el proxy servicio que es
usualmente un stub RMI que puede comunicarse con el servidor. Este proxy objeto
permite a clientes usar el servicio sin saber nada acerca de este. Aquí, no son necesarios
los drivers en caso de que el servicio proveído sea un periférico (por ejemplo impresora).
El objeto servicio descargado puede ser el servicio mismo o un objeto inteligente capaz de
conversar en un protocolo de comunicación privado.
Arrendamiento en Jini
Jini concede acceso a sus servicios en base al arrendamiento. Un cliente pude solicitar un
servicio por un determinado periodo de tiempo, y Jini dará un arriendo negociado por ese
periodo. Este arriendo debe ser renovado antes que este expire; sino Jini liberá los
recursos asociados con el servicio. El arrendamiento permite a Jini ser robusto cuando se
enfrenta a fallas abruptas o desconexión de dispositivos y servicios.
Además del servicio básico de descubrimiento y mecanismos join y lookup, Jini soporta
eventos remotos y transacciones que ayudan a programadores a escribir programas
distribuidos en una forma fiable y escalable. Los eventos remotos notifican un objeto
cuando un cambio deseado ocurre en el sistema. Nuevos servicios publicados o algunos
UpnP es una evolución del estándar inicial de Microsoft que extiende el modelo de
periféricos Plug-and-Play. Esto apunta a permitir el anuncio, descubrimiento y control de
dispositivos en red, servicios y electrodomésticos. En UPnP, un dispositivo puede
dinámicamente unirse a una red, obtener una dirección IP, transmitir sus capacidades por
demanda, y conocer la presencia y capacidades de otros dispositivos. Un dispositivo
también puede abandonar una red fácilmente, y automáticamente abandonar el estado de
disponibilidad anterior. UpnP está influenciado por TCP/IP y tecnologías Web, incluyendo
IP, TCP, UDP, HTTP, y XML.
UpnP usa Simple Service Discovery Protocol (SSDP) para el descubrimiento de servicios.
Este protocolo anuncia la presencia de un dispositivo a otros y descubre otros dispositivos
o servicios. Por consiguiente, SSDP es análogo al trío de protocolos en Jini. SSDP usa
http sobre multicast y unicast UDP, conocidos como HTTPMU y HTTPU, respectivamente.
Un dispositivo acoplado envía un mensaje de anuncio (ssdp:alive) multicast para anunciar
sus servicios a puntos de control. Los puntos de control funcionan de forma similar que los
servicios lookup de Jini. Un punto de control, si está presente, puede guardar el anuncio,
u otros dispositivos pueden también ver directamente este mensaje multicast. UpnP
puede trabajar con o sin los puntos de control (servicio lookup). Este envía un mensaje
multicast de búsqueda (ssdp:discover) cuando un nuevo punto de control es agregado a
la red. Cualquier dispositivo que escuche este multicast responderá con un mensaje
unicast de respuesta.
UPnP usa XML para describir las capacidades y características de los dispositivos. El
Luego de que un punto de control descubre un dispositivo, este aprende mas acerca de
cómo usarlo, controlarlo, coordinarse con él por medio de la descripción del archivo XML.
El control es expresado como una colección de objetos de Simple Object Access Protocol
(SOAP) y sus URLs en el archivo XML. Para usar un control específico, un mensaje
SOAP es enviado a un objeto de control SOAP a la URL específica. El dispositivo o
servicio retorna valores específicos de la acción.
Una descripción UPnP para un servicio incluye una lista de acciones a las que el servicio
responderá y una lista de variables que muestran el estado del servicio en tiempo de
ejecución. El servicios publica actualizaciones cuando estas variables cambian, un punto
de control puede suscribirse para recibir esta información. Las actualizaciones son
publicadas enviando mensajes de evento que contienen los nombres y valores de una o
mas variables de estado. Estos mensajes también son expresados en XML y formateados
usando la Arquitectura General de Notificación de Eventos.
UPnP ofrece agregar una descripción de alto nivel de servicios en la forma de una interfaz
de usuario. Esta característica permite que los usuarios controlen directamente el servicio.
Si un dispositivo o servicio tiene una URL de presentación, entonces el punto de control
puede recuperar una página desde esta URL, cargar la página en un navegador web y
(dependiendo de las capacidades de la página) permite al usuario controlar el dispositivo
o ver el estado de él.
Introducción
El propósito de la computación distribuida es diseñar y construir aplicaciones como un
conjunto de procesos que son distribuidos sobre una red de computadores y trabajar
juntos como una unidad. Esto produce una mayor escalabilidad y fiabilidad en los
sistemas. Estos sistemas son difíciles de construir ya que al existir más de un computador
se requiere de la coordinación entre ellos, tarea nada sencilla.
Aproximaciones:
• Basada en páginas.
• Basada en variables compartidas.
• Basada en Objetos (espacios de tuplas): los procesos en múltiples máquinas
comparten un espacio abstracto que contiene los objetos compartidos.
Espacios de tuplas
Un espacio de tuplas es una implementación del paradigma de la memoria asociativa para
la computación paralela/distribuida. Un espacio es un repositorio compartido a través de
red para objetos o tuplas. Como un espacio de tuplas es global a todo el sistema, los
procesos en cualquier máquina pueden insertar tuplas o quitarlas concurrentemente. Una
tupla es una estructura de datos con varios campos tipados con algún valor.
Operaciones
Se definen sólo unas pocas operaciones atómicas.
Operación Descripción
Out Inserta una tupla dentro del espacio
In Saca una tupla del espacio
Rd Inspecciona (lee) una tupla en el espacio
sin quitarla.
Tabla 3.2: Operaciones básias en un espacio de tuplas
Las dos últimas operaciones de la tabla usan una plantilla (template) para buscar en un
espacio. Cuando múltiples tuplas emparejan con una plantilla de búsqueda, una tupla
arbitrariamente es devuelta.
Los programas escritos en Linda son procesados por un precompilador que transforma el
programa en un lenguaje de programación nativo. Después, ese programa se puede
compilar con el compilador normal para ese lenguaje. Linda añade predicados variantes
de las operaciones in y rd llamados inp y rdp que son no-bloqueantes. Si ninguna tupla
empareja, se devuelve false.
• Único punto de fallo. Las tuplas están almacenadas sólo en un lugar de modo que
si el computador falla, todas se pierden.
• Cuello de botella.
! Persistencia distribuida.
! Diseño de algoritmos distribuidos.
Los servicios de JavaSpaces usan RMI y las características de serialización de Java para
alcanzar esas metas.
Se puede mostrar interés en un tipo de entradas según una plantilla, de forma que el
Persistencia distribuida
Las implementaciones de la tecnología JavaSpaces proveen un mecanismo para
almacenar un grupo de objetos relacionados y recuperarlos según un patrón de búsqueda
especificado por los campos de una plantilla. Esto permite a un servicio de JavaSpaces
ser usado para almacenar y recuperar objetos en un sistema remoto.
Beneficios
Los servicios JavaSpaces son herramientas para construir protocolos distribuidos, pueden
proveer un sistema de almacenamiento distribuido fiable para objetos y persistencia de
objetos distribuidos facilitando la computación cooperativa.
• Mientras que una BDOO provee una imagen OO de los datos que almacena, los
sistemas JavaSpaces no lo hacen, funcionando estos últimos simplemente como
copias de entradas.
Objetivos y requerimientos
Los objetivos de esta tecnología son:
Operaciones
Existen cuatro tipos de operaciones principales que podemos invocar sobre un servicio de
JavaSpaces: write, read, take, notify.
Entradas
Objetos de la clase Entry, que se almacenan en el espacio para (mediante su intercambio)
ReadIfExists y Read
Ambas formas buscan en un servicio JavaSpaces (espacio) una entrada que empareje
con la plantilla pasada. Si hay un emparejamiento, una copia local es creada, de lo
contrario, se devuelve NULL. Los valores nulos (NULL) en las plantillas suponen el
emparejamiento con cualquier valor en las entradas.
TakeIfExists y Take
Son esencialmente idénticas a las anteriores con la salvedad de retirar (eliminar) del
espacio el objeto que empareje.
Notify (notificación)
Una petición de este tipo registra el interés en la llegada posterior de entradas al espacio
con características que emparejen con una plantilla determinada.
Ordenamiento
Las operaciones sobre un espacio carecen de orden; preservándose sí la atomicidad de
las mismas a la hora de ver los cambios por otras operaciones concurrentes.
Transacciones
La API de JavaSpaces usa el paquete net.jini.core.transaction para proporcionar
transacciones atómicas básicas que agrupen múltiples operaciones a través de múltiples
espacios actuando como una única operación atómica.
Poco a poco los elementos de los que carecía MySQL están siendo incorporados tanto
por desarrollos internos, como por desarrolladores de software libre. Entre las
características disponibles en las últimas versiones se puede destacar:
• Conectividad segura.
• Replicación.
En 2003, investigadores de Sun Labs desarrollaron el PDF Renderer como parte de una
herramienta de audio colaborativo (Sun(TM) Labs Meeting Suite), que es usada de forma
intensiva en las reuniones distribuidas en Sun. Meeting Suite fue desarrollada para
realizar presentaciones creadas con OpenOffice.
PDF Renderer es justo lo que su nombre indica: una librería open source escrita en java
que permite renderizar documentos PDF en la pantalla utilizando Java2D. Típicamente
esto significa dibujar en un JPanel de la biblioteca Swing, pero también puede dibujarse
en otras implementaciones de Graphics2D.
Es importante destacar que PDFRender no genera documentos PDF, sino que permite
visualizarlos.
Las relaciones que se establecen entre los elementos anteriores son la siguientes:
3. El mismo comportamiento del punto anterior de los usuarios con respecto a los
componentes se da para los roles.
Al igual que con los componentes cada fichero tiene asociados unos permisos, que
determinan qué usuarios y/o roles pueden leer/escribir el documento en cuestión.
Así mismo, cada aplicación constará de una serie de plugin, o mini-aplicaciones, que
extenderán la funcionalidad de la aplicación (chat, videoconferencia, pizarra compartida,
cálculo distribuido, etc.). Los plugins podrán ser añadidos, eliminados, desactivados, etc.
por parte del usuario de las aplicaciones cliente. Además, los plugin dispondrán de un
mecanismo de versionado, que permitirá que el sistema distribuya de forma automática
las nuevas versiones de los plugins, dando siempre al usuario la opción de actualizarla el
plugin o no.
Por otro lado, el framework también ofrecerá la posibilidad de desarrollar plugins para las
aplicaciones y gestionar su versionado de forma fácil y simple; más aún, el framework
proporcionará mecanismos de gestión de los plugins, de manera que éstos puedan ser
cargados en el sistema, activados, modificados, etc. Con el uso de los plugins se
conseguirá el objetivo de construir un framework para dar soporte a la implementación de
aplicaciones extensibles.
1. Requisitos de Calidad
2. Pseudo-Requisitos
En la Tabla 4.1: Resumen de los casos de uso se listan los casos de uso hallados para la
aplicación típica que se desarrollará con nuestro Framework.
A partir de los casos de uso descritos en los puntos anteriores, se han identificado las
siguientes operaciones del sistema:
• AbrirDocumento(nombreDocumento)
• realizarAnotacion(Documento, Anotacion, x, y)
• cambiarPagina(Documento, numPagina)
• eliminarAnotacion(Documento, Anotacion)
• imprimirDocumento(Documento)
• guardarDocumento(Documento)
• subirFichero(pathFichero, pathDestino)
• crearCarpeta(nombreCarpeta, carpetaPadre)
• eliminarDocumento(nombreDocumento)
• moverDocumento(Origen, Destino)
• obtenerPropiedades(nombreDocumento)
• cambiarPermisos(nombreDocumento, nuevosPermisos)
• renombrarDocumento(original, nuevoNombre)
• iniciarChat()
• enviarMensaje(Mensaje)
• iniciarChatPrivado(Usuario)
• enviarMensajePrivado(Mensaje, Usuario)
• iniciarVC(Usuario)
• cambiarRol(nuevoRol)
• obtenerListadoPlugins()
• cambiarVisibilidad(Plugin, visible?)
• agregarPlugin(fichero)
• eliminarPlugin(Plugin)
A lo largo de éste documento se recogerán las diferentes entidades extraídas del estudio
detallado de los requisitos impuestos para la aplicación (tanto funcionales como no
funcionales).
Durante el análisis se han extraído los siguientes puntos, relativos a la división en clases
del problema (necesaria debido al uso de un lenguaje orientado a objetos para la
implementación), a los atributos que deberán tener éstas clases y a las relaciones entre
ellas:
• Deberá haber usuarios. Un usuario podrá tener un solo rol en un momento dado,
pero deberá contemplarse que tenga una lista de roles permitidos (para poder
cambiar de rol en cualquier momento).
• Un documento será una colección de páginas, cada una con una serie de
anotaciones. Los documentos deberán soportar permisos y tener un propietario (Un
usuario en un rol).
• Para cada usuario se debe permitir asociar un permiso para cada componente
gráfico.
• Para cada rol se debe permitir asociar un permiso para cada componente gráfico.
En ésta sección del documento se tratará de describir el comportamiento del modelo ante
la interacción de los actores con él. Se describirán una serie de operaciones de gran
importancia en la fase de diseño del sistema y se mostrarán un conjunto de diagramas de
colaboración que permitan obtener una descripción gráfica completa de la interacción de
los actores y las clases entre sí.
Las operaciones aquí descritas se corresponden con aquellas que se han considerado de
gran importancia para la fase de diseño e implementación del sistema. En base, serán
una explicación de las precondiciones y postcondiciones de éstas operaciones, así como
de las responsabilidades de las que se hará cargo el sistema al realizarlas.
Nombre abrirDocumento(nombreDocumento:String)
Responsabilidad Abre un documento para su visualización
Dependencias Ninguna
Nombre imprimirDocumento(d:Documento)
Responsabilidad Imprime un documento
Dependencias Ninguna
Precondición - El documento d debe existir
Postcondición - El documento d se imprime
Nombre guardarDocumento(d:Documento)
Responsabilidad Guarda el estado actual de un documento
Dependencias abrirDocumento
Precondición - El documento d debe estar abierto
- El estado del documento d se guarda de forma
Postcondición
persistente.
crearCarpeta(nombreCarpeta:String,
Nombre
carpetaPadre:String)
Responsabilidad Crea una carpeta en la carpeta padre especificada
Dependencias Ninguna
- carpetaPadre debe ser un destino válido en el sistema
- nombreCarpeta no puede ser un nombre vacío ni repetido
Precondición dentro de la carpeta padre
- Debemos tener permisos suficientes para escribir en la
carpeta padre
Postcondición - La carpeta se crea en la carpeta padre
Nombre eliminarDocumento(nombreDocumento:String)
Responsabilidad Elimina un documento del sistema
Dependencias Ninguna
- nombreDocumento debe hacer referencia a un documento
existente en el sistema
Precondición
- Debemos tener permisos suficientes para eliminar el
documento
Postcondición - El documento se elimina
Nombre obtenerPropiedades(nombreDocumento:String)
Responsabilidad Muestra las propiedades de un documento
Dependencias Ninguna
Precondición - El documento debe existir
Postcondición - Se muestran las propiedades del documento
renombrarDocumento(original:String,
Nombre
nuevoNombre:String)
Responsabilidad Cambia el nombre de un documento
Dependencias Ninguna
- El documento original debe existir
- El nuevo nombre no puede ser vacío ni repetido dentro del
Precondición
directorio padre del documento
- Debemos tener permisos de escritura sobre el documento
Postcondición - El documento cambia de nombre
Nombre iniciarChat()
Responsabilidad Inicia un chat con los usuarios conectados del sistema
Dependencias Ninguna
Precondición Ninguna
- Se abre un canal de comunicaciones entre los usuarios
Postcondición
conectados del sistema
Nombre enviarMensaje(mensaje:String)
Envía un mensaje desde un usuario hasta el resto de
Responsabilidad
usuarios conectados
Dependencias iniciarChat
Precondición - El usuario debe haber abierto el chat previamente
Postcondición - El mensaje se muestra a los usuarios conectados
Nombre iniciarVC(u:Usuario)
Responsabilidad Se inicia una videconferencia con el usuario u
Dependencias Ninguna
Precondición - El usuario u debe estar conectado
Postcondición - Se inicia la videconferencia entre los dos usuarios
Nombre cambiarRol(nuevoRol:Rol)
Responsabilidad Cambia el rol del usuario en el sistema
Dependencias Ninguna
- El nuevo rol debe estar entre los permitidos para el
Precondición
usuario
Postcondición - El usuario cambia su rol en el sistema
Nombre obtenerListadoPlugins()
Responsabilidad Obtiene la lista de todos los plugins del sistema
Dependencias Ninguna
Precondición Ninguna
Postcondición - Se ofrece una lista con todos los plugins del sistema
Nombre agregarPlugin(fichero:String)
Responsabilidad Agrega un nuevo plugin al sistema
Dependencias Ninguna
- El fichero debe contener un plugin válido para el sistema
Precondición
- El fichero debe existir de forma local al usuario
Postcondición - El plugin se agrega al sistema
Nombre eliminarPlugin(p:Plugin)
Responsabilidad Elimina un plugin del sistema
Dependencias Ninguna
Precondición - El plugin p debe existir
Postcondición - El plugin p se elimina del sistema
Tablas 5.1: Contratos del sistema
Los diagramas de colaboración permitirán mostrar de forma gráfica la interacción entre las
distintas clases y los actores. Como se podrá observar por la semejanza entre ellos, en la
realización del sistema jugará una parte fundamental el envío de eventos, el cuál deberá
ser detallado en la fase de diseño del sistema.
En cuanto al proceso de desarrollo, se realizará una mezcla del enfoque top-down (para
las aplicaciones en sí mismas) y bottom-up (para los elementos reutilizables, por ejemplo,
componentes gráficos).
Hay que destacar que los diagramas que se usarán, en la mayoría de los casos, no
seguirán el estándar UML, debido a que precisamente una de las carencias de éste es la
imposibilidad de modelar correctamente sistemas distribuidos.
Tanto los clientes como los servidores serán implementados siguiendo un esquema por
capas: Una capa de interfaz gráfica, una capa de modelo y una capa física, más cercana
al bajo nivel (conexiones de red, comunicación con hardware, comunicación con bases de
datos, etc.).
Los componentes gráficos que por su cometido no sean reutilizables (por ejemplo, la
ventana principal de la aplicación) serán creados siguiendo un modelo vista-controlador.
También se hará uso de una arquitectura par a par (peer to peer o P2P) para la
transmisión de datos (ficheros, imágenes, etc.) entre usuarios del sistema, para evitar la
sobrecarga de los servidores.
Haciendo uso de los diagramas anteriores, de la fase de análisis, y de los requisitos del
sistema se ha elaborado un diagrama de paquetes, cuyo esquema será el tomado para la
fase de implementación del sistema.
Principios
4. Principio de coherencia.
8. Principio del contexto. Hay que limitar la actividad de los usuarios a su contexto.
Un conjunto de operaciones válidas en un contexto podrían no serlo en otro.
Los puntos anteriores, en nuestra filosofía de desarrollo, los resumiremos a uno solo, que
mantendremos siempre por encima de todos los demás:
Motivos aceptación:
Durante la implementación del sistema se han realizado una serie de cambios sobre la
versión tercera del interfaz:
• Inclusión de la ayuda del sistema dentro de tooltips en cada uno de los elementos
de la interfaz
• Evitar que el usuario poco experimentado deba "navegar" entre cientos de páginas
de documentación para saber como se realiza una acción. Todo es accesible con
un sólo click y toda la ayuda está integrada en cada elemento del interfaz.
Por último, el aspecto final de la ventana principal de la aplicación cliente, una vez
El primer diseño de la ventana del editor incorporaba una barra de herramientas que
permitía acceder a las funciones más básicas (definir la figura y el color de las
anotaciones, navegar por el documento, guardar las anotaciones y deshacer) y el lienzo
donde se dibuja el documento:
Una segunda aproximación, agregó una nueva barra. En esta barra se incluyen los
controles para la navegación del documento. En la barra superior, se incluyen los
controles relativos a la anotación del documento tales como guardar las anotaciones,
imprimir el documento con las anotaciones, desplazarnos a lo largo de las anotaciones
cambiar el tipo de anotación, el color, eliminar anotaciones deshacer, rehacer, etc.
• Usabilidad. Se seguirán una serie de principios de diseño para que el sistema sea
de fácil uso. Se hará un especial empeño en la simplificación en lo posible del uso
del sistema, mediante el uso de interfaces gráficas que permitan el acceso a los
componentes de una forma rápida y directa.
• Fácil Desarrollo. Se seguirá una arquitectura por capas que permitirá en un futuro
reemplazar su implementación por otra sin necesidad de cambiar la
implementación del resto de capas, al menos, en gran medida. El código será
estructurado en paquetes con una funcionalidad específica, intentando limitar en lo
posible el acoplamiento entre éstos. Se seguirán estándares en el formateo del
código fuente que aumentarán la legibilidad de éste. Además, se documentará
ampliamente. Los componentes gráficos seguirán el sistema de desarrollo
impuesto para JavaBeans, de tal forma que puedan ser usables mediante un IDE
que posea un editor gráfico de interfaces de usuario. Se distribuirá una biblioteca
en un único fichero que permitirá utilizar el framework para la realización de nuevas
aplicaciones, así como un manual para los desarrolladores para ésta.
Subsistemas específicos para el framework (núcleo del sistema). Todo éstos subsistemas
se caracterizarán por proporcionar servicios para la implementación de cualquier
aplicación que haga uso del framework:
Para que el sistema sea ajeno al uso de JavaSpaces se debe proporcionar una serie de
clase que actúen de capa de abstracción sobre éste. Éstas clases serán las usadas por
cualquier elemento del sistema para el envío y recepción de eventos. También habrá que
realizar una abstracción acerca del servidor al que se dirigen los eventos del cliente, es
decir, ésto debe ser transparente, en la medida de lo posible para el resto de subsistemas.
Cuando un usuario se conecte al sistema como cliente, ésta clase deberá almacenar la
metainformación de éste, requerirle su nombre de usuario y su contraseña y, en caso de
que sea necesario, encargarse de la sincronización de los componentes para que el
usuario se encuentre gráficamente con el mismo estado que el resto de usuarios.
Un componente que desee enviar un evento le será suficiente con colocarlo en una cola
de eventos que dispone el DConector y será este el que haga el envío efectivo. Para
realizar dicho envío se usa el algoritmo basado en un token descrito con anterioridad para
garantizar una correcta numeración de los eventos.
Además del envío y recepción de eventos se encarga de todas las tareas de inicialización,
tanto propias como lazar el cliente de metainformación. Durante la inicialización es el que
notifica a los componentes cuando deben realizar el envío de su petición de inicio de
sincronización, si es que necesitan hacerlo.
Ésta clase deberá mostrar gráficamente un pequeño diálogo donde se muestre el hecho
de estar sincronizando elementos y otro que permita introducir el nombre de usuario y la
contraseña. El diseño de ambos componentes gráficos se muestra a continuación.
Los siguientes datos del sistema deberán ser accesibles para el cliente desde ésta clase:
• Nombre de usuario
• Clave
• Rol desempeñado actualmente
• Rol por defecto
• Aplicación en ejecución
Los siguientes métodos serán los permitidos por ésta clase para el cliente:
• Sincronizar componentes.
• Obtener el DConector en ejecución (solo podrá haber una instancia del Dconector
en ejecución por cada cliente).
Se implementará así mismo un evento especial que permita enviar y recibir eventos con la
metainformación del sistema completo. Éste evento se conocerá como DMIEvent.
Permitirá notificar y obtener respuestas de notificación para inserción, eliminación o
modificación de usuarios, roles, componentes, aplicaciones, etc. También permitirán
notificar y obtener notificaciones para la conexión y desconexión de usuarios.
Para mantener un estado consistente, hay que garantizar que todos los eventos se
procesan en el orden en que fueron enviados.
El primer paso necesario es tener un número de secuencia que nos permita diferenciar
qué eventos se han producido antes o después de qué otros. A este número de secuencia
se le va a denominar a partir de ahora contador. Un contador menor indicará que el
evento se ha producido antes que otro que tenga un contador mayor. Cuando un
componente sincroniza su estado con el de las demás instancias de ese componente
(puede haber componentes que no realicen sincronización) también sincroniza el contador
a partir del cual comenzará procesar eventos. Así pues si un componente sincroniza su
contador al valor 357 esperará hasta recibir el evento con contador 358. Una vez recibido
éste esperará hasta recibir el 359 y así sucesivamente, siempre con incremento de una
unidad en el número de evento que se espera. Para que esto funcione debe garantizarse
que los eventos se numeran en orden ascendente y que no se envían 2 eventos con un
mismo contador. Para ello existe un único token que un usuario que desee enviar un
Un caso particular del uso de token son los TokenFichero, que está asociado a la
anotación compartida de documentos. Por cada documento que se esté abierto en un
momento dado en el sistema, existirá un TokenFichero correspondiente a ese documento.
Se deberá impedir, usando ésta filosofía, que en cualquier otro subsistema se deba
realizar un “acceso directo” a la BD o se deba usar un comando de SQL para realizar
alguna operación concreta.
Se deberá implementar una clase específica para realizar la conexión con la base de
datos. Gracias a ésta clase, se hará transparente para el resto de subsistemas el hecho
de que se esté usando un tipo específico de gestor de bases de datos. Si en cualquier
momento se tomase la decisión de sustituir MySQL, tan solo se debería modificar ésta
clase.
Para gestionar el contenido del repositorio y abstraer el hecho de que se esté usando una
base de datos, se crearán dos clases específicamente destinadas a la gestión de la
metainformación de las aplicaciones y de los ficheros. Gracias a éstas clases, si se optase
por reemplazar el almacenamiento en una base de datos por otro esquema de
almacenamiento (por ejemplo, un fichero), tan solo se debería sustituir la implementación
de las operaciones de cada una de ellas, sin necesidad de alterar el resto de subsistemas.
Éstas clases se encargarán también de traducir los resultados obtenidos de la base de
datos a objetos de diversas clases de la capa de modelo del sistema.
Las entidades, relaciones y atributos que se utilizarán para construir el modelo E/R de la
base de datos han sido identificadas a continuación.
Entidades encontradas:
• Rol. Representa a cada uno de los roles que se pueden desempeñar en las
distintas aplicaciones.
• Usuario. Representa a cada uno de los posibles usuarios que pueden actuar como
clientes para las distintas aplicaciones.
• Fichero. Identifica a cada uno de los distintos ficheros del sistema. Los ficheros
serán compartidos entre aplicaciones, con lo que no existirá ningún campo
orientado a identificar la aplicación a la que pertenece cada uno.
• Usuario-Rol. Indica los roles permitidos para un usuario y su rol por defecto
• Rol-Fichero. Indica el rol que tenía el propietario cuando creó el fichero. La raíz del
sistema de ficheros será el único fichero que no tenga rol del propietario, ya que
éste no existe.
• Aplicacion(Nombre, nivel)
• Rol(Nombre)
• Usuario(Nombre, RolPorDefecto, Administrador)
• Componente(Nombre)
• Componente-Usuario(usu, comp, permisos)
• Componente-Rol(rol, comp, permisos)
• Fichero(nombre, es_directorio, permisos, directorio_padre, ruta_local, tipo)
posibleR(ID_rol, ID_app)
posibleU(ID_usu, ID_app)
permitidos(ID_usu, ID_app)
permisosR(ID_rol, ID_cmp, nivel)
permisosU(ID_usu, ID_cmp, nivel)
Para facilitar la instalación del sistema se implementarán una serie de scripts que
permitan crear la base de datos y rellenarla con datos de prueba. Éstos scripts deberán
de ser provistos junto con la implementación final de la aplicación.
1 A pesar de estar incluídos en el diagrama, las clases GestorMetainformaciónBD y ConcectorBD, estas clases no
serán especificadas en este punto.
• Cada vez que una aplicación cliente necesita de algún tipo de información de
awareness o de control de acceso a los componentes, el Componente en cuestión
envía un mensaje al Dconector.
• MIAplicación
• MIUsuario
• MIRol
• MIComponente
• MICompleta
Por otro lado, las clases involucradas en este subsistema incluyen mecanismos de
detección y recuperación de errores (servidores caídos, clientes colgados, etc.), lo que
redunda en la fiabilidad y robustez del sistema.
Clases Involucradas
Ilustración 50: Diagrama de estados La clase cuenta con una serie de hebras:
para el monitor del cliente de
metainformación
#Hebra localizadora: es la encarga de localizar
el JavaSpace. Una vez localizado, notifica esta condición haciendo uso del
monitor.
# Hebra detectora de error: controla los posibles errores que podrían producirse
durante la localización del JavaSpace, si transcurrido un tiempo prudencial no
se ha localizado el JavaSpace, se indicará la condición de error y la aplicación
terminará. El funcionamiento es bien sencillo: la hebra se duerme un tiempo
establecido, transcurrido el cual comprueba si se ha localizado el JavaSpace, si
Una vez realizados los cambios que el usuario considere oportunos, los cambios
realizados en la metainformación serán difundidos por el sistema al resto de
clientes, si procede, y almacenados de forma permanente en el repositorio de
datos del sistema.
3 A pesar de estar incluidos en el diagrama, las clases GestoFicherosBD y ConcectorBD, estas clases no serán
especificadas en este punto.
Por otro lado, las clases involucradas en este subsistema incluyen mecanismos de
detección y recuperación de errores (servidores caídos, clientes colgados, etc.), lo que
redunda en la fiabilidad y robustez del sistema.
Esta clase incluye una serie de método que permiten acceder a la metainformación
de los documentos compartidos y modificarla. Algunos de los métodos más
importantes son:
Envía un evento al servidor de Ficheros para que este elimine un fichero (su metainformacion y el fichero
físico)
existeFichero(java.lang.String path, java.lang.String aplicación):MIDocumento
Método estático que devuelve el cliente de ficheros que se esta utilizando en este momento.
Cada vez que una aplicación cliente inicia la sincronización con el servidor de
ficheros, éste envía al cliente la metainformación del conjunto de documentos y
carpetas para las cuales el usuario tiene, como mínimo, permiso de lectura
recuperadas de la base de datos. La forma en la que se envían estos datos es la
de un árbol en la cual la raíz es el directorio raíz (/), las hojas los documentos y los
nodos intermedios las carpetas. También en este mismo mensaje, el servidor
facilita la dirección IP y el puerto donde estará a la espera de solicitudes de
documentos vía RMI.
Debido al uso de la tecnología RMI, será necesaria la creación de un interfaz que herede
de Remote y una clase que implemente ésta interfaz y herede de UnicastRemoteObject.
Tantos las clases anteriores como las interfaces creadas se explicarán a continuación.
Se creará una interfaz Java llamada TransmisorFicheros que herede de Remote y que
implemente métodos para realizar las siguientes operaciones:
El path de los ficheros será con respecto al directorio base de donde los almacene
físicamente el servidor de ficheros. Por ejemplo, si los ficheros se almacenan en el path
“/Data”, entonces, se realizará una traducción del path de los ficheros de “/” a “/Data”.
1. Por un lado, permite que una aplicación se convierta en un servidor RMI, con la
sola invocación del método estático establecerServidor(int). Este método registra
un objeto de la clase TransferenciaFichero, que será el objeto que reciba las
peticiones de los clientes. El proceso seguido es sencillo: crea el registro local, crea
un nuevo objeto TransferenciaFichero y lo registra mendiante el método bind().
1. Por un lado, permite que una aplicación se convierta en un servidor RMI, con la
sola invocación del método estático establecerServidor(int puerto_escucha). Este
método registra un objeto de la clase TransferenciaFichero, que será el objeto que
reciba las peticiones de los clientes. El proceso seguido es sencillo: crea el registro
local, crea un nuevo objeto TransferenciaFicheroP2P y lo registra mediante el
método bind(). A diferencia de la Transfer, también se permite para y re-iniciar el
servidor, mediante los métodos estáticos pararHebra() y establecerServidor(int
puerto, Documento aCompartir). Cada vez que una aplicación cliente inicia un
servidor se le asigna un identificador, que será utilizado por las aplicaciones cliente
para identificarla.
• Cada vez que un cliente solicite el acceso a un fichero concreto a través de su ruta
en el sistema de archivos virtual, el servidor de ficheros realizará una traducción
automática de esa ruta a la ruta correspondiente en su sistema de ficheros local.
Por ejemplo, si el cliente solicita el documento /Publico/Informes/Informe0.pdf y en
la máquina del servidor la carpeta donde se almacenan los datos es /DATA, el
servidor realizaría la siguiente traducción: /DATA/Publico/Informes/Informe0.pdf
La clase Documento proporciona métodos para acceder a las página y anotaciones del
documentos, crear documentos a partir de ficheros, imprimir el contenido de los
documentos, guardar los comentarios realizados sobre un documento, etc.
La clase Documento contiene una lista estática de filtros (DocumentFilter), que son los
que determinan que tipos de ficheros pueden ser abiertos (ver el siguiente punto). Una de
4 Ver punto donde se explica de forma detallada la clase Documento y los filtros aplicados a los documentos.
Un ejemplo de la utilidad del agregado dinámico de filtros podría estar en los plugins:
utilizando un plugin que agregue nuevo filtros a al lista de la clase Documento podemos
soportar nuevos documentos si necesidad de retocar el código de la aplicación.
La interfaz Filtro, permite la definición de filtros para abrir documentos: un filtro es una
clase que genera un objeto de la clase Documento a partir de un fichero aplicando un
determinado renderizado sobre las imágenes que componen cada página del documento,
en función del tipo de fichero que sea.
Dicho de otra forma, un filtro es una clase que provee un determinado método cuyo
funcionamiento básico sería:
1. Se abre el fichero.
2. Se lee el contenido del fichero y se “pinta” sobre las imágenes que forman
las páginas del documento, de acuerdo con el tipo de fichero.
3. Se devuelve el documento creado.
Los métodos que deben implementar las clases que implementes este interfaz serían:
Dentro del Framework desarrollado se han incluido los siguientes cuatro filtros:
La clase provee de una serie de métodos para acceder y modificar sus atributos.
La clase Anotación proporciona métodos que permiten acceder a sus atributos y modificar
sus valores.
Funcionamiento conjunto:
2. Anotación del documento. En esta fase se presenta al usuario una visualización del
documento y se le permite desplazarse por el documento, anotarlo, eliminar
anotaciones, guardar los cambios realizados en el documento, etc.
Durante el proceso de apertura de un documento para su edición hay que garantizar que
el documento recibido sea consistente con la última copia modificada de éste. Dicho en
otras palabras, la copia del documento que tenemos que recibir debe de ser aquella que
refleje la últimas modificaciones realizadas sobre el documento, aunque éstas no hayan
sido guardas.
Además de la lista de usuarios, también se incluye el estado del token, que puede ser
bloqueado o desbloqueado. Cuando el token está en estado bloqueado, significa que hay
algún cliente abriendo el documento y que los eventos relacionados con las anotaciones
La implementación del token será muy parecida a la de un evento cualquiera del sistema:
Será una implementación de la interfaz “Entry” para permitir su escritura en JavaSpaces.
1. Apertura de un documento.
Una vez abierto el documento, este le será presentado al usuario en una ventana dividida
en tres secciones: una barra de controles, el lienzo donde se muestra el documento y una
barra de estado en la que se indica la página actual y que nos permite cambiar de página.
• Barra de controles:
La barra de controles consta de una barra de herramientas conteniendo una serie
de botones que nos permiten acceder a la anotación del documento. De izquierda a
derecha tenemos:
# Abrir: permite abrir un nuevo fichero.
# Guardar: guarda las modificaciones realizadas en el documento en el servidor.
• Barra de estado:
• Lienzo:
El lienzo nos permite realizar anotaciones sobre los documentos, borrarlas, etc. y
mediante el envío de eventos distribuir estas acciones al resto de aplicaciones que
también se encuentren editando ese documento.
Para realizar una anotación sobre un documento ya abierto no tenemos más que:
1. Desplazarnos hasta la página deseada utilizando la barra de estado.
2. Seleccionar el tipo de anotación y el color.
3. Arrastrar el ratón sobre el lienzo.
Hay que destacar el hecho de que se proporcionarán tres plugins por defecto en el
sistema, los cuales proporcionan características que son fundamentales para éste: Un
chat completo (con videoconferencia), un gestor de plugins (que permita agregar o
eliminar plugins y alguna tarea de configuración básica) y una pizarra para dibujo
compartido (una versión reducida del editor de documentos que se verá en secciones
posteriores).
A lo largo de las sucesivas secciones se podrá ver cuales serán los mecanismos que se
usarán para el desarrollo del subsistema de plugins, así como de los plugins
anteriormente expuestos.
La clase DabstractPlugin será una clase abstracta donde se establecerán los parámetros
fundamentales que deberá tener cualquier plugin, así como tres métodos a implementar
por el desarrollador (abstractos): init, start y stop.
• version. Versión del plugin. Solo tendrá sentido para cuando activemos el control
de versiones (se verá en secciones posteriores).
• jarFile. Nombre del fichero .jar que contendrá el plugin. Permitirá, si se activa el
control de versiones, que el sistema tenga conocimiento del fichero que se debe
transmitir al resto de clientes.
• init. Será el método encargado de inicializar todos los parámetros específicos para
un plugin, así como los parámetros generales que sean opcionales. Deberá
llamarse desde el constructor de cualquier plugin implementado.
• start. Indicará que se debe realizar para ejecutar el plugin. Será el método que se
llame cuando el usuario final desee abrir un plugin en la aplicación cliente.
Puesto que al iniciar la aplicación cliente se cargarán los plugins que estén disponibles
para ésta, será necesario modificar el classpath de java en tiempo de ejecución. Éste
proceso, que a priori puede resultar muy complejo tendrá una solución muy sencilla:
Buscar el método de Java encargado de realizar ésto e invocarlo en tiempo de ejecución.
El método más adecuado para realizar ésto en Java, tras un arduo proceso de
investigación, se ha determinado que es addURL, el cual es un método privado de la
clase URLClassLoader.
La clase que realiza éste proceso se llamará ClassPathModifier y tendrá tres métodos
para agregar un fichero al classpath: Mediante una cadena de texto que represente el
path del fichero, mediante un objeto de tipo File y mediante un objeto de tipo URL.
Se implementará una clase que permitirá la carga de los plugins que se encuentren
disponibles para la aplicación cliente. Los plugins serán una serie de ficheros con
extensión .jar que se encontrarán situados en la carpeta “plugin”, subcarpeta del directorio
donde se encuentre la aplicación cliente.
Se deberá implementar una clase que permita actuar de interfaz para la gestión de
plugins. Deberá mantener un listado de los plugins disponibles para el sistema, así como
permitir la inserción o eliminación de éstos. También permitirá métodos para hacer visible
o no un plugin concreto, u obtener algunas de sus características principales (sin tener
que acceder elemento a elemento de la colección, solo haciendo uso de su índice dentro
de ésta).
El diagrama de estados del funcionamiento de las hebras que hacen uso del monitor que
controla la actualización dinámica de plugins se muestra a continuación.
El proceso seguido por el sistema de control de versiones funcionará del siguiente modo:
• Los plugins actualizados se cargarán de forma dinámica para que sea transparente
para el usuario. En algún caso, para que el funcionamiento sea el esperado, puede
ser necesaria una reinicialización de la aplicación cliente.
Puesto que todo el sistema hace uso de elementos de distribución de eventos, se deberá
hacer una base para la creación de plugins orientados a realizar cálculos paralelos,
haciendo uso de la infraestructura implementada. Debido al modelo seguido por el resto
del sistema, el modelo del sistema de cálculo paralelo será del tipo Maestro/Esclavos.
• Una píldora venenosa (poison pill) para los esclavos que se implementará en la
clase PoisonPill. Ésto será un tipo de evento especial que hará que un esclavo
termine automáticamente de ejecutar sus tareas.
Éste chat estará formado por tres componentes gráficos: Una lista de usuarios
conectados, un chat y una barra de herramientas. De éstos, el único que deberá
implementarse específicamente será la barra de herramientas, el resto serán
provistos por el framework. El sistema de videoconferencia será el mismo que se
explicó dentro de la sección dedicada al subsistema de videoconferencia.
Puesto que se mostrará un tooltip al pasar el raton sobre cada elemento de la lista, será
necesaria la implementación de un componente gráfico específico para ello. Este
componente se llamará “PluginList”. Simplemente será una especialización de la clase
JList de Java, donde se agregará un método para controlar que al pasar el raton sobre
una entrada de la lista se muestre el tooltip correspondiente. El contenido del tooltip de
cada plugin se corresponderá con su descripción, por lo que nuevamente se hace
hincapié en el hecho de que se introduzca una descripción en los plugins desarrollados.
Como mejora a éste sistema, en un futuro se plantea el hecho de que se intercambie ésta
lista por un árbol, donde cada rama se corresponda a un categoría de entre las posibles y
cada hijo sea un plugin de esa categoría.
Captura de vídeo
Para realizar la captura de vídeo haremos uso de unas bibliotecas de código abierto
conocidas como LTI-Civil (disponibles en http://lti-civil.org). La primera restricción que nos
impondrá el uso de éstas bibliotecas es que el acceso al hardware en los principales SO's
no se puede realizar desde Java con la misma eficiencia, con lo que obtendremos los
siguientes ratios de captura:
Transmisión de vídeo
La transmisión de vídeo debe ser lo más óptima posible en todos los casos para evitar
que éste proceso tenga un impacto significativo con respecto a las imágenes por segundo
que se reproducirán, que de por sí, debido a las limitaciones de acceso a hardware son
muy pocas.
• No importa demasiado que una imagen llegue corrupta o no, puesto que se
actualizará en un plazo muy pequeño de tiempo. Ésto implica que no sea necesario
realizar un control exhaustivo de errores en la transmisión.
• Las imágenes se pueden transmitir una a una, ya que la velocidad de captura es
muy pequeña en comparación con la velocidad de transmisión, con lo que no
merece la pena la implementación de bufferes intermedios. Si se consiguiera en un
futuro una mejora sustanciosa en la velocidad de captura del hardware, entonces
ésto podría ser interesante para poder ver vídeo con mayor fluidez.
• La transmisión no puede realizarse haciendo uso de JavaSpaces. Será necesario
idear un sistema de sockets para transmitir las imágenes entre dos clientes. Ésto
se integrará en la clase donde se realiza la captura desde el hardware. El uso de
RMI podría facilitar el desarrollo, pero tendría un impacto muy importante en la
Cada una de las imágenes transmitidas requerirá ser reproducida en el otro extremo. Éste
proceso se describirá en la siguiente sección.
Reproducción de vídeo
La reproducción de vídeo es un proceso bastante simple según el esquema de captura y
transmisión usado. Puesto que se realiza una transmisión imagen a imagen, tan solo
tendremos que crear un panel donde se muestren éstas imágenes una a una. Para
realizar ésto, se implementará la clase ImageComponent, que será la encargada de
mostrar cada imagen, y que será una subclase de JPanel. Para mostrar una imagen se
deberá sobrecargar el método paint, poniéndole el modificador synchronized, puesto que
se deben evitar conflictos a la hora de pintar las imágenes (se puede dar el caso de que
no se haya terminado de pintar una imagen y que llegue otra y se vuelva a llamar al
método paint).
En la ventana final donde se mostrará éste componente, se mostrará también una versión
Se deberán, así mismo, implementar dos hebras encargadas de leer la imagen local y la
remota cada cierto tiempo para actualizar ambos paneles. La lectura deberá realizarse
cada 30 milisegundos aproximadamente (33 asignaciones por segundo) para mostrar la
imagen remota y cada 100 milisegundos (10 asignaciones por segundo) para mostrar la
imagen local (La fluidez de la imagen local no tiene importancia si lo comparamos con el
rendimiento general del subsistema).
Captura de audio
La captura de audio se realizará mediante diversas clases que posee la SDK de Java.
Será necesario implementar una clase que se encargue de abstraer ésta captura, puesto
que las clases provistas por Sun son complejas de usar y difíciles de comprender en gran
medida.
Éstas clases harán uso de flujos de entrada de audio, flujos de salida de datos (bytes)
para posteriormente reproducir o transmitir el audio capturado, de un mezclador y de una
linea de datos. El mezclador será el dispositivo desde el cual se capturará el audio. La
linea de datos será la línea específica dentro del mezclador desde la que capturaremos.
Según el código de formato que se utilice, se capturará de una línea u otra.
El flujo de entrada de audio será provisto por la clase AudioInputStream de Java. El flujo
de salida será un OutputStream, también de Java, que nos permitirá obtener los bytes
contenidos en él.
Haciendo uso de las clases descritas previamente, con los datos proporcionados
mediante el panel anterior, se establecerá una conexión mediante la cuál se transmitirá un
buffer capturado desde un cliente hasta otro, para que éste último lo reproduzca.
Transmisión de audio
La transmisión del audio es una parte sumamente compleja, puesto que requerirá un
cierto control de errores y se deberá controlar el flujo de datos para evitar saturaciones en
el buffer de entrada.
Para realizar ésta transmisión de la forma más adecuada se establecerá una red de
comunicación base, que será una implementación mediante sockets del protocolo TCP.
De éste protocolo aprovecharemos principalmente el control de flujo que proporciona y el
hecho de que sea orientado a conexión. Éste control de flujo se realiza mediante buffers
de envío y de recepción. Del resto de tareas se encargarán los sockets. Se deberán crear
métodos para poner una red en escucha y para abrir y cerrar la conexión y para crear los
Handshake activo
Fase I
• Se envía un entero con un valor especial (PROTOCOL_MAGIC). Éste valor es
0x43484154.
• Se envía un entero con la versión del protocolo (PROTOCOL_VERSION). En
nuestro caso es la versión 1.
• Se envía el código de formato para el audio en forma de entero.
Fase II
• Se lee el buffer que se envió desde el otro extremo.
• Si el buffer contiene PROTOCOL_ACK (el valor entero 1001) entonces se
establece la conexión.
Handshake pasivo
Fase I
• Se leen tres enteros (12 bytes).
• Se comprueba que el primer entero sea PROTOCOL_MAGIC, el segundo
PROTOCOL_VERSION y el tercero un código de formato válido.
• Si todo fue correcto, se escribe PROTOCOL_ACK. Si no, se escribe
PROTOCOL_ERROR (el valor entero 1002).
Puesto que la conexión podrá admitir ciertos parámetros de configuración (como los
puertos usados o el código de formato para la transmisión) será necesario agregar un
sistema de configuración también para la transmisión. Éste sistema de configuración se
integrará con el descrito para la captura en una ventana con dos pestañas. La ventana
final que deberá implementarse, mostrándose la pestaña correspondiente a la
configuración de la conexión se muestra a continuación.
Diagrama de clases
La aplicación cliente será un caso específico de aplicación distribuida que puede ser
creada mediante la infraestructura descrita a lo largo de éste documento. El esquema de
cualquier aplicación distribuida que se desee implementar (incluyendo, por supuesto, a la
aplicación cliente) se muestra a continuación.
Para la gestión de eventos de los componentes de los que hace uso, se encargará de
instanciar un objeto de la clase DConector. También se encargará de leer los plugins
disponibles y de realizar las distintas comprobaciones de seguridad necesarias (permisos
sobre documentos, componentes, de los usuarios, etc.). En la mayoría de los casos, se
encargará de mostrar mensajes que informen al usuario final de las posibles
eventualidades ocurridas.
Mediante PluginList, mostraremos al usuario final las aplicaciones (plugins) de los que
dispone. Para actualizar ésta lista ante diversas eventualidades (agregar, eliminar,
actualizar u ocultar plugins) se hace uso de PluginContainer. Ésta última también se
encargará de recuperar los plugins de la carpeta “plugin”. Cada vez que se actualice un
elemento en la capa de modelo, actualizaremos la lista convenientemente.
Gestión de usuarios
Para la gestión de usuarios se hará uso del subsistema de videoconferencia, de gestión
de metainformación del sistema, de gestión de metainformación de ficheros y de sistema
de ficheros. Gráficamente se mostrará un pequeño árbol donde se podrán observar los
usuarios conectados y el rol de cada uno. Bajo éste árbol tendremos una pequeña barra
de herramientas que permitirá realizar diversas tareas de sistema o comunicarnos con
otros usuarios. Éstas operaciones se describen a continuación:
• Cambiar el rol actual. Permitirá cambiar el rol actual del usuario. El cambio de rol
se reflejará en la interfaz de usuario habilitando o deshabilitando
correspondientemente ciertos componentes o mostrando u ocultando documentos.
Gestión de documentos
La gestión de documentos requerirá del uso del subsistema de metainformación de
ficheros y del sistema de ficheros. Se mostrará un componente gráfico que permite
mostrar en árbol los documentos. Éste componente gráfico asignará distintos iconos a los
distintos tipos de documentos soportados y de forma gráfica se mostrará que alguien está
editando un documento (mediante un icono especial). A éste componente gráfico se le
agregará una barra de tareas que permitirá acceder a las siguientes operaciones que
provee la clase que modela el árbol de documentos:
Todo componente gráfico que desee ser replicado deberá implementar la interfaz que se
describe a continuación. Ésta interfaz se llamará Dcomponente.
8.1. DcomponenteBase
Descripción
" Interfaz de envío de eventos: Disponemos del método enviarEvento() que nos
" Inicio automático de la hebra procesadora del componente así como la de sus
hijos. De esta forma nos despreocupamos de iniciar la hebra nosotros y que sea en
el momento correcto.
Diagrama UML
" activar(): Este método se encarga de activar el componente. Por si solo no tiene
funcionalidad alguna aunque normalmente no será necesario sobreescribirlo.
" Métodos add(…): Estos métodos nos permiten añadir elementos directamente al
panel de contenido.
" conectadoDC(): Devuelve TRUE en caso de que el componente tenga conexión
directa con el DConector. FALSE en cualquier otro caso. No es necesario que se
sobrescriba.
" crearHebraProcesadora(): Uno de los métodos mas importantes para el desarrollo de
nuevos componentes a partir de este. Este método devuelve una hebra que será la
encargada del procesamiento de los eventos que lleguen al componente. La clase
que debe devolverse es una subclase de HebraProcesadoraBase.
" desactivar(): Desactiva el componente. Normalmente no es necesario sobreescribirlo.
" enviarEvento(): Mediante este método realizamos el envío de un evento. No es
necesario sobrescribirlo ya que implementa la funcionalidad deseada.
" getID(): Devuelve el ID del componente. No es necesario sobreescribirlo.
" getNivelPermisos(): Devuelve el nivel de permisos del componente. No es necesario
sobrescribirlo.
" getNombre(): Obtiene el nombre del componente. No es necesario sobreescribirlo.
" iniciarHebraProcesadora(): Inicia la hebra procesadora. No es necesario
sobreescribirlo.
" mostrar(): Muestra el componente y notifica a los hijos que ha sido mostrado. No se
debe sobrescribir.
" obtenerColaEnvio(): Nos devuelve la cola que está usando como cola de envío. No es
necesario su uso para la generación de nuevos componentes.
" obtenerColaRecepcion(): Nos devuelve la cola que está usando como cola de
recepción. No es necesario su uso para la generación de nuevos componentes.
" obtenerComponente(int num): Otro método bastante importante. Deberemos
sobrescribirlo cuando diseñemos un nuevo componente para que devuelva el
componente hijo con el número indicado. Los componentes hijos irán numerados
comenzando por el 0.
Cada componente replicado deberá tener una hebra que se encargue de procesar los
eventos que reciba. Conceptualmente a esta hebra se la ha denominado hebra
procesadora y en los ejemplos de desarrollo de componentes se pueden encontrar
ejemplos de cómo implementarlas.
" obtenerEventosColaRecepcion(): Devuelve un array que contiene todos los eventos que
hay en el momento de la llamada en la cola de recepción del componente. Este
método se usa normalmente en le proceso inicial de sincronización para examinar
los eventos existentes en busca de una respuesta de sincronización.
"
" leerSiguienteEvento(): Nos devuelve el siguiente evento disponible para ser procesado.
Si no hay ninguno disponible, quien lo llamó quedará bloqueado hasta que lo haya.
"
" iniciarHebra(): Mediante una llamada a este método se inicia la ejecución de la hebra.
Este método no tendrá que ser llamado cuando se desarrolla un componente a
partir del componente genérico DComponenteBase que ya será el quien de forma
automática lo llame cuando sea necesario.
Los eventos que genera una clase captadora para notificarnos acciones del usuario sobre
la clase captadora tienen un nombre coincidente con el patrón DJxxxEvent. Las xxx
denotan la clase captadora que los generan.
Aparte de recoger los eventos que generan debe de haber alguna forma de notificarles
esos para que actualicen su estado (como se describe en la sección de diseño dentro del
apartado de eventos distribuidos). Esa notificación se realiza pasándoles el evento que
han generado con anterioridad (normalmente una vez haya sido recibido desde el
JavaSpace) a través de su método porcesarEvento(). El estado se actualiza después de
recibir la notificación y no antes puesto que se ha implementado un algoritmo pesimista.
Como primer paso vamos a mostrar una tabla indicando el tipo de eventos que genera
cada clase captadora para notificarnos mediante un DJListener de las acciones que vaya
realizando el usuario sobre dicha captadora.
Lista (DJList):
Este componente genera sus notificaciones en forma de un evento del tipo DJListEvent.
Su diagrama UML es el siguiente:
CAMBIO_POSICION posicion
tipo
El resto de campos serán usados por el componente replicado que usa esta clase
captadora.
CAMBIO_SELECCION_LISTA seleccionLista
tipo
SELECCIONADO itemSeleccionado
tipo
DListener
La interfaz definida como DListener en este componente se llama DJComboBoxListener.
Diagrama UML
Botón (DJButton):
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo
DJButtonEvent. Su diagrama UML es el siguiente:
PRESIONADO tipo
SOLTADO tipo
" PRESIONADO: Mensaje enviado cuando se pulsa el botón con el botón izquierdo
del ratón.
" SOLTADO: Mensaje enviado cuando se suelta el botón izquierdo del ratón
anteriormente pulsado.
DListener
La interfaz definida como DListener en este componente se llama DJButtonListener.
Diagrama UML
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo
DJToggleButtonEvent. Su diagrama UML es el siguiente:
PRESIONADO tipo
SOLTADO tipo
" PRESIONADO: Mensaje enviado cuando se pulsa el botón toggle con el botón
izquierdo del ratón.
" SOLTADO: Mensaje enviado cuando se suelta el botón izquierdo del ratón
anteriormente pulsado.
DListener
La interfaz definida como DListener en este componente se llama
DJToggleButtonListener.
Diagrama UML
PRESIONADO tipo
SOLTADO tipo
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo
DJTextFieldEvent. Su diagrama UML es el siguiente:
INSERT contenido
p1
tipo
REMOVE p1
p2
tipo
REPLACE contenido
p1
p2
tipo
DListener
La interfaz definida como DListener en este componente se llama DJTextFieldListener.
Diagrama UML
Árbol (DJTree):
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo DJTreeEvent.
Campos disponibles
APERTURA_CIERRE path
tipo
SELECCION path
tipo
DListener
La interfaz definida como DListener en este componente se llama DJTreeListener.
Diagrama UML
Menú (DJMenu)
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo DJMenuEvent.
Su diagrama UML es el siguiente:
Campos disponibles
CAMBIO_ESTADO path
tipo
DListener
La interfaz definida como DListener en este componente se llama DJMenuListener.
Diagrama UML
Dispone un único método que recibe un evento con el campo path establecido.
Item (DJMenuItem)
Evento manejado
Este componente genera sus notificaciones en forma de un evento del tipo
DJMenuItemEvent. Su diagrama UML es el siguiente:
Campos disponibles
CAMBIO_ESTADO path
tipo
DListener
La interfaz definida como DListener en este componente se llama DJMenuItemListener.
Diagrama UML
Dispone un único método que recibe un evento con el campo path establecido.
Los componentes que se han diseñado para ser usados en aplicaciones futuras o como
subcomponentes de componentes que se diseñen se van a catalogar en dos tipos. Por un
lado tendremos aquellos que hacen uso de una clase captadora y por otro los que no la
usan.
" DILista. Clase que hace uso de la clase captadora DJList. Esta lista ya trae
incorporada las barras de scroll para desplazarse por los elementos cuando estos
no entran todos en la parte visible de la lista. El comportamiento distribuido que
implementa es el siguiente:
" Selección de elementos de la lista
" Adición y sustracción de elementos de la lista
" DITree. Clase que hace uso de la clase captadora DJTree. El comportamiento
distribuido que implementa es el siguiente:
" Apertura y cierre de niveles del arbol.
" Selección de un elemento del arbol.
Estos componentes tienen un método que nos puede interesar, crearDJListener(). Este
método es llamado por el componente en su inicialización para obtener el DJListener que
se va a encargar de procesar los eventos que genere la clase captadora. Normalmente
este comportamiento será el envío del evento generado. Si nos interesa que eso no sea
así podemos sobrescribir dicho método para devolver un listener personalizado y de esa
forma hacer el procesamiento que desee el programador.
En esta sección describiremos aquellos componentes que no hacen uso de una clase
captadora para implementar su comportamiento.
" DIChat. Este componente nos permitirá chatear con los demás usuarios que haya
conectados. Su funcionamiento es tan simple como escribir un texto en el campo
destinado a ello y pulsar sobre el botón Enviar. En la ventana de salida de
mensajes, cada mensaje aparecerá precedido por el nombre del usuario que lo ha
escrito. El nombre irá encerrado entre paréntesis como se puede ver en la imagen
siguiente:
Para cambiar de rol solo deberemos escoger un rol de la lista y pulsar sobre el
botón Cambiar.
" DIEtiquetaRolActual. Este componente solo nos muestra información acerca del
rol que estamos desempeñando en cada momento:
" DlistaUsuariosConectados. Este componente nos permite ver qué usuarios están
conectados en cada momento:
Este componente permite realizar una serie de acciones sobre los documentos
mostrados por éste. Las posibles acciones son:
Este componente permite realizar una serie de acciones sobre la página actual del
documento. Las posibles acciones son:
• Agregar una anotación a la página.
• Seleccionar una anotación.
• Eliminar una anotación.
• Deshacer la ultima anotación.
• Cambiar el estilo de anotación (elipse, rectángulo, texto, etc.).
• Cambiar el color de las anotaciones.
• Cambiar la página del documento.
• Eliminar todas las anotaciones de la página actual.
DIComboBox
DIButton
DIToggleButton
DICheckBox
DITextField
DIChat
JMenu
JMenuItem
Barra de menú
Menú
Se denomina menú a cada uno de los botones que hay en la barra de menú sobre los que
si se pulsa el botón del ratón se abre un menú popup con las opciones que contiene. Un
menú se puede añadir a otro menú con lo que conseguiremos un submenú que podrá
contener sus propias opciones.
Si no hay ningún menú abierto para abrir uno será necesario pulsar el botón izquierdo del
ratón sobre alguno de los menús principales (los situados en la barra de menú). Una vez
abierto alguno sólo será necesario situar el puntero del ratón sobre algún menú o
Ítem
Los ítems de un menú son los elementos que se añaden a un menú y sobre los que se
puede pulsar para ejecutar alguna acción (si la tiene asociada). A los ítems del menú se
les puede asociar un LListener y/o un LUListener para ser notificados cuando se pulse
sobre él.
Cuando un usuario arranca una aplicación distribuida ésta deberá sincronizar sus
componentes con los de los demás usuarios para que queden en un estado consistente.
La hebra procesadora de cada componente deberá estar preparada para responder a las
peticiones de sincronización por parte de otros componentes enviándoles su estado
actual.
Los componentes que se han implementado para el uso posterior por el programador
podrán usarse como parte de un componente más complejo (en los ejemplos de
desarrollo de componentes se podrá ver cómo). Cuando este componente complejo
reciba una petición de sincronización probablemente deba incluir en su información de
estado actual el estado del componente que forma parte de él. Para este fin cada
componente de los ofertados tiene los métodos obtenerInfoEstado() y procesarInfoEstado().
Mediante el primero obtenemos un evento con la información del estado actual del
componente. Dicho evento normalmente será añadido al evento de respuesta de
sincronización del componente complejo. Cuando el componente que se está
sincronizando reciba una respuesta que contenga el evento que se obtuvo con el método
obtenerInfoEstado() podrá pasárselo el componente correspondiente mediante el método
pocesarInfoEstado() para que actualice su estado. Debido a la forma de funcionamiento de la
sincronización es aconsejable implementar unos métodos iguales o similares a estos si
tenemos en mente que nuestro componente sea usado posteriormente como parte de otro
futuro componente desarrollado.
Antes de entrar en detalles debemos tener muy claro que si se añade a una aplicación la
funcionalidad de telepunteros la ventana en la que se muestran no deberá poderse
cambiar de tamaño ya que ello podría llevarnos con facilidad a inconsistencias de las
posiciones de los telepunteros.
El GMR mantiene en una estructura interna de información sobre los demás participantes
que se va actualizando conforme se van recibiendo notificaciones cuando se mueven sus
punteros. Cada vez que se actualiza la posición del puntero de alguno de los usuarios se
vuelven a pintar los punteros para reflejar dicho cambio.
Para la implementación de este gestor se plantearon dos problemas, los cuales se
comentan a continuación así como la solución adoptada ante el problema:
Está claro que debemos captar los movimientos de nuestro ratón para notificárselo a los
demás participantes. Esta captación presenta un problema.
Alguien podría pensar que una forma de resolverlo sería añadir un lístener a cada uno de
los componentes de la aplicación para captar esos movimientos. Esta solución es
demasiado compleja dado que tendríamos que ir componente por componente añadiendo
un nuevo listener.
Otra opción barajada sería añadir un listener al Glass Pane (ver explicación en el
siguiente problema) del JFrame y desde ahí notificar los cambios. El problema de esta
solución es que si se capta el evento de ratón en el Glass Pane dicho evento no llega a
los componentes de la aplicación por lo que se anularía totalmente la interacción.
Finalmente se adoptó como solución sustituir la cola de eventos del sistema por una
Se debía buscar un lugar donde pintar los botones. De todas las posibilidades barajadas
se ha optado por usar el Glass Pane como lugar para pintar dichos punteros. El Glass
Pane es un componente transparente que se sitúa sobre todos los demás componentes.
En la siguiente figura podemos ver su localización:
Por tanto al estar sobre cualquier otro componente aparecerá siempre en primer plano.
En la implementación actual se restringe el uso de telepunteros a un único Frame.
Tras realizar las operaciones anteriores, nuestro proyecto debería quedar como en la
siguiente imagen (salvo los nombres del proyecto y del paquete por defecto creado por
Netbeans).
package template_jdistframework;
import componentes.base.DJFrame;
Primero, agregaremos dos paneles, uno llamado "oeste" y otro llamado "centro". A ambos
paneles les asociaremos un layout del tipo "BorderLayout". Por ahora tendremos el
siguiente código para el panel:
package template_jdistframework;
package template_jdistframework;
import componentes.gui.DIChat;
import componentes.gui.usuarios.ArbolUsuariosConectadosRol;
import javax.swing.JPanel;
import java.awt.BorderLayout;
package template_jdistframework;
import componentes.gui.DIChat;
import componentes.gui.usuarios.ArbolUsuariosConectadosRol;
import javax.swing.JPanel;
import javax.swing.JLabel;
import java.awt.BorderLayout;
import java.awt.Dimension;
Con el código anterior ya podremos ver los distintos usuarios que están conectados, así
como recibir y enviar mensajería instantánea desde y hacia todos los usuarios
try {
ev.ipVC = InetAddress.getLocalHost().getHostAddress();
ev.receptores.add(new String(user));
chat.enviarEvento(ev);
}
catch (UnknownHostException e1)
{
JOptionPane.showMessageDialog(null,
"Ha ocurrido un error en la comunicación. Inténtelo mas tarde");
return;
}
}
});
Comenzaremos creando la clase que implementa los métodos abstractos requeridos, pero
con todos ellos creados como "stubs". Para hacer ésto, agregaremos convenientemente
un nuevo fichero a nuestro proyecto al que llamaremos "Plugin" y que heredará de la
clase "DAbstractPlugin". La IDE se encargará de crear los métodos "stubs" por nosotros.
@Override
public void init() throws Exception
{
versioningEnabled = true;
categoria = DAbstractPlugin.CATEGORIA_COMUNICACION;
descripcion = "Chat con videoconferencia integrada. Permite
mensajes privados y publicos";
ventanaChat = new VentanaPlugin();
ventanaChat.setResizable(true);
ventanaChat.pack();
ventanaChat.setSize(550, 430);
ventanaChat.setTitle(":: Chat - " + DConector.Dusuario + " ::");
ventanaChat.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
ventanaChat.setLocationRelativeTo(null);
}
@Override
public void start() throws Exception
{
ventanaChat.setVisible(true);
}
@Override
public void stop() throws Exception
{
ventanaChat.dispose();
}
@Override
public void enviarEvento(DEvent e)
{
DIChat chat = ventanaChat.obtenerChat();
El método "init" se encargará de inicializar todas las variables de instancia del plugin. En
nuestro caso solo tendremos la variable de instancia asociada a la ventana. También
podremos, opcionalmente, asignar valores a ciertos parámetros de configuración del
plugin, como pueden ser la descripción, la categoría o si queremos o no tener un control
automático de versiones (para poder distribuir las nuevas versiones de nuestro plugin
entre los usuarios de la plataforma de una forma más rápida y sencilla). Es muy
importante, que se asigne siempre la operación por defecto al cerrarse la ventana a
El método "stop" permite poner fin a la ejecución del plugin. En éste caso bastará con
hacer un "dispose" a la ventana. Hay que destacar que el método "stop" solo se llamará al
final de la ejecución de la plataforma, con que es válido descargar de memoria todos los
componentes o asignarlos a null y llamar al recolector de basura explícitamente, puesto
que en ningún caso, tras un "stop" se realizará un "start".
Una vez poseamos el fichero .jar, lo copiaremos y pegaremos en la carpeta "plugin" que
hay dentro del directorio donde tengamos instalada la aplicación cliente. Si hemos usado
alguna biblioteca ajena al framework, deberemos situarla en la carpeta "lib", también
dentro del directorio donde esté instalado el cliente.
El fichero .jar que situemos en la carpeta "plugin" deberá tener el mismo nombre que
nosotros hayamos especificado en el código fuente del plugin (en nuestro caso el fichero
se llamará "chat.jar").
Una vez situados todos los ficheros en los lugares correspondientes, ejecutaremos la
plataforma para ver los resultados obtenidos. Veamos ahora una serie de capturas de
pantalla que ilustren ésto.
Se puede observar como la ventana tiene una barra de herramientas con diversas
características. Ésta barra de herramientas se puede ampliar o reducir tanto como
queramos en funcionalidad. El segundo botón de ésta barra, por ejemplo, tiene el mismo
código fuente que se mostró al final de la fase 3. El último botón se limita simplemente a
cerrar la ventana.
Si abrimos el gestor de plugins veremos como las características que nosotros pusimos al
configurar el plugin se muestran correctamente.
Para distribuir la versión inicial del plugin podremos, por ejemplo, subirlo como fichero a la
plataforma. Las sucesivas versiones, una vez que actualicemos el fichero .jar en uno de
los clientes se distribuirán automáticamente al resto (mostrando previamente un mensaje
de confirmación de descarga de la nueva versión).
Los siguientes casos de uso son los identificados para el sistema. Algunos de ellos, tras
realizar el proceso de análisis y diseño, han pasado a formar parte de los casos de uso de
ciertos plug-ins (como el de chat o el de videoconferencia), aportados por defecto, y otros
se han asociado con el comportamiento general de la aplicación principal. El siguiente
listado tiene como objeto mostrar las principales características perseguidas en el proceso
de desarrollo del proyecto.
Actor: Usuario
Postcondiciones: 1.
Flujo Normal: 3. El sistema muestra al usuario el documento en una nueva
ventana
4. Mientras el usuario no cierre la ventana
a. Si el usuario desea agregar una anotacion se inicia el
caso de uso Anotar Documento
b. Si el usuario desea eliminar una anotacion se inicia el
caso de uso Eliminar Anotacion Documento
c. si el usuario dese imprimir el documento se inicia el
caso de Uso imprimir documento
d. Si el usuario desea guardar el documento se inicia el
caso de uso Guardar Documento
Prioridad: Alta
Actor: Usuario
Iniciador:
Flujo Alternativo: 1.
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Flujo Alternativo:
Incluye:
Actor: Usuario
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Flujo Alternativo: 2. Si existe una carpeta con el mismo nombre y ruta en el servidor, el
sistema da a elegir al usuario entre dos opciones: renombrar el nuevo
fichero o cancelar
a. Se le da un nuevo nombre al documento y volvemos al
paso 3.
b. Termina el caso de uso
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Flujo Alternativo:
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Flujo Alternativo:
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Flujo Alternativo: 3. El usuario selecciona salir sin guardar los cambios. El caso de uso
termina
Excepciones:
Incluye:
Prioridad:
Nombre: Chatear
Precondiciones: -
Postcondiciones: -
Flujo Alternativo: -
Excepciones: -
Incluye: -
Prioridad: -
Postcondiciones: -
Flujo Normal: 1. El sistema avisa el receptor de que el usuario desea iniciar una
conversación privada.
2. El receptor acepta.
3. Mientras el usuario y el receptor mantengan abierta la
conversación:
a. El usuario escribe un texto para enviar al receptor.
b. El sistema envia el texto al receptor.
Excepciones:
Incluye:
Prioridad:
Actor: Usuario
Postcondiciones: -
Flujo Normal: 1. El sistema avisa el receptor de que el usuario desea iniciar una
conversación privada.
2. El receptor acepta.
3. El sistema captura el audio y video del usuario y del receptor y
lo envía al receptor y al usuario.
Excepciones: -
Incluye: -
Prioridad: -
Actor: Usuario
Precondiciones: -
Flujo Normal: 1. El sistema muestra al usuario la lista con todos sus posible
roles
2. El usuario escoge uno de los roles de la lista
3. El sistema actualiza el rol actual del usuario
Flujo Alternativo: -
Excepciones: -
Incluye: -
Prioridad: -
Actor: Usuario
Precondiciones: -
Flujo Alternativo: -
Excepciones: -
Incluye: -
Prioridad: -
Actor: Usuario
Excepciones: -
Incluye: -
Prioridad: -
Actor: Usuario
Flujo Normal: 1. El sistema pregunta al usuario por la ruta hacia el nuevo plugin
en el sistema de archivos local.
2. El usuario introduce la ruta del archivo del nuevo plugin.
3. El sistema copia el nuevo plugin y lo carga.
Excepciones: -
Incluye: -
Actor: Usuario
Flujo Alternativo: -
Excepciones: -
Incluye: -
Prioridad: -
Primera Versión
Inspirados en los SOs Web, tales como podría ser EyeOS, la primera propuesta de
interfaz fue similar a la siguiente:
El objetivo perseguido con este diseño era el de construir una interfaz que hiciese creer al
usuario que se encuentra utilizando un sistema operativo completo.
Segunda versión
Requisitos previos
Se requerirá un SO operativo que soporte la versión de Java 1.5. Ésta versión de Java se
podrá instalar gracias al soporte físico adjunto. Se aportan instaladores para Windows y
Linux (Mac OS X lo tienen preinstalado de serie).
Para los servidores se requiere tener una conexión con un servidor MySQL. Se aportan
instaladores para Windows, Linux y Mac OS X de ésta aplicación.
Los ordenadores donde se instalen las aplicaciones deben tener soporte para conexión en
red y estar conectados permanentemente.
Se aportan scripts específicos para la creación y relleno con datos de prueba para la base
de datos necesaria para el funcionamiento del sistema.
Los servidores tienen adjunto un fichero de configuración. Éste fichero se llama “config” y
es de texto, es decir, se puede abrir con cualquier editor de texto plano. Contiene dos
filas:
La primera fila (“datos”) deberá tener a continuación el path absoluto de la carpeta donde
guardaremos físicamente los documentos del sistema.
La segunda fila (“MIBD”) tiene tres elementos: La dirección IP del ordenador donde se
encuentra la base de datos del sistema instalada, el nombre de usuario para la base de
datos y la contraseña para ese usuario.
VENTANA PRINCIPAL
• Lista de aplicaciones.
• Árbol de usuarios conectados.
• Árbol de documentos compartidos.
Documentos
En el panel central podemos observar los documentos a los que se tiene acceso en, como
mínimo, modo lectura. Para una fácil organización, los documentos aparecen organizados
en forma de árbol, de manera similar a como se estructuran los sistemas de archivos
Aplicaciones
Se muestran las aplicaciones disponibles. Para ejecutar alguna de ellas bastará con hacer
doble click sobre su nombre.
Usuarios
Se muestra un árbol con los usuarios conectados en un momento dado, clasificados por
rol. El árbol lleva asociada una barra de herramientas en su parte inferior que nos permite:
A lo largo del desarrollo del proyecto se han encontrado dificultades de diversa índole. A
continuación se detallan las de mayor relevancia. Hay que destacar que la simplicidad de
uso de la aplicación cliente final entra en contraste con gran dificultad en la resulución de
la mayoría de dificultades encontradas, debido sobre todo al uso de tecnologías
complejas, y a menudo, muy diferentes entre sí.
15. Si un usuario habría un documento para anotar sobre él y éste estaba siendo
anotado por otro usuario que no había guardado los cambios, debía
transmitirse información acerca de las anotaciones producidas. Para realizar
ésto último se ideó un sistema de tokens. Si un usuario posee un token, entonces
puede anotar y tiene las últimas anotaciones realizadas sobre un documento.
Cuando un nuevo usuario desee anotar sobre éste documento, se detecta quién
tiene el token y se realiza una transmisión (mediante RMI) desde éste último de
toda la información del sistema de anotaciones (las anotaciones realizadas, quién
las realizó, las pilas para deshacer o rehacer acciones, etc.). Si el token no existe,
entonces nadie está editando el documento y se recupera directamente desde el
servidor de ficheros, el cuál mantiene persistentemente los ficheros del espacio de
trabajo compartido.
Se van a realizar una serie de pruebas para ver el consumo de recursos cuando está
funcionando el sistema implementado. En éstas se va a ejecutar la aplicación cliente y los
servicios necesarios para el correcto funcionamiento del sistema en un PC.
Para la obtención de los datos se han utilizado las aplicaciones Monitor de Actividad
(monitorización del consumo de memoria) y BigTop ( uso de CPU y tráfico de red).
Consumo de memoria
El primer paso será arrancar los servicios necesarios para la ejecución (JavaSpaces,
Transaction Manager). Con estos servicios arrancados se consumen unos 22 MB de
memoria RAM. Como podemos ver en la captura siguiente:
Uso de CPU
Uso de red
El consumo de red red bajo (salvo en los casos en los que se intercambian documentos,
en cuyo caso el consumo experimenta un pico).
Como podemos ver, incluso en los picos de tráfico de red, el uso de la red no pasa de los
3 KB.
Con los resultados obtenidos vemos que la plataforma que hemos usado así como la
implementación realizada es bastante eficiente, cumpliéndose así uno de los requisitos
iniciales.
11. JavaSpaces. Principles, Patterns and Practice. Eric Freeman, Susanne Hupfer,
Ken Arnold. Addison-Wesley.