138
Tesis de grado de Ingeniería en Informática Extensión del estándar de para proveer servicios de estado persistente con acceso remoto: Análisis, diseño e implementación. Buenos Aires, Argentina. Diciembre 2007

Tesis de grado de Ingeniería en Informática servicios de ...materias.fi.uba.ar/7500/pilardi-tesisingenieriainformatica.pdf · La mayoría de los sistemas de información, ... contemplado

Embed Size (px)

Citation preview

Tesis de grado de Ingeniería en Informática

Extensión del estándar ���

de ����� para proveer

servicios de estado persistente con acceso remoto: Análisis, diseño e

implementación.

�� �� � ������ ���� � �������������� ��� �� ����� �� ����

Buenos Aires, Argentina. Diciembre 2007

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

3

INFORMACIÓN DE CONTACTO

PABLO MAXIMILIANO ILARDI: [email protected] O [email protected]

MARÍA FELDGEN: [email protected] O [email protected]

4

Abstract Pablo M. Ilardi

5

RESUMEN

Muchas de las aplicaciones distribuidas tienen requerimientos de persistencia y se encuentran

desarrolladas en lenguajes de programación orientados a objetos (POO) tales como Java. Una de las

arquitecturas, CORBA de OMG, define un estándar de persistencia denominado ����������� ���������� (PSS), siendo una de sus alternativas con un lenguaje PSDL. En este trabajo se analizó el estado

del arte de la persistencia, en particular en el marco de las aplicaciones CORBA desarrolladas en

lenguaje Java y la especificación del servicio PSS. Para estas aplicaciones, se diseñó y construyó un

servicio de estado persistente, junto con un compilador de lenguaje PSDL. Este servicio, además de la

persistencia, permite compartir objetos persistentes entre servants ubicados en distintas máquinas o

procesos, característica que no está prevista en la especificación PSS de CORBA. El compilador y el

servicio construidos se caracterizan por ser extensibles y configurables, soportando distintas formas de

lograr persistencia. Finalmente, los resultados obtenidos fueron comparados con otros servicios PSS.

Palabras clave: Persistencia, OMG, CORBA, Servicio, POO, Java, PSS, PSDL, Compilador, Servant,

Conector, OODB

ABSTRACT

A large number of distributed applications require persistence. Many of these applications are

developed using object oriented programming languages (OOP) like Java. The OMG CORBA

architecture defines the ���������� ���� ������ (PSS) as a standard for persistence. One of the options

is using a language PSDL. In this work, the state of art of persistence was analyzed specifically in the

context of CORBA applications developed in Java, altogether with the PSS specification itself. For these

applications, a persistent state service with a PSDL language compiler was designed and built. The

service includes in addition of persistence, the sharing of persistent objects with servants existing in

different machines or processes, a feature which is not provided by the PSS specification. The built

compiler and service are characterized for being extensible and configurable, supporting different ways

for persisting objects. Finally, the work’s results were compared to other existing PSS services.

Keywords: Persistence, OMG, CORBA, Service, POO, Java, PSS, PSDL, Compiler, Servant, Connector,

OODB

6

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

7

CONTENIDO

Introducción ................................................................................................................... 9

Introducción a POO - Programación Orientada a Objetos ............................................ 13

Introducción a CORBA................................................................................................... 21

Servicio de Estado Persistente CORBA - PSS ................................................................. 31

Implementación del servicio de estado persistente CORBA - PSS................................. 47

Comparación con otras implementaciones del servicio de estado persistente de CORBA ........................................................................................................................ 121

Trabajos adicionales ................................................................................................... 131

Índice de Gráficos ....................................................................................................... 133

Glosario ...................................................................................................................... 135

8

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

9

INTRODUCCIÓN

os juegos, sistemas de bases de datos distribuidas, multimedia, aplicaciones gráficas y los sistemas

distribuidos en general, almacenan información que requiere persistencia. La mayoría de los

sistemas de información, requieren de algún soporte para mantener su estado en forma

persistente. Persistencia en la programación orientada a objetos (POO), significa almacenar los objetos y

por consiguiente el valor de sus atributos para uso futuro.

El soporte de persistencia no es trivial, ya que la representación de un objeto en memoria puede

variar en tamaño y estructura de una plataforma a otra. Además, no está soportado en forma nativa por

todos los lenguajes de programación (por ejemplo, C++), plataformas de desarrollo y sistemas operativos.

Por consiguiente, es necesario el uso de frameworks, como por ejemplo, CORBA, para su

implementación.

CORBA (Common Object Request Broker Architecture) o ������������ �� ���� �� ��� �� �������� �� ������ [CR01] es la propuesta del OMG (Object Management Group) para la construcción de

software interoperable, portable y reusable. OMG define una arquitectura de software detallada, que

consta de interfases claramente definidas. Esta arquitectura, llamada OMA (Object Management

Architecture) [OM0], está dividida en dos modelos fundamentales. El primero, llamado ���� � ������ ������ (�� ������ ����), permite el desarrollo de aplicaciones distribuidas basadas en un ��� ���� ������ � ������ u ORB (Object Request Broker). El segundo modelo, llamado ���� �� ������ ���

(������ �� ����), define un marco para el desarrollo de las aplicaciones junto a un conjunto de

interfases estandarizadas llamadas �������� �� ������ (������ ��������) que utilizan la ORB.

Uno de los servicios que CORBA provee, tiene el objeto de facilitar y unificar la forma de hacer

persistentes a los objetos utilizados por los servants [CRO1], por medio de una interfase común para el

manejo de persistencia. Los servants son aquellos objetos que están bajo el control de la ORB. Todo

servant tiene una interfase definida en lenguaje IDL (Interface Definition Language o �� ����� ����� � ���� �� � �������� y puede ser accedido en forma remota por medio de los mecanismos que la ORB

provee a tal fin.

El servicio de CORBA que da soporte de persistencia de objetos se denomina: ������� �� !������������ �� (P"#$%$&"'&S&(&"

S"#)%*") PSS [PS0]. Este servicio no intenta ser una interfase a una base de

datos orientada a objetos OODBMS (Object Oriented Database Management System), sino que provee

una forma estándar de persistencia de objetos. No incluye los dos conceptos fundamentales de OODBMS

como ser las transacciones y las consultas [BgVaDk]. Su fundamento, es la separación de intereses

(+,-./.0123 24 5235,/3+) que está presente en la arquitectura y las especificaciones de servicios CORBA.

De esta forma, si se tienen requerimientos transaccionales no serán implementados por este servicio,

sino que este servicio se conectará o comunicará con el servicio de transacciones CORBA [TS01].

El modelo de trabajo que propone PSS, es similar al modelo general propuesto por CORBA. Los

objetos persistidos por PSS, son denominados 267,02+ .89.5,3.:2+ (+02/.;, 267,50+). Todo objeto

persiste en un .89.5<3 (+02/.;, =29,). Los almacenes existen en /,-2+102/12+. PSS provee un lenguaje

para definir estos objetos y almacenes, llamado PSDL (P>?@A@B>CBSBDB>

D>E ACABAFC

LDCGHDG>), que es una

extensión al lenguaje IDL. Los servants que utilicen PSS, pueden hacer persistentes a los objetos de dos

formas: por su definición en lenguaje PSDL, o por medio de objetos comunes definidos en el lenguaje de

programación que se utilice. La segunda forma de persistencia se la denomina IJKLMLNJOPMQ NKQOLIQKJONJREn ambos casos, los objetos persistidos por los servants, son sólo conocidos por el servant que los define,

y no son exportados a la ORB, lo que implica que los objetos almacenados no pueden ser accedidos en

forma remota.

L

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

10

Para usar el lenguaje PSDL se requiere de un compilador que traduzca las definiciones PSDL en

las definiciones del lenguaje de programación que se utiliza. En cambio, la persistencia transparente,

permite persistir objetos directamente en el lenguaje nativo, pero a costa de perder muchas

funcionalidades que solo están disponibles en las definiciones PSDL.

Si bien PSS es un estándar cuya última revisión es del año 2002, no existen muchas

implementaciones del mismo, dado que no se trata de una de las especificaciones más difundidas.

El problema a resolver es: ¿Cómo hacer uso de objetos que requieran estado persistente en

CORBA? Se deben analizar las soluciones existentes, viendo que alternativas son viables de construir. Las

especificaciones son ambiguas en algunas definiciones, en las cuales se pierde la idea de interfase de

servicio CORBA. En particular, sucede cuando se trata de soportar dos modelos de persistencia, como lo

son el transparente y el de definición por PSDL, por medio de una misma interfase de servicio.

Sin embargo, la construcción de un servicio siguiendo la especificación PSS se plantea como una

idea atractiva y desafiante. Es atractiva, porque permite tener una interfase o modelo estándar para

solucionar este problema no trivial, lo cual es algo muy útil y valioso. Es desafiante, porque se trata de un

problema complejo con muchos matices a considerar, tales como la construcción de un compilador del

lenguaje PSDL, proveer persistencia transparente sin uso de un lenguaje nuevo, definir dónde y cómo

almacenar el estado que define a los objetos utilizando una base de datos u otro medio, evaluar cómo se

podría extender el servicio para que soporte el acceso remoto de los objetos almacenados, no

contemplado en el estándar actual, etc.

OBJETIVOS

Se realizó un estudio del estado del arte de la persistencia en objetos, focalizado principalmente

en CORBA y el lenguaje de programación Java.

Se realizó un análisis de la especificación del servicio de estado persistente (PSS) CORBA,

evaluando sus posibles usos, ventajas y desventajas. Se analizaron las distintas alternativas que plantea el

estándar, considerando los beneficios y las desventajas de la ������������ ������������. Se evaluaron las

diferentes estrategias a seguir frente a ambigüedades en las definiciones. Se evaluaron las distintas

herramientas que pueden utilizarse para mantener el estado persistente, y su posible uso en un servicio

de persistencia.

Se diseñó y construyó un servicio de persistencia, que soporta el estándar CORBA, utilizando el

lenguaje de programación Java y patrones de diseño adecuados para este fin [GA0]. Se proveyó de un

mecanismo básico que permite a dos o más servants acceder en forma remota a un repositorio definido

en otra máquina, pudiendo así acceder a objetos almacenados definidos por otros servants, extendiendo

el servicio que propone el estándar. El servicio de persistencia que se construyó, soporta definiciones

PSDL, junto con las funcionalidades que este lenguaje provee. Para ello, fue necesaria la construcción de

un compilador [CooperRice00] PSDL que genera código Java que es compatible con el servicio construido.

Finalmente, se realizó una comparación entre otras implementaciones del servicio de

persistencia de CORBA y la construida.

ETAPAS DEL TRABAJO

1. REVISIÓN DEL MARCO TEÓRICO

Esta etapa del trabajo se focalizó en obtener un conocimiento del marco teórico necesario para

la realización de este trabajo. Este marco incluye CORBA, Persistencia de Objetos Java y Servicios de

CORBA.

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

11

2. ANÁLISIS

Se realizó un análisis completo de la especificación del servicio de estado persistente de CORBA. Se evaluó el aporte del servicio al desarrollo de aplicaciones CORBA, valorizando sus beneficios y desventajas. Se analizaron los requerimientos para la construcción de un servicio CORBA que cumple con este estándar y permite compartir un repositorio por más de una ORB. Se consideraron las diferentes herramientas, lenguajes de programación y � ��������� que podrían utilizarse en la construcción. Esta etapa definió los requerimientos del servicio a construir.

3. DISEÑO

En esta etapa se diseñó una solución que cumple con los requerimientos relevados durante la

etapa de análisis. Esta solución incluye el diseño de un compilador de lenguaje PSDL a Java, y varios

conectores para el servicio. El diseño obtenido es lo suficientemente amplio, como para permitir futuras

extensiones al servicio en forma de nuevos conectores para distintos tipos de repositorios, tales como

archivos o bases de datos relacionales. La solución también contempla el soporte para su funcionamiento

en distintas ORBs y no sólo para una en particular. El diseño hace uso de distintos patrones de diseño

adecuados para resolver los problemas que se presentaron.

4. CONSTRUCCIÓN

En esta etapa se construyeron los artefactos definidos en la etapa de diseño. La construcción se

realizó en forma incremental, y se proveyeron pruebas unitarias que permiten futuras extensiones al

servicio con otros conectores. Esta forma de construcción se realizó en dos iteraciones entre la etapa de

diseño y construcción, focalizando la primera en el compilador y las herramientas. Entre los artefactos

construidos se encuentran el compilador y el conector que permite compartir repositorios entre distintas

ORBs. También se construyeron herramientas para la generación de código y junto con sus respectivas

pruebas unitarias.

5. EVALUACIÓN DEL SERVICIO

Esta etapa se evaluó el servicio construido en una aplicación CORBA. También se compararon las

funcionalidades y las formas de uso de otras implementaciones de este mismo servicio. El objetivo de

esta etapa fue poner un marco de referencia de este trabajo en relación con otros servicios.

6. REDACCIÓN DEL INFORME Y CONCLUSIONES

Esta epata consistió en la realización de este informe final del trabajo. Se adjuntan también un CDROM con el código fuente de todas las construcciones realizadas para este trabajo, referencias en formato digital, herramientas utilizadas, y este documento en formato digital.

Tesis de Grado – Ingeniería en Informática Pablo M. Ilardi

12

REFERENCIAS

OM0 – “������ �������� ��� �������� �����”. Third Edition June 13, 1995. Richard Mark Soley,

Ph.D. (ed.) Christopher M. Stone

BM0 – “��������������� �������� ������������”. Meyer, Bertrand. Prentice Hall. (1997) ISBN 0-13-

629155-4.

CR01 – “���� ������ ������� ������ ��� ��������� ���� ������������� OMG”.

http://www.omg.org/technology/documents/corba_spec_catalog.htm

PS0 – “Persistent State Service”, V2.0 OMG 2002. http://www.omg.org/cgi-bin/doc?formal/02-09-06

GA0 – “Design Patterns: Elements of Reusable Object-Oriented Software”, Addison-Wesley

Professional Computing Series - by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,

January 15, 1995.

BgVaDk – “Java Programming with CORBA, Advanced Techniques for Building Distributed

Applications”, 3rd Edition – Gerald Brose, Andreas Vogel, Keith Duddy, Wiley 2001.

TS01 – “Transaction Service Specification”, OMG 2003.

http://www.omg.org/technology/documents/corbaservices_spec_catalog.htm

EisenbergMelton01 – “SQL: 1999, formerly known as SQL3”. Andrew Eisenberg, Sybase, Concord,

MA 01742 [email protected]; Jim Melton Sandy UT 84093 [email protected]

Ramakanth01 – “Object-Relational Database Systems - The Road Ahead”; Ramakanth S.

Devarakonda; http://www.acm.org/crossroads/xrds7-3/ordbms.html

CooperRice00 – “Engineering A Compiler”, Keith Cooper - Rice University, Houston, Texas; Linda

Torczon - Rice University, Houston, Texas. http://www.cs.rice.edu/~keith/

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

13

INTRODUCCIÓN A POO - PROGRAMACIÓN ORIENTADA A OBJETOS

Elementos principales .............................................................................................. 15

Clase ..................................................................................................................... 15

Objeto .................................................................................................................. 15

Atributo ................................................................................................................ 15

Método ................................................................................................................ 16

Mensaje ................................................................................................................ 16

Características:......................................................................................................... 16

Encapsulamiento .................................................................................................. 16

Herencia ............................................................................................................... 16

Abstracción ........................................................................................................... 16

Polimorfismo ........................................................................................................ 17

Ciclo de Vida ............................................................................................................ 17

Persistencia .............................................................................................................. 17

Java .......................................................................................................................... 18

Patrones de Diseño .................................................................................................. 18

Referencias .............................................................................................................. 20

14

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

15

a Programación Orientada a Objetos (POO) es un paradigma de programación, así como también lo

son el Estructurado y el Funcional que preceden a POO. Un paradigma de programación es un estilo

de programación que conlleva una cierta filosofía. Esta filosofía define una serie de premisas que

son las llamadas mejores prácticas (best practices). Existen múltiples lenguajes de programación para un

paradigma dado. El paradigma define las herramientas que los lenguajes de programación deben proveer

para permitir el desarrollo de aplicaciones de la forma más natural posible [HJ0].

Si bien la POO tiene ya muchos años no fue recién hasta los comienzos de los 90 donde se

popularizó y comenzó a ser utilizado como paradigma de desarrollo de software.

Muchas de las buenas prácticas en otros paradigmas son igualmente válidas en POO, como por

ejemplo la Modularización.

La Modularización tiene como objetivo separar un sistema en módulos, simplificando su diseño,

implementación y entendimiento, así como también facilitar su evolución y mantenimiento. El grado de

efectividad de esta técnica depende del criterio utilizado para descomponer el sistema en módulos [PD0].

La modularización está presente en POO principalmente en las clases. Para algunos autores, las clases

debieran ser los únicos módulos existentes en un sistema [BM0]. En algunos lenguajes, tales como ADA o

Java, existe un concepto que también se podría asociar a los módulos, que es el de paquete. Estos

paquetes se utilizan para agrupar clases, pero no son estrictamente necesarios en el mundo de POO.

La idea básica en POO es resolver un problema planteándolo como un modelo del mundo real en

el que existen objetos que interactúan, a diferencia del modelo de desarrollo estructurado donde se trata

de resolver el problema descomponiéndolo en un conjunto de funciones.

Existen múltiples lenguajes de programación orientados a objetos, entre los que se encuentran:

Smalltalk, Object Pascal, ADA, C++, Java, Eiffel, C#, entre muchos otros.

ELEMENTOS PRINCIPALES

Todo lenguaje que se base en POO debe proveer de algunas herramientas o construcciones

básicas como son las: clases, objetos, métodos, mensajes y atributos.

CLASE Una clase define o modela las características de una “cosa”. Las características incluyen los

atributos o propiedades junto con su comportamiento (métodos). El típico ejemplo es el de un Animal, el

cual tiene propiedades tales como: edad, ubicación, forma de alimentación, etc. su comportamiento

podría estar definido por: alimentarse, dormir, procrear, etc.

OBJETO Un objeto es un individuo de una clase. Los individuos en POO se denominan instancias. En el

ejemplo de los animales un objeto seria un Animal concreto, por ejemplo, un animal en el zoológico, que

tiene un estado determinado, como por ejemplo, 2 años de vida.

ATRIBUTO Un atributo de una clase es análogo a una variable en la programación estructurada, con la

diferencia del contexto donde se define. Los atributos son accesibles desde una instancia de una clase u objeto, mientras que las variables son accesibles desde el contexto en el que se definieron. En la programación más estricta todo es un objeto, o sea un entero o un string es un objeto. Los atributos son tipados. Se puede tener un atributo de tipo Animal y otro de tipo Entero, o un atributo de tipo Objeto, que podría referenciar tanto una instancia de un Animal como de un Entero. Los valores de los atributos de una instancia de un objeto determinan su estado.

L

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

16

MÉTODO

Los métodos de POO son análogos a las funciones de programación estructurada. La diferencia

fundamental es que las funciones o procedimientos se ejecutan en un contexto global, mientras que los

métodos se ejecutan en el contexto del objeto en el cual se llamó al método. Se dice que el conjunto de

métodos de un objeto determinan su comportamiento.

MENSAJE Se entiende por mensaje, a la invocación a un método de un objeto. Esta invocación se realiza

con los parámetros aceptados por el método. El mensaje es el equivalente a una llamada a una función en

el paradigma estructurado.

CARACTERÍSTICAS:

La POO se basa en cuatro características: Encapsulamiento, Herencia, Abstracción y

Polimorfismo.

ENCAPSULAMIENTO El encapsulamiento es una forma de ocultar y agrupar información de forma tal de unificar el

acceso a la misma. El encapsulamiento es generalmente equiparado a “ocultamiento de la información”.

Según [BM0]:

“Debiera ser posible para el autor de una clase especificar los atributos que estarán disponibles

para todos, ninguno o algunos de los clientes de la clase”.

Pero ocultar información, no se refiere solamente a los aspectos físicos de la misma. Por

ejemplo, que una clase Lista utilice un array para almacenar sus elementos no debiera ser visible para un

usuario de esta clase. Los usuarios de la clase debieran acceder a los elementos de una Lista por medio

las operaciones de la misma, por ejemplo: obtenerElementoX. Esto garantiza que los clientes de las clases

se comuniquen por medio de su interfase o contrato sin depender de la implementación de la misma.

HERENCIA La herencia es una característica que permite agrupar funcionalidades comunes en lugares

comunes, es la forma más natural de reutilización en POO. Al igual que en el mundo real, la herencia

refleja que una clase hereda de otra. En POO las clases pueden heredar de otras clases sus características

y comportamiento. Por ejemplo, en la familia de los animales, algunos son de tipo carnívoro y otros de

tipo herbívoro. Una forma de herencia se podría definir como que las clases Carnívoro y Herbívoro

heredan de la clase Animal. Esto implica que ambas tiene las mismas características que cualquier animal

como la edad, peso, etc. pero además cada una tiene sus características propias, por ejemplo, un

Carnívoro se alimenta de distinta forma que un Herbívoro.

Existen dos tipos distintos de herencia: la herencia simple y la herencia múltiple. La primera,

implica que una clase solo puede heredar a lo sumo de una única clase, mientras que la segunda, permite

que una clase pueda heredar de más de una clase al mismo tiempo. No todos los lenguajes de

programación soportan el segundo tipo de herencia, por ejemplo, C++ la soporta y Java no lo hace.

La herencia incluye un nuevo concepto llamado redefinición. Básicamente significa que una clase hija puede redefinir una característica de la clase padre, por ejemplo un atributo o un método.

ABSTRACCIÓN La abstracción permite que dos objetos sean tratados de forma uniforme sin que importe cual

clase sea. Por ejemplo, dos animales, uno Carnívoro y otro Herbívoro como un Perro y un Caballo, pueden

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

17

interpretar un mensaje como dormir. Para quien envía el mensaje dormir, es indistinto si es un Perro o un Caballo el que lo recibe.

POLIMORFISMO El polimorfismo es una de las características esenciales de POO y está muy asociada a la

redefinición y a la abstracción. Cuando un Animal recibe el mensaje alimentarse, que debe hacer,

depende básicamente del tipo de Animal. Los Carnívoros consumirán carne y los Herbívoros vegetales.

Desde el punto de vista del cliente o usuario de una clase de tipo Animal, el mensaje enviado es el mismo,

pero los resultados son muy distintos.

La forma de conseguir un comportamiento similar en programación estructurada, sería mediante

una condición. Por ejemplo, se tiene la función alimentar(animal, alimento): dentro del código fuente de

la función se debe preguntar ‘si animal es carnívoro alimentar_carne() sino alimentar_vegetal()’. Mientras

que en objetos sólo sería necesario escribir: ‘animal.alimentar(alimento)’. El polimorfismo es la propiedad

que permite que un objeto, dado un mismo mensaje responda de manera distinta en función del tipo de

objeto.

CICLO DE VIDA

Para utilizar una instancia de una clase es necesario tener una referencia al objeto, o construir

una instancia de la misma. Para construir una instancia de una clase, se utilizan métodos especiales de las

clases llamados ‘constructores’. Todo constructor devuelve como resultado una instancia de la clase

donde está definido. Cuando la instancia es creada se dice que es referenciada por la variable a la que se

asignó. Una vez creada la instancia de la clase, ya está lista para ser utilizada mediante sus métodos y

atributos.

Toda instancia que está creada ocupa un espacio en memoria. La forma de liberar este espacio

depende principalmente del lenguaje de programación utilizado. Cuando se libera esta memoria, se dice

que es el objeto se destruye.

Los primeros lenguajes de POO requerían que el usuario de los mismos se encargara de alocar y

liberar la memoria utilizada por las instancias de los objetos. Por ejemplo, en C++ es necesario llamar

explícitamente al operador delete para liberar la memoria que el objeto utiliza. Algunos lenguajes más

modernos, liberan la memoria que utilizan los objetos de forma automática, este es el caso de Java, o C#.

PERSISTENCIA

Muchas aplicaciones desarrolladas utilizando POO requieren mantener los objetos utilizados

entre distintas sesiones o ejecuciones de la aplicación. Los objetos que deben ser compartidos entre

distintas sesiones se dicen que son Persistentes, mientras que los que no lo son se dice que son

Transientes. Los objetos no pueden ser mantenidos en memoria entre distintas sesiones, ya que la

memoria es liberada cada vez que se cierre la aplicación donde se utilizan los objetos. Es necesario

almacenar los objetos en algún repositorio permanente.

Si el estado de un objeto está definido por el valor de sus atributos, se puede asumir que para

recuperar un objeto de un repositorio permanente, es suficiente con almacenar los valores de sus

atributos. Pero esto no es suficiente cuando estos atributos son otros objetos con sus propios atributos o

cuando estos atributos referencian a otros objetos que pueden referenciar al objeto inicial que se quería

presistir. Los objetos en memoria pueden constituir un grafo de dependencias complejo que puede incluir

ciclos definidos por referencias cruzadas. Un mecanismo de persitencia que pueda almacenar en forma

automática el objeto junto con estas dependecinas cruzadas, se dice que soporta persistence closure o

persitencia cerrada [BM0].

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

18

Cuando los objetos necesitan ser compartidos entre distintas sesiones, y además ser

compartidos entre distintas aplicaciones, se agrega la necesidad de la existencia de una bases de datos u

objetos. Las bases de datos pueden ser de tipo relacional (RDBMS) que require un mecanismo de

traducción entre el modelo de objetos y el modelo relacional, o pueden ser sistemas de bases de datos

orientadas a objetos (OODBS) que soportan los objetos en forma nativa.

Algunos mecanismos de persitencia proveen soluciones para la evolución del esquema [BM0]. La

evolución del esquema se aplica cuando se modifica una clase y para dicha clases ya existen instancias de

objetos con la estructura original de la clase almacenadas en un repositorio. Cuando se recuperan dichas

intancias puede ocurrir que ya no esté disponible la clase original y no sea posible recuperarla con la

nueva clase ya que no existe algún atributo de la misma. A este problema se lo denomina object

mismatch o incompatibilidad de objeto. La incompatibilidad de objetos no ocurre solamente cuando se

recupera un objeto de un repositorio donde persiste, también puede ocurrir cuando se transmite un

objeto por la red, y el receptor del mismo posee una versión distinta de la clase a la que pertenece el

objeto transmitido.

La persistencia de los objetos no está ligada directamente a la POO, cada lenguaje puede proveer

o no, herramientas que permitan la persistencia de los objetos utilizados.

JAVA

Java es un lenguaje de programación orientado a objetos cuya sintaxis deriva de C y C++, pero

que provee un modelo de objetos más simple que C++. Java fue creado por James Gosling [JavaTech01] y

por la empresa SUN en el año 1995, como parte de una plataforma llamada Java Virtual Machine o

Máquina Virtual Java. Desde entonces ha evolucionado hasta su versión 6. La máquina virtual permite

que un programa escrito y compilado en Java pueda ejecutarse en cualquier sistema operativo y

arquitectura sin ser modificado. El sistema operativo requiere tener una implementación de la máquina

virtual instalada. Java se compila en código de bytes o bytecode, y no en código binario. El bytecode es la

representación interna que entiende la máquina virtual, y es lo que permite la independencia de

arquitectura y sistema operativo.

Una de las características principales de Java y que lo diferencian fundamentalmente de C++, es

el manejo de memoria. Java provee manejo automático de memoria, lo que implica que el programador

no debe encargarse de pedir y liberar la memoria necesaria para la creación y destrucción de los objetos.

Esta característica se implementa por medio de automatic garbage collection o recolección automática

de basura [HoskingMoss01]. Este mecanismo permite que un objeto sea eliminado cuando no existan

referencias accesibles a él desde los objetos activos. La máquina virtual es la encargada de llevar el

control de las referencias a los objetos y de administrar la memoria utilizada.

Existen algunas otras diferencias importantes con C++ [TateB01]. Java no permite herencia

múltiple de clases, aunque si permite herencia múltiple de interfases. Las interfases son definiciones de

comportamientos que pueden tener o implementar las clases, pero sin su implementación. Son análogas

a las clases abstractas con todos sus métodos abstractos de C++. En Java todos los métodos son

potencialmente polimórficos, mientras que en C++, un método debe ser declarado virtual para que pueda

comportarse en forma polimórfica. Java no posee destructores ni operadores redefinibles por el

programador. En Java las clases son definidas en paquetes y un paquete puede contener múltiples clases

y sub paquetes.

PATRONES DE DISEÑO

El diseño de sistemas en lenguajes orientados a objetos no es una tarea simple, se deben

considerar múltiples factores que permitan reutilización, bajo acoplamiento y alta cohesión de las clases

que conformen el sistema.

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

19

Generalmente, el mejor diseño no se obtiene en un solo paso, sino a través de iteraciones que lo

refinen. Cuando se plantea diseñar una solución a un problema, se trata de reutilizar soluciones

anteriores buscando analogías a otros problemas similares. Muchos de los problemas que se plantean al

realizar un diseño, fueron afrontados por otros diseños anteriores de la misma u otras personas. Las

soluciones a estos problemas recurrentes siguen patrones de comunicación y estructuras de clases

comunes.

Estos patrones son los llamados Design Patterns o Patrones de Diseño que permiten reutilizar

diseños y arquitecturas exitosas. Un patrón de diseño fue definido por Erich Gamma [GA01] como:

“Un patrón de diseño nombra, motiva y explica en forma sistemática un diseño general que

soluciona un problema recurrente en sistemas orientados a objetos. Describe el problema, da su solución,

y define cuando es aplicable, determinando las consecuencias de la solución. También da ejemplos y

lineamientos para su implementación. La solución es un esquema general de objetos y clases que

solucionan el problema. La solución se debe adaptar e implementar para resolver el problema en el

contexto particular”.

Los patrones de diseño se popularizaron a partir de la tesis doctoral de Erich Gamma [GA02], que

luego fue ampliada con nuevos patrones y publicada en el libro “Design Patterns: Elements of Reusable

Object-Oriented Software” [GA01] o “Patrones de Diseño: Elementos de Software Orientado a Objetos

Reutilizable”.

Algunos de los patrones de diseño más difundidos son: Abstract Factory, Adapter, Composite,

Decorator, Factory Method, Observer, Strategy, Template Method, etc.

Introducción a POO – Programación Orientada a Objetos Pablo M. Ilardi

20

REFERENCIAS

AD0 – Armstrong, Deborah J. “The Quarks of Object-Oriented Development”. Communications of the

ACM 49 (February 2006): 123-128. ISSN 0001-0782.

BM0 – Meyer, Bertrand. “Object-Oriented Software Construction”. Prentice Hall PTR, 1997, 2da

Edición, ISBN 0136291554.

CO0 – “Software engineering: Report of a conference sponsored by the NATO” Science Committee.

Peter Naur, Brian Randell (eds.) Garmisch, Germany, 7–11 October 1968, Brussels, Scientific Affairs

Division, NATO (1969).

HJ0 – John Hunt. “Samlltalk and Object Orientation: An Introduction”. JayDee Technology Ltd,

Hartham Park Corsham, Wiltshire, SN13 0RP United Kingdom.

PD0 – David Parnas, "On the Criteria to Be Used in Decomposing Systems Into Modules".

Communications of the ACM in December, 1972.

VRH0 –Peter Van Roy, Seif Haridi, “Concepts Techniques and Models of Computer Programming”. The

MIT Press, Cambridge, Massachusetts. London, England 2004. ISBN 0-262-22069-5

JavaTech01 – “Java Technology: The Early Years", by Jon Byous.

http://java.sun.com/features/1998/05/birthday.html

HoskingMoss01 – “Protection traps and alternatives for memory management of an object-oriented

language”. Antony L. Hosking J. Eliot B. Moss. Object Systems Laboratory Department of Computer

Science University of Massachusetts Amherst, MA 01003.

ftp://ftp.cs.purdue.edu/pub/hosking/papers/sosp93.pdf

TateB01 – "Beyond Java", by Bruce A. Tate. O'Reilly, September 2005. ISBN 0-596-10094-9

GA01 – “Design Patterns: Elements of Reusable Object-Oriented Software”, Addison-Wesley

Professional Computing Series - by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,

January 15, 1995.

GA02 – “Object-Oriented Software Development based on ET++: Design Patterns, Class Library,

Tools”. Erich Gamma PhD thesis, University of Zurich Institut für Informatik, 1991.

Introducción a CORBA Pablo M. Ilardi

21

INTRODUCCIÓN A CORBA

OMG y CORBA _______________________________________________________ 23

OMA _______________________________________________________________ 23

Object Services _____________________________________________________ 23

Common Facilities __________________________________________________ 23

Domain Interfases __________________________________________________ 24

Principios de Diseño en CORBA__________________________________________ 24

Características de CORBA ______________________________________________ 24

IDL Interface Definition Language ______________________________________ 24

Pasaje de Parámetros: _______________________________________________ 25

Mapeos de lenguajes (Language Mappings) ______________________________ 25

Invocación de Operaciones y facilidades de despacho (dispatching) ___________ 25

Object Adapters (OA) ________________________________________________ 26

Protocolo Inter-ORB _________________________________________________ 26

Un request en CORBA _________________________________________________ 27

Object References __________________________________________________ 27

Adquisición de OR ________________________________________________ 27

Proceso para la invocación de un request ________________________________ 27

Las características de la invocación _____________________________________ 27

Contenedor de Componentes CORBA ____________________________________ 28

Referencias _________________________________________________________ 29

22

Introducción a CORBA Pablo M. Ilardi

23

n Sistema Distribuido es un conjunto de computadoras independientes que se presentan al

usuario del sistema como un único sistema. Desde la perspectiva de hardware, las máquinas o

computadoras son autónomas, pero desde el punto de vista del software, el sistema se ve por el

usuario como un todo [Tanenbaum01].

La construcción de este tipo de sistemas plantea desafíos que no se presentan en los Sistemas

Centralizados. Las computadoras que se interconectan son heterogéneas y pueden tener arquitecturas

diversas. La comunicación conlleva latencia y fallos que deben ser contemplados. Esta problemática

impone la necesidad de un modelo de abstracción que simplifique la construcción y diseño de los

Sistemas Distribuidos [TariBukhers01]. La especificación CORBA de OMG provee un modelo para estos

problemas.

OMG Y CORBA

OMG (Object Management Group) o Grupo de Administración de Objetos, es una organización o

consorcio internacional fundado en 1989 que trata de establecer los lineamientos y modelos para el

desarrollo de aplicaciones reusables, portables e interoperables en ambientes distribuidos heterogéneos,

bajo un modelo de objetos e interfases claramente definidas. OMG ha recibido un gran apoyo de la

industria, transformándose en el consorcio de software más grande del mundo.

CORBA (Common Object Request Broker Arquitecture) o Arquitectura Común para el Agente de

Pedidos de Objetos [CR0] es el núcleo del modelo de arquitectura propuesto por OMG, llamado OMA.

OMA

Esta arquitectura, llamada OMA (Object Management Architecture) [OM0], está dividida en dos

modelos fundamentales. El primer modelo, llamado Modelo Central de Objetos (Core Object Model),

permite el desarrollo de aplicaciones distribuidas basadas en un Agente de Pedidos a Objetos u ORB

(Object Request Broker). Se trata de un modelo con definiciones abstractas que no poseen ninguna

implementación y permiten interpretar el modelo de objetos e interfases. Este modelo fija las bases para

CORBA. Está enfocado en el diseño e implementación de la ORB y no en la construcción de aplicaciones.

CORBA es una especialización de los conceptos definidos en este modelo, llevando las definiciones

abstractas a construcciones concretas.

El segundo modelo, llamado Modelo de Referencia (Reference Model), define a la ORB junto a un

grupo de interfases estandarizadas, como el núcleo para la construcción de aplicaciones distribuidas.

Estas interfases son: los Servicios de Objetos (Object Services) [COS0], las Utilidades Comunes (Common

Facilities) [CCF0] y las Interfases de Dominio (Application Domain Interfases). El modelo de referencia es el

utilizado por los desarrolladores de aplicaciones.

OBJECT SERVICES

Los servicios de objetos (Object Services) son un conjunto de interfases de servicios y objetos que

soportan operaciones básicas para la implementación y utilización de objetos. Estos servicios permiten la

construcción de aplicaciones distribuidas independientes del dominio. Los servicios definen operaciones

genéricas que se aplican a objetos de cualquier dominio, por ejemplo acceder a un objeto por algún

identificador. Existen servicios, tales como Naming Service o Trading Service, que proveen referencias a

objetos que permiten accederlos desde cualquier ubicación. Algunos de los servicios de objetos son:

Naming Service [NS01], Persistence State Service [PSS01], Transaction Service [TSS01], etc.

COMMON FACILITIES

U

Introducción a CORBA Pablo M. Ilardi

24

Las utilidades comunes (Common Facilities), son también servicios u objetos, pero no esenciales

para la construcción de aplicaciones, como los servicios de objetos. Su existencia permite que diferentes

aplicaciones puedan beneficiarse compartiéndolos, sin tener que implementar su funcionalidad en cada

aplicación. Un ejemplo de estas utilidades son los servicios para el manejo de correo electrónico.

DOMAIN INTERFASES Las interfases de dominio corresponden a grupos de interfases generalizadas para dominios de

aplicación particulares, tales como: telecomunicaciones, internet, salud, etc.

PRINCIPIOS DE DISEÑO EN CORBA

OMA define lineamientos que se deben respetar en el diseño de interfases de servicios de CORBA:

• Separación de la interfase de su implementación.

• Las referencias a objetos deben ser tipadas por interfases.

• Los clientes sólo dependen de interfases, no de implementaciones.

• Las interfases permiten herencia múltiple.

• Para especializar o extender funcionalidades se utilizan subtipos.

• Se asume que las implementaciones de los servicios son eficientes al cumplir su cometido, de forma tal que la eficiencia no sea un problema para los clientes.

CARACTERÍSTICAS DE CORBA

La ORB se puede interpretar como un canal de comunicación que transporta pedidos y

respuestas desde y hacia objetos en cualquier ubicación, sin importar como estén implementados. La

noción de transparencia es fundamental en CORBA. Un objeto puede ser invocado independientemente

de su ubicación física, por medio de este canal. La transparencia también se aplica al lenguaje de

programación. Se pueden realizar pedidos a objetos independientemente del lenguaje en el cual estén

implementados, por medio del lenguaje de definición de interfases o IDL que es común a todas las

implementaciones y usuarios.

IDL INTERFACE DEFINITION LANGUAGE Uno de los lineamientos que permite la abstracción de CORBA, es la separación de las interfases

de los objetos de su implementación. Las interfases se definen en un lenguaje provisto por CORBA para

este propósito, llamado IDL (Interface Definition Language o Lenguaje de Definición de Interfases). IDL no

permite definir la implementaciones. El hecho de que la definición se realice en un lenguaje genérico,

permite que la especificación sea independiente del lenguaje de implementación de los objetos.

IDL soporta todos los tipos básicos del lenguaje C, enumerados y relaciones entre otras

interfases. La definición de interfases, también soporta herencia en el formato diamante [CR0]. Toda

interfase que no declare heredar de otra en forma explícita, hereda en forma implícita de la interfase IDL

Object. De esta forma, todos los objetos CORBA, tienen una interfase común.

La definición IDL, es compilada en un lenguaje específico, como puede ser C, C++. Java, Fortran,

etc. El mecanismo de compilación de la definición IDL, se concreta según la especificación de CORBA,

denominada language mappings (o mapeos de lenguaje). El código que genera el proceso de compilación

de un segmento de código en lenguaje IDL, es sólo la cáscara para la implementación. Para generar un

objeto servidor o servant, un desarrollador toma esta cáscara e implementa el cuerpo de las operaciones

o funciones que se hayan definido en la interfase.

Cuando se compila un archivo que tiene definiciones IDL, para el caso de C++, por lo general se

crean cuatro archivos. Un archivo contiene las interfases C++, otro contiene las operaciones de la

interfase, otro contiene la implementación C++ del cliente (stubs), y el último contiene la implementación

Introducción a CORBA Pablo M. Ilardi

25

base del servidor o esqueleto (skeleton) de la misma, ya que el cuerpo de cada función en particular es lo

que se desarrolla a posteriori.

Cuando se comunican diferentes implementaciones de ORBs, no se pueden compartir los

archivos generados por el compilador IDL, ya que el código que éste genera es propietario. No tiene

sentido hablar de compartir código si se comunica una ORB que funciona con Java y otra con C++.

Mediante el uso del IDL, se puede llamar a una operación definida en una interfase IDL, que esté

implementada en un objeto programado en otro lenguaje y corriendo en otra ORB.

PASAJE DE PARÁMETROS: La definición de una operación requiere que se especifique el modo de pasaje de sus

parámetros. Las formas permitidas son: in, out, e inout.

- in: el argumento se pasa del cliente al servidor. - out: el argumento es devuelto al cliente, desde el servidor. - inout: el argumento es inicializado por el cliente, modificado por el servidor, y devuelto al cliente

nuevamente.

Dado que IDL, carece del concepto de punteros, tiene que existir una forma más eficiente de

pasar por parámetro, estructuras más complejas que un tipo de dato simple. Cuando se compila una

operación de una interfase que recibe como parámetro a otra interfase, se pasa una referencia al objeto

que implemente este parámetro, lo que es análogo a un “puntero”.

MAPEOS DE LENGUAJES (LANGUAGE MAPPINGS) Los Lenguaje Mappings especifican como se mapean las interfases IDL a un lenguaje

determinado. Para cada construcción IDL, el mapeo del lenguaje define como se traduce a ese lenguaje.

Por ejemplo, en C++ las interfases IDL se mapean a clases, mientras que en Java [IDJ0], se mapean a

interfases públicas.

Las referencias a objetos en C++ se mapean a una construcción que soporta el operator->,

mientras que en C, se mapean a un void *.

De igual forma, los mapeos de lenguajes especifican como utilizar las facilidades que provee la

ORB (ORB facilities), y como las aplicaciones servidoras implementan los servants.

Existen múltiples mapeos de lenguajes que permite que las aplicaciones distribuidas puedan ser

construidas en partes y utilizando múltiples lenguajes.

La independencia del lenguaje que plantea CORBA, es la clave para interconectar sistemas

heterogéneos.

INVOCACIÓN DE OPERACIONES Y FACILIDADES DE DESPACHO (DISPATCHING) Las aplicaciones CORBA funcionan recibiendo pedidos o request, o emitiendo request a objetos

CORBA. Los tipos de invocación que soporta CORBA son:

- Invocación estática: se realiza mediante una llamada a los stubs generados por el compilador IDL. El servant hereda o modifica el skeleton generado por el compilador IDL.

- Invocación dinámica: este mecanismo requiere la construcción del request en tiempo de ejecución. Dado que no se tiene información en tiempo de compilación, la creación e interpretación de los requests, requiere el acceso a los servicios que permitan obtener dicha información. Este servicio puede estar implementado, por ejemplo, mediante la interrogación a un operador que brinde la información necesaria, o utilizando el Interfase Repository Service, que es un servicio que provee acceso a interfases IDL en tiempo de ejecución.

Introducción a CORBA Pablo M. Ilardi

26

La invocación estática es comúnmente utilizada por los desarrolladores cuando se implementan

aplicaciones que utilicen interfases bien definidas. Mientras que el mecanismo dinámico es utilizado,

generalmente, por dispositivos que procesen información que a priori es desconocida, tales como los

bridges, o gateways.

OBJECT ADAPTERS (OA) Los OA son el nexo entre los servants y la ORB, corresponden al patrón de diseño Adapter [GA0].

Un objeto adapter adapta la interfase de un objeto a otra esperada por el emisor. De esta forma, permite

a un emisor comunicarse con un receptor sin conocer realmente cual es su interfase.

Los Object Adapters cumplen con tres requerimientos básicos:

1- Crean Object References, que permiten a los clientes acceder a los objetos. 2- Aseguran que cada objeto a invocar o target, esté encarnado o representado por un servant. 3- Toman los request que llegan a la ORB en la cual se aloja el objeto target y se los transmite

al servant que es la encarnación del target.

Esto permite a la ORB separarse de los diferentes tipos de servants que puedan existir. De esta

forma, la única responsabilidad relacionada con el request de la ORB es entregarlo al OA que es el que

realmente se encarga de comunicarse con el servant.

En C++ [HeVi0] un servant, es una clase que probablemente herede de un skeleton generado por

el mapeo del IDL al lenguaje. Para implementar las operaciones, se deben sobrescribir los métodos

virtuales. En el caso de Java [BgVaDk0], se deben implementar los métodos de la interfase que defina el

skeleton. El servant se registra con el OA, permitiéndole a la encarnación de las interfases que el servant

represente, recibir los request de los clientes.

Hasta la versión 2.1 de CORBA existía una especificación base del OA, llamada Basic Object

Adapter (BOA) solamente para C. La versión 2.2 de CORBA introdujo el Portable Object Adapter (POA),

para solucionar los inconvenientes que tenia BOA. El POA, mejoró mucho la relación entre los objetos

CORBA, y las encarnaciones servants. Como resultado de esto, la especificación de BOA fue eliminada de

CORBA.

PROTOCOLO INTER-ORB Antes de CORBA 2.0, una de las principales desventajas que se le criticaban a CORBA, era la

inexistencia de una especificación de protocolo para comunicar ORBs. Por ello, cada proveedor de ORB

debía definir el protocolo de red que usaba para comunicar la ORB. Lo que daba como resultado el

aislamiento de las distintas implementaciones de ORB, ya que cada una contaba con un protocolo

propietario.

En CORBA 2.0, se introdujo una arquitectura para la interoperabilidad de distintas

implementaciones de la ORB llamada General Inter-ORB Protocol (GIOP se pronuncia “gee-op”). GIOP es

un protocolo abstracto que define la sintaxis para la transmisión y el conjunto de formatos de mensajes

que permite a cualquier implementación de ORB comunicarse sobre una capa de transporte orientada a

la conexión. Internet Inter-ORB Protocol (IIOP se pronuncia “eye-op”) especifica como GIOP se mapea

sobre TCP/IP.

Para la interoperabilidad de las ORBs se requiere un formato estándar de Object References. Este

formato se denomina Interoperable Object Reference (IOR), y es lo suficientemente flexible como para

permite almacenar cualquier tipo de IOP. Los IOR, identifican uno o más protocolos soportados (IOPs).

Para cada protocolo, el paquete IOR contiene la información propietaria del mismo. En el caso de IIOP, el

Introducción a CORBA Pablo M. Ilardi

27

IOR contiene el nombre del host, el puerto TCP/IP, y una clave de objeto que identifica al objeto target,

para la combinación host - puerto.

UN REQUEST EN CORBA

OBJECT REFERENCES Las Object References (OR), son análogas a los punteros a objetos de C++ o las referencias de

Java.

- Cada OR identifica una única instancia de objeto. - Pueden existir múltiples OR que referencien al mismo objeto. - La referencia puede ser null (nil reference) - Las referencias pueden apuntar a objetos eliminados. - Son opacas, los cliente no conocen el contenido de las mismas. (deber ser tratadas como caja

negra) - Son fuertemente tipadas. - Soportan late-binding (soporta el polimorfismo de C++, a diferencia del de RPC). - Pueden ser persistentes (las referencias pueden haber sido almacenados en disco para luego ser

recuperadas nuevamente). - Pueden ser interoperables (diferentes implementaciones de ORBs, pueden intercambiar OR).

ADQUISICIÓN DE OR

La única forma de acceder a un objeto CORBA es a través de las OR. Cada servidor puede

publicar las ORs que contiene. La forma más natural de adquirir una OR por un cliente es la de recibirla

como respuesta a una invocación a un servicio. Otra forma es la de buscarla en un servicio como Naming

o Trading Service.

PROCESO PARA LA INVOCACIÓN DE UN REQUEST Cuando un cliente invoca una operación por medio de la OR de un objeto, la ORB hace lo

siguiente:

- Ubica el objeto target. - Activa la aplicación servidor, si es que esta no está activa. - Transmite los argumentos para la operación. - Activa el servant para el request si es necesario. - Espera que se complete el request. - Devuelve todos los parámetros out, o inout, y el resultado del request. - Alternativamente devuelve una excepción, en conjunto con los datos, cuando el request falle.

El mecanismo para el cliente es completamente transparente y lo ve como la invocación de

cualquier otro método en un objeto no controlado por la ORB.

LAS CARACTERÍSTICAS DE LA INVOCACIÓN

- Transparencia de la ubicación: El cliente no sabe si el objeto es local al proceso en el que corre, o si se encuentra en otro proceso de la misma máquina, o si realmente se encuentra en otro proceso de otra máquina. Los procesos servidores, no están obligados a permanecer en la misma máquina eternamente, pueden ser movidos de máquina en máquina sin que los clientes sean conscientes de ello.

- Transparencia del servidor: El cliente no necesita saber cual es el servidor que implementa el objeto.

- Independencia del lenguaje: Al cliente no le interesa saber en cual lenguaje está implementado el objeto que está invocando.

Introducción a CORBA Pablo M. Ilardi

28

- Independencia de la implementación: El cliente no necesita saber como funciona la implementación. Incluso los objetos servidores, no necesariamente deben estar implementados en un lenguaje orientado a objetos.

- Independencia de la arquitectura: El cliente no conoce realmente el tipo de máquina en la que está corriendo el objeto.

- Independencia del sistema operativo: no se sabe realmente cual es el sistema operativo que está corriendo el servidor.

- Independencia del protocolo: El cliente no conoce el protocolo que se está utilizando para comunicarse. La ORB, es la encargada de seleccionar, en tiempo de ejecución, el protocolo que utilizará para comunicarse.

- Independencia del Transporte: El cliente desconoce el transporte o topología de la red que se utilice para la comunicación. Se pueden utilizar distintos tipos de red sin que el cliente sea consciente de ello.

CONTENEDOR DE COMPONENTES CORBA

La versión 3.0 de CORBA incluye un nuevo modelo de trabajo, denominado modelo de

componentes [CCM01]. Los componentes extienden el modelo de objetos de CORBA (las interfases de

IDL), y promueven la composición en vez de la herencia.

El modelo de componentes de CORBA toma características del modelo de componentes de Java,

llamado EJB (Enterprise Java Bean), y del modelo de componentes de Microsoft llamado COM

(Component Object Model), pero a diferencia de ambos no está basado en un lenguaje particular o en un

único entorno como Windows.

CCM o CORBA Component Model (Modelo de Componentes CORBA) promueve un modelo de

desarrollo de aplicaciones, donde se oculta parte de la complejidad de CORBA. Los componentes de

CORBA tienen acceso a los servicios estándar de CORBA, tales como el de Transacciones, Seguridad,

Persistencia o Eventos.

Todo componente se instala en un contenedor de componentes. La especificación CCM

introduce el concepto de componente y un conjunto compuesto por interfases, técnicas para especificar

la implementación, empaquetado, y despliegue (deploy) o instalación de los componentes.

Los componentes implementan al menos una interfase, pero además permiten definir a los otros

componentes que se requieren para que un componente pueda funcionar. Un componente también

puede exponer atributos que permiten configurarlos en el contenedor donde sean instalados. Además, se

define un modelo de conexión de componentes mediante eventos, que permite independencia de

interfases de los componentes conectados. Este modelo de conexión sigue el patrón de diseño Observer

[GA0].

Todas estas nuevas características son definidas mediante un nuevo lenguaje llamado CIDL (Component Implementation Definition Language), que extiende al lenguaje PSDL [PSS01] y al IDL versión 3.0 de CORBA.

Introducción a CORBA Pablo M. Ilardi

29

REFERENCIAS

Tanenbaum01 – “Distributed Operating Systems”, Andrew, S. Tanenbaum, Practice-Hall Inc 1996.

ISBN 0-13-219908-4

TariBukhers01 – “Fundamentals of Distributed Object Systems, the CORBA Perspective”, Zahir Tari,

Omran Bukhers, Willey Inc. 2001. ISBN 0-471-35198-9

CR0 – Object Management Group, “Common Object Request Broker Architecture”, version 3.0.3,

OMG document number formal/04-03-01. http://www.omg.org/cgi-bin/doc?ptc/04-03-01

OM0 – Richard Mark Soley, “Object Management Architecture Guide”, June 13, 1995, Ph.D. (ed.)

Christopher M. Stone

COS0 – CORBAservices: Common Object Services Specification, Updated: November 1997. ftp://ftp.omg.org/pub/docs/formal/98-07-05.pdf CCF0 – CORBAfacilities: Common Facilities Architecture. ftp://ftp.omg.org/pub/docs/formal/97-06-08.pdf NS01 – Object Management Group, “Naming Service Specification”, version 1.1. February 2001. OMG document number formal/01-02-65. http://www.omg.org/cgi-bin/doc?formal/01-02-65 PSS01 – Object Management Group, “Persistent State Service”, version 2.0. September 2002. OMG document number formal/02-09-06. http://www.omg.org/cgi-bin/doc?formal/02-09-06 TSS01 – Object Management Group, “Transaction Service Specification”, version 1.4. September 2003. OMG document number formal/03-09-02. http://www.omg.org/cgi-bin/doc?formal/03-09-02 IDJ0 - Object Management Group, “Java™ to IDL Language Mapping Specification”, version 1.3. September 2003. OMG document number formal/03-09-04. http://www.omg.org/cgi-bin/doc?formal/03-09-04 GA0 – Erich Gamma, “Design Patterns: Elements of Reusable Object-Oriented Software”, Addison-

Wesley Professional Computing Series, Richard Helm, Ralph Johnson, John Vlissides, January 15,

1995.

BgVaDk0 – Gerald Brose, Andreas Vogel, Keith Duddy, “Java Programming with CORBA, Advanced

Techniques for Building Distributed Applications”, 3rd Edition, Wiley 2001.

HeVi0 – Michi Henning, Steve Vinoski, “Advanced CORBA Programming with C++”. Addison Wesley.

February 12, 1999. ISBN: 0-201-37927-9

CCM01 – CORBA “Component Model Specification”, OMG Available Specification Version 4.0

formal/06-04-01. http://www.omg.org/cgi-bin/doc?formal/06-04-01

30

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

31

SERVICIO DE ESTADO PERSISTENTE CORBA - PSS

Conceptos Fundamentales en el Servicio ................................................................. 33

Identificación ........................................................................................................... 35

Tipos y Herencias. .................................................................................................... 35

Claves ....................................................................................................................... 36

Uso del Servicio de Persistencia ............................................................................... 36

Acceso al Servicio de Persistencia ............................................................................ 37

Transacciones .......................................................................................................... 38

Construcciones en PSDL ........................................................................................... 38

Características de las construcciones básicas PSDL ............................................... 39

AbstractStorageObject ...................................................................................... 39

AbstractStorageHome ....................................................................................... 40

StorageObject ................................................................................................... 40

StorageHome .................................................................................................... 41

Mapeo del lenguaje PSDL a un lenguaje concreto ................................................... 41

Diferencias entre C++ y Java del servicio de persistencia ...................................... 41

Críticas a la especificación ....................................................................................... 42

Notas finales sobre el servicio de estado persistente .............................................. 43

Alternativas a PSS en Java ....................................................................................... 43

Referencias .............................................................................................................. 45

32

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

33

n 1997 OMG creó un RFP (Request for Proposal) para la creación de un servicio CORBA. Se trató de

un servicio de persistencia de objetos [PSS01]; Una RFP consiste en el llamado a entidades a

participar en la creación de una nueva especificación.

El servicio de persistencia de CORBA tiene el objeto de facilitar y unificar la forma de hacer

persistente el estado de los servants [CORBA01] y fue denominado: Servicio de Estado Persistente

(Persistent State Service) PSS.

El hacer persistente el estado del objeto significa que su estado sobrevive o se mantiene sin

importar cuantas veces el objeto sea encarnado o destruido. No debe confundirse el persistir referencias

a servants, con el hacer persistente el estado de un objeto. El hacer persistente las referencias a los

servants significa guardar en forma persistente una referencia a un servant y no el estado del mismo.

En el año 2000, PSS reemplazó una especificación anterior del año 1994, denominada POS

Persistent Object Service [POS01], que fue muy poco aceptada y ampliamente criticada [KjPfTp].

Actualmente está definida la revisión 2 de PSS que data de Septiembre del 2002.

PSS no intenta ser una interfase a una base de datos orientada a objetos OODBMS, sino el de

proveer una forma estándar de hacer persistentes a los objetos. No define los conceptos fundamentales

en OODBMS como transacciones y consultas [BgVaDk]. Su fundamento es la separación de intereses

(separation of concerns) que está presente en todas la arquitectura y especificaciones de servicios CORBA

[CORBA01]. De esta forma, si se tienen requerimientos transaccionales, éstos no serán implementados

por este servicio sino que este servicio se conectará o comunicará con el servicio de transacciones CORBA

[TS01]. Una de las características que fue mas ampliamente criticada a POS fue no usar los otros servicios

que CORBA provee [KjPfTp].

PSS está basado en dos lenguajes de programación: C++ y Java. Aunque su modelo tiene mas

similitudes con el segundo que con el primero.

CONCEPTOS FUNDAMENTALES EN EL SERVICIO

Según la especificación, los clientes de los servants son ajenos a este servicio y no tienen forma

de saber si se está usando el mismo. El PSS es visible solo dentro de la implementación de los servants.

Según muestra el Gráfico 1, el cliente de la ORB que accede al servant, no accede al PSS directamente

sino que lo hace a través del Servant.

Interfa

ce P

SS

(PS

DL

)

Dominio de ORB Dominio de PSS

Servant

Interfa

ce OR

B e

xtern

a (ID

L)

ClienteORB

Objeto deldominio

PSS

GRÁFICO 1 - INTERACCIÓN CON LA ORB

En PSS, la información persistente se presenta como objetos almacenados (Storage Objects) en

almacenes (Storage Homes), que a su vez se encuentran ubicados en repositorios de datos (DataStores)

tales como una base de datos o un conjunto de archivos. Este modelo está representado en el Gráfico 2.

E

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

34

Data Store o Repositorio de Datos

ObjetoAlmacenado

Almacen o StorageHome

ObjetoAlmacenado

ObjetoAlmacenado

Almacen o StorageHome

ObjetoAlmacenado

GRÁFICO 2 - MODELO LÓGICO

Dentro de un repositorio de datos pueden existir múltiples almacenes y a su vez dentro de los

almacenes pueden existir múltiples objetos almacenados. Conceptualmente, un repositorio de datos es

un conjunto de almacenes, cada almacén contiene objetos de un tipo definido dentro del repositorio.

Dentro del repositorio, un almacén es un Singleton [Gamma01], es decir que existe a lo sumo una única

instancia de un almacén por tipo de objeto almacenado.

Los objetos almacenados se manipulan a través de instancias de una clase definida en el lenguaje

de implementación del programa o proceso que utilice el servicio. Estas instancias representan en un

momento dado un objeto existente en el repositorio de datos, mediante el cual se accede al estado

persistido. Las instancias de objetos que estén ligadas a un objeto en el repositorio se llaman

encarnaciones, al cambiar un atributo de una de estas instancias se está cambiando el estado del objeto

almacenado en el repositorio.

Los almacenes de objetos también son manipulados mediante instancias de los mismos. Estas

instancias son provistas por catálogos. Para acceder a las instancias de los objetos almacenados desde del

proceso o programa se requiere de una conexión con el repositorio de datos, denominada sesión. El

Gráfico 3 define la relación entre el modelo lógico y el modelo real de la aplicación.

Data Store o Repositorio de Datos

Almacenes oStorage Homes

ObjetosAlmacenados

ObjetosAlmacenados

Programa o Proceso

Catálogo

sesión

Encarnaciones

Instancia deAlmacen

Encarnaciones

Instancia deAlmacen

GRÁFICO 3 - SESIÓN PARA ACCEDER AL DATASTORE

Muchos de los conceptos existentes en PSS, fueron tomados del estándar SQL3 o SQL 1999

[EisenbergMelton01]. SQL3 fue una extensión propuesta al estándar SQL en el año 1999, que agrega

conceptos de los lenguajes de programación orientados a objetos. Las bases de datos que lo soportan son

llamadas Object-Relational DBMS u ORDBMS [Ramakanth01]. Las tablas en SQL3 permiten tipos de datos

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

35

más ricos llamados ADT (Abstract Data Type) que soportan herencia. Los almacenes de PSS son el

análogo a las tablas de SQL3, mientras que los objetos almacenados son los registros o tipos de datos de

SQL3.

Todos los objetos definidos por el servicio de persistencia implementan la interfase

LocalInterface [CORBA01]. Según la especificación de CORBA, todos los objetos que implementen dicha

interfase tienen su ciclo de vida limitado al proceso en el que fueron engendrados. De esta forma, es

imposible para un servant exportar un objeto definido en el dominio del servicio de persistencia. Esta es

otra forma de encapsular el servicio de persistencia sólo dentro de la implementación de los servants.

IDENTIFICACIÓN

Dado que existen múltiples instancias de objetos dentro de un repositorio y de un almacén, es

necesaria una forma de diferenciarlos.

Cada objeto almacenado posee un número único que lo identifica dentro del almacén al que

pertenece denominado short-pid (Short Process Identifier o Identificador corto de Proceso). Además

posee un identificador único dentro del repositorio en el que existe, que es denominado pid (Process

Identifier o Identificador de Proceso). El alcance de este pid está limitado a todas las instancias que

pueden ser accedidas por el mismo catálogo.

El concepto de pid es análogo al de oid dentro de CORBA. Una forma simple de relacionar un

objeto CORBA con un objeto del servicio de persistencia (un objeto persistente) es directamente por sus

identificadores (oid igual al pid).

TIPOS Y HERENCIAS.

Los tipos estén separados en dos grandes categorías abstractos y concretos, los primeros

implican definiciones que no pueden ser instanciadas, las segundas son las construcciones que serán

instanciadas en el programa que utilice el servicio.

custom Herencia Diamante

«interface»

A

«interface»

B

«interface»

C

«interface»

D

«interface»

A'

«interface»

B'

«interface»

C'

«interface»

D'

«interface»

E'

GRÁFICO 4 - HERENCIA DIAMANTE DE INTERFASES EN CORBA

Este modelo es muy similar al modelo de herencia del lenguaje de programación Java, los tipos

abstractos son análogos a las interfases de Java, mientras que los concretos son análogos a las clases. Al

igual que las interfases, los tipos abstractos admiten herencia múltiple, mientras que los concretos solo

pueden heredar a lo sumo de un único tipo concreto. De igual forma que las clases, los tipos concretos

pueden “implementar” múltiples interfases. La herencia múltiple incluye el modelo de herencia diamante.

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

36

La herencia diamante (gráfico 4) está definida en la especificación de CORBA [CORBA01], se

utiliza principalmente para la definición de interfases.

AlmacenTipoA TipoA

TipoBAlmacenTipoB

TipoCAlmacenTipoC

AlmacenConcreto TipoConreto

almacena

almacena

almacena

almacena

ImplementaImplementa

ImplementaImplementa

Hereda Hereda Hereda

Hereda

GRÁFICO 5 - TIPOS Y MODELO DE HERENCIA EN PSS

Tanto los objetos almacenados como los almacenes son tipados con tipos abstractos o concretos,

según si son abstractos o concretos respectivamente.

Cada objeto almacenado tiene un tipo asociado, este tipo define el estado y las operaciones que

poseen todas las instancias de este tipo. A su vez un tipo puede ser derivado de otros tipos definidos en el

repositorio.

Cada almacén a su vez está definido por un tipo, el cual define los tipos de objetos que el almacén puede contener o manejar junto con las operaciones que pueda realizar. De igual forma que los tipos de objetos, los tipos de almacenes también pueden derivar de otros tipos de almacenes definidos dentro del repositorio.

Dentro de un repositorio, un almacén puede manejar tanto las instancias de sus objetos

almacenados como las de sus subtipos, que se denomina familia de almacenes.

En el gráfico 5 se muestra la herencia de tipo diamante soportada por el servicio de persistencia

CORBA.

CLAVES

Un almacén puede definir que una serie de atributos de los objetos que maneja, definen una

clave que representa un identificador único para cualquier instancia de los objetos existentes en el

almacén. Los almacenes pueden definir tantas claves como deseen. Hay una clave implícita que es el

short-pid. Un concepto muy importante, es que las claves no están definidas por los objetos

almacenados, sino por el almacén donde se encuentran. Esto permite que diferentes almacenes definan

diferentes claves. Una implicación importante es que la unicidad de los objetos sólo es válida dentro del

almacén en el que existen.

USO DEL SERVICIO DE PERSISTENCIA

Para hacer uso del servicio de persistencia, se requiere que el usuario defina los tipos que usará en un programa, la especificación propone dos formas para definir los tipos de objetos.

- Mediante el lenguaje de definición llamado PSDL (Persistent State Definition Language); o

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

37

- mediante el uso del lenguaje programación en el que se quiera acceder al servicio.

PSDL es una ampliación al IDL estándar de CORBA, que agrega cuatro construcciones: abstract

storage object, abstract storage home, storage object y storage home. En el caso de utilizar la primera

forma de definir los tipos, la implementación del servicio deberá proveer una herramienta o compilador

que transforme el código definido en PSDL al código fuente que utilice el servicio.

En el caso de utilizar la segunda, también llamada persistencia transparente, el usuario será

responsable de programar las clases que usará el servicio de persistencia.

Ambas alternativas son igualmente válidas, la segunda no requiere que el usuario entienda el

lenguaje PSDL. Tiene algunas limitaciones ya que no hay un estándar para cada lenguaje, que defina cómo

hacer uso de algunas de las funcionalidades que provee el servicio, tales como definir claves adicionales a

las implícitas. Este problema surge porque el servicio requiere que el usuario sea quien defina y provea

los tipos y no alcanza con solo llamar a una rutina del servicio. A cualquier usuario acostumbrado a

trabajar con CORBA le es natural utilizar el lenguaje IDL y por consiguiente le debiera ser relativamente

sencillo aprender a utilizar PSDL.

Según la especificación del servicio, las aplicaciones que utilicen el servicio de persistencia solo

deberían tratar con los tipos abstractos, de esta forma se puede lograr una aplicación aislada o separada

de la implementación del servicio. Por otro lado, los tipos concretos son mapeados a construcciones

definidas en el lenguaje de implementación del servicio (Java o C++). Algunas de estas construcciones

están definidas en el mapeo de PSDL al lenguaje concreto, pero otras no, y en este punto es donde se

pierde la independencia de la implementación del servicio de persistencia.

ACCESO AL SERVICIO DE PERSISTENCIA

La puerta de entrada al servicio es el ConnectorRegistry (o registro de conectores). Este objeto es

un Singleton registrado en la ORB como una referencia inicial [CORBA01]. El nombre de la referencia

depende de la implementación del servicio, pero generalmente es PSS. Como su nombre lo indica, el

ConnectorRegistry tiene un registro de los conectores que tiene registrados el servicio de persistencia.

ConectorRegistry Connector CatalogBase

Session SessionPool

TransactionalSession

ORBRefencia inicial

Tieneregistrados Provee

GRÁFICO 6 - ACCESO AL SERVICIO

El conector (Connector) determina el tipo de servicio de persistencia que se usará. Se dice que el

conector define la implementación del servicio de persistencia a utilizar. Un conector típico podría ser uno

que se conecte a una base de datos relacional o uno que utilice un sistema de archivos para almacenar el

estado de los objetos. Los conectores son responsables de crear los catálogos (CatalogBase) que se

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

38

usarán para trabajar con el servicio de persistencia. El tipo de catálogo que se cree, será dependiente del

conector y el método que se utilice para crearlo.

La sesión, o sea la conexión lógica entre el o los repositorios y el proceso, tiene credenciales de

uso, como por ejemplo, solo lectura o lectura-escritura. Las sesiones pueden a su vez ser de tipo

transaccional (TransactionalSession) mediante el uso del servicio de transacciones CORBA, o sesiones

donde la atomicidad de las operaciones dependa del conector utilizado, por ejemplo, con acceso de tipo

archivo (Session), donde cada operación se hace inmediatamente persistente.

El acceso a las sesiones puede ser en forma explícita o implícita. Cuando se trabaja con sesiones

implícitas (ya sean transaccionales o no) el usuario del servicio de persistencia es el encargado de

crearlas, escribiendo código en el lenguaje utilizado. Cuando se maneja en forma implícita, un pool de

sesiones es el encargado de administrarlas (SessionPool). Un pool de sesiones tiene el objeto de permitir

la reutilización de las conexiones o sesiones a un repositorio, ya sea dentro de la misma aplicación o no.

Por lo general, su existencia tiene dos justificaciones: la creación de una sesión puede ser costosa en

términos de recursos involucrados para crearla, y cómo y dónde conectarse puede ser centralizado en un

sólo punto, permitiéndole a la aplicación desentenderse de ello.

El servicio de persistencia puede ser utilizado también mediante un contenedor de componentes

CORBA [CCM01, CCM02], tanto en forma explícita como implícita.

TRANSACCIONES

Las transacciones en el servicio de persistencia están presentes a través del servicio de

transacciones CORBA [TS01]. Esto implica que los objetos almacenados pueden ser accedidos en el

contexto de una transacción. Para interactuar con el servicio de transacciones es necesario registrar en él

los recursos que serán utilizados en la transacción.

En el caso de que se trate de sesiones explícitas, las instancias de los objetos almacenados están

relacionadas con una transacción por medio de una sesión transaccional. Esta sesión está asociada a una

transacción de datos (datastore transaction), que es el recurso que se registra con el servicio de

transacciones. En el modo de trabajo explícito, la sesión transaccional provee operaciones explícitas para

interactuar directamente con el servicio de transacciones.

El escenario anterior no es el más típico, por lo general, cuando se trabaja con transacciones se

utiliza un proveedor de las mismas, y es el pool de sesiones el que se comunica con este servicio. En el

caso de utilizar el pool (sesiones implícitas), éste es el encargado de verificar cuando es necesario iniciar

una transacción y no se tiene control programado del manejo de transacciones.

CONSTRUCCIONES EN PSDL

PSDL define el módulo CosPersistentState, que contiene todas las construcciones propias del

lenguaje. Las principales son:

CatalogBase, Connector, Session, SessionPool, TransactionalSession, AccessMode,

ParameterList, StorageHomeBase, StorageObjectBase, StorageObjectFactory, StorageHomeFactory,

SessionFactory, NotFound, TypeId, Pid, ShortPid, ConnectorRegistry.

A continuación, voy a hacer una breve descripción de las construcciones que agrega PSDL a IDL

no definidas hasta este punto.

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

39

- AccessMode define el modo de acceso que tendrá garantizado el usuario del servicio sobre una sesión al momento de crearla, mediante el uso de un conector. Puede ser solo lectura, o lectura – escritura.

- ParameterList es simplemente una lista de parámetros variables que le permiten al conector recibir parámetros propios de la implementación del mismo para crear una sesión. Por ejemplo, un conector podría requerir un nombre de usuario y clave para conectarse a un repositorio, si el repositorio es de tipo remoto, podría ser necesario indicarle la ubicación del mismo en la red.

- NotFound es una excepción utilizada para indicar que un objeto que se quiere localizar no existe o no está disponible. Es utilizada tanto por el registro de conectores para indicar que un conector no existe, como por una sesión cuando se quiere ubicar un objeto por su pid.

- TypeId es una cadena de caracteres que identifica un tipo PSDL. El formato de la misma es similar a los ids de los repositorios IDL [CORBA01 ver sección 10.7.1], pero en vez de comenzar con IDL lo hacen con PSDL. Al igual que los ids de repositorios, estos ids incluyen el número de versión al que corresponden. Estos ids son utilizados por los conectores para registrar las fábricas de objetos de un tipo PSDL. Por ejemplo, para un tipo persistido: ar.uba.fi.pmi.corba.pss.prueba1.Persona, en su primer versión podría tener asociado un tipo persistido: PSDL:ar.uba.fi.pmi.corba.pss.prueba1.Persona:1.0. Esta cadena de caracteres será utilizada por el usuario final para registrar la clase Persona en el conector donde quiera utilizarla.

CARACTERÍSTICAS DE LAS CONSTRUCCIONES BÁSICAS PSDL En este punto se definen las características principales de las definiciones PSDL. La sintaxis de las

mismas se podrá consultar en la especificación del servicio [PSS01].

ABSTRACTSTORAGEOBJECT

La sintaxis para definir un objeto de este tipo es prácticamente la misma que para una interfase

IDL, salvo que esta no puede contener constantes ni definiciones de subtipos. Un objeto almacenado

abstracto puede heredar de múltiples objetos almacenados abstractos, pero sólo puede ser definido

como supertipo directo una única vez en la misma definición. No se permite heredar de tipos que

contengan estados u operaciones con el mismo nombre, salvo para el caso de la herencia diamante

explicada anteriormente. Se permiten definiciones hacia adelante (forward), pero las mismas tienen que

estar completas dentro de la misma especificación PSDL (dentro del mismo documento).

Estas definiciones pueden contener definiciones de estados que representan a los atributos

persistentes de los objetos almacenados. Los estados pueden ser definidos como lectura – escritura o

sólo lectura (explícitamente). Los estados son tipados y los tipos válidos son: un tipo básico (número o

carácter), una cadena de caracteres, una referencia a un tipo persistido abstracto, u otra definición local

(interfase u objeto almacenado).

Cuando el estado tiene definido un tipo que sea otro tipo persistido, se dice que el objeto está

empotrado (embedded) en el otro. Este objeto que representa el estado, no posee identidad propia y no

puede ser referenciado fuera del contexto del objeto que lo contiene.

Cuando el tipo del estado es una referencia a otro objeto almacenado mediante el uso de la

palabra strong (fuerte), se puede indicar que cuando el objeto almacenado que lo referencia sea

destruido también lo sea el referenciado.

La definición puede contener operaciones locales (local operations), que podrán utilizar

parámetros de entrada, salida o entrada - salida tal como cualquier operación IDL. Además, podrán

utilizar como parámetros, cualquier definición IDL válida o algún objeto almacenado abstracto definido

previamente. Las operaciones pueden ser definidas además como constantes, en cuyo caso no se les

estará permitido modificar el estado del objeto al que pertenecen. Se les denomina operaciones locales

dado que los objetos almacenados definen interfases locales en CORBA.

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

40

Toda definición de objeto almacenado abstracto que no herede de otra definición

explícitamente, heredará implícitamente del objeto almacenado abstracto StorageObject definido en el

módulo CosPersistentState.

ABSTRACTSTORAGEHOME

La definición de un almacén de objetos almacenados debe especificar que tipo de objeto

almacenado abstracto es el que puede contener (un único tipo). Al igual que los objetos almacenados

abstractos, se admiten definiciones hacia delante, que deben ser completadas en el mismo documento.

Los almacenes abstractos pueden heredar de múltiples almacenes abstractos, con la única

condición de que los tipos de objeto persistidos manejados por los almacenes heredados, deben ser un

supertipo o el mismo que el manejado por el almacén. Tampoco se permite redefinir operaciones con el

mismo nombre, salvo para el caso de la herencia diamante, que también es soportada por los almacenes

abstractos.

Los almacenes abstractos pueden definir claves (keys), que son simplemente una lista de

nombres de estados (al menos uno), que están identificados por un nombre. Una clave determina que

para el almacén donde está definida, sólo puede existir un único objeto almacenado con los mismos

valores de los estados definidos en ella. Los estados que conforman la clave no deberán estar repetidos y

los tipos de los mismos deberán ser comparables (tipos nativos, cadenas de caracteres, o estructuras de

tipos comparables).

La definición de una clave implica la definición implícita de dos operaciones locales en el

almacén, que permitirán ubicar un objeto almacenado dentro del mismo. Estas operaciones serán

find_by_nombreclave, y find_ref_by_nombreclave, ambas tomarán como parámetro los valores de los

estados que conformen la clave, pero la primera devolverá el objeto almacenado, mientras que la

segunda, una referencia al objeto. Si dicho objeto no existe, se lanza una excepción NotFound en el

primer caso y se devolverá NULL en el segundo.

Además, se permite una definición extendida de operación, llamada factory method (operación

de fábrica) [Gamma01], que permite construir un objeto almacenado en el almacén. Los parámetros

serán una lista de estados presentes en el objeto almacenado (por lo menos uno).

Factory Method es un patrón de diseño que permite encapsular la construcción de un objeto. El

tipo o clase será definido por el objeto que implemente dicho factory method en función de su estado

interno y de los parámetros de dicho método. En el contexto de un almacén de objetos, según la

configuración del servicio de persistencia, se definirá la clase que implementa el objeto almacenado. De

esta forma, el usuario del servicio de persistencia puede tratar siempre con las interfases de los objetos,

sin necesidad de conocer las implementaciones de los mismos, que por lo general, se encuentran ligadas

a la implementación del servicio de persistencia.

Al igual que los objetos almacenados abstractos, los almacenes abstractos también admiten la

definición de operaciones locales.

STORAGEOBJECT

Se permite la herencia de a lo sumo un objeto almacenado por definición, mientras que es

posible implementar múltiples definiciones de objetos almacenados abstractos. Se dice que el objeto

almacenado implementa directamente una definición de un objeto almacenado abstracto cuando se

define en forma explícita que lo implementa. También puede implementarlo en forma indirecta, si

hereda de otro objeto almacenado que implemente la definición abstracta. Esta definición es importante,

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

41

ya que existen múltiples restricciones que se aplican cuando se implementa en forma directa otra

definición.

Por ejemplo, cuando un objeto almacenado implemente directamente un objeto almacenado

abstracto, que defina un estado que sea otro objeto almacenado abstracto o una referencia, la definición,

deberá declarar una directiva de almacenamiento (store directive) al compilador. Esta directiva indica

cual es el objeto almacenado concreto que será referenciado o utilizado en dicho estado, y

adicionalmente, si se trata de una referencia, cual será el almacén concreto donde se albergará a dicho

objeto almacenado. Esto le permite al compilador, por ejemplo, optimizar el espacio requerido para

almacenar el objeto conociendo de antemano de que tipo de objeto se trata.

Toda definición de objeto almacenado que sea la primera en la jerarquía, podrá indicarle al

compilador los estados que definen una referencia a un objeto almacenado de este tipo. Esta lista de

estados, además constituirá una clave única dentro del almacén de objetos almacenados que contenga

objetos de este tipo.

STORAGEHOME

La definición de un objeto almacenado requiere indicar que tipo de objeto almacenado concreto

será el que manejará este almacén. El almacén puede heredar de a lo sumo un almacén y el tipo

almacenado de dicho almacén deberá ser un supertipo del tipo almacenado definido por el almacén.

Además, dos almacenes dentro de la misma jerarquía no podrán tener definido el mismo tipo de objeto

almacenado.

El almacén puede implementar múltiples almacenes abstractos, siempre y cuando los tipos

almacenados de los almacenes abstractos sean implementados por el objeto almacenado definido por el

almacén.

Al igual que los objetos almacenados, para los almacenes también se define el concepto de

implementar en forma directa. Se dice que un almacén implementa en forma directa una definición del

almacén abstracto, cuando el almacén sea el primero dentro de la jerarquía que lo implemente.

También, se dice que un almacén implementa directamente un objeto almacenado abstracto

cuando éste sea el primero en su jerarquía en implementar dicha definición. Y en base a esto, el almacén

implementará en forma directa un estado, cuando implemente en forma directa el objeto almacenado

abstracto donde dicho estado esté definido.

Estas definiciones se aplican en restricciones a la jerarquía del almacén. Cada clave definida en

un almacén abstracto, que sea implementado directamente por un almacén, deberá tener como mínimo

un estado implementado directamente por el almacén. Esto evita que existan almacenes que definan

claves únicas sin contener estados definidos en él. Es como si en una base de datos relacional, una tabla

definiera una clave única que no tenga columnas de dicha tabla, sino todas columnas de una tabla con la

que se tiene una clave foránea (foreign key).

Todo almacén, que sea el primero dentro de la jerarquía, puede definir una de sus claves o la

representación de la referencia del tipo de objeto que almacena, como la clave primaria.

MAPEO DEL LENGUAJE PSDL A UN LENGUAJE CONCRETO

El servicio de persistencia define el mapeo de las entidades definidas en PSDL al lenguaje que

implemente el servicio. Algunos son de tipo nativo, o sea que su valor depende puramente del lenguaje

que se utilice.

DIFERENCIAS ENTRE C++ Y JAVA DEL SERVICIO DE PERSISTENCIA

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

42

El mapeo de PSDL a C++ requiere agregar estructuras que en Java ya se encuentran presentes.

Algunos de los mapeos de tipo nativo a Java se traducen en clases ya existentes en el lenguaje, mientras

que en C++, son construcciones nuevas. Por ejemplo, en Java, toda clase hereda implícitamente de la

clase Object, mientras que en C++ no existe una clase base a todas las clases, por lo que se requiere

agregar la construcción CosPersistentState::StorageObject. Otro ejemplo, es el de las fábricas (factories),

para instanciar o crear objetos de los tipos definidos por los usuarios (mapeo nativo): en Java se traduce

directamente en un objeto de tipo Class, mientras que en C++ se traduce a una clase Factory definida por

el servicio de persistencia, que deberá crear objetos en forma programática. Esta es una de las diferencias

de mapeo del servicio a los lenguajes de programación, donde se muestra que el servicio se adapta más

naturalmente al lenguaje Java. Dado que Java es un lenguaje más moderno que C++, provee de

herramientas que hacen más simple los programas escritos en este lenguaje.

Otro ítem importante, es el manejo automático de la memoria. En Java, el ciclo de vida es

manejado automáticamente por la máquina virtual, mientras que en C++, el programador es el encargado

de reservar y liberar la memoria requerida por los objetos. El servicio de persistencia provee métodos

para liberar referencias a objetos accedidos en una sesión y que ya no son utilizados. En C++ se requiere

de esta operación, ya que es el programador el encargado de liberar la memoria que ha alocado, en Java

ésta operatoria es automática.

CRÍTICAS A LA ESPECIFICACIÓN

El servicio de persistencia define un tipo persistido llamado StorageObject, que tiene una serie

de métodos, tales como get_pid() o get_short_pid(), entre otros. Según la especificación (sección 3,

página 8[PSS01]), toda definición de un objeto persistido abstracto que no herede explícitamente de otro,

hereda de la clase StorageObject. Sin embargo, de acuerdo a la especificación, todos los objetos

almacenados concretos heredan implícitamente de la clase StorageObjectBase. En Java, esta clase está

representada por la clase java.lang.Object, mientras que en C++, al no existir una clase base de todos los

objetos, se define una nueva clase CosPersistentState::StorageObjectBase. Ninguna de estas dos clases

(tanto en Java como en C++) definen los métodos que tiene definidos la clase abstracta StorageObject.

Dado que no es obligatorio que la definición de un objeto almacenado concreto, implemente

alguna definición de un objeto almacenado abstracto, lo expresado en el párrafo anterior sobre

StorageObject y StorageObjectBase, no constituye una contradicción.

Esto implica que no está definido que ocurre cuando un objeto almacenado concreto

implementa alguna definición abstracta. ¿Qué pasa con las operaciones definidas en StorageObject?

Queda completamente libre y depende puramente de la implementación del servicio de persistencia.

Tampoco está claro, por qué es necesaria la existencia de la definición de StorageObject, ya que todas las

operaciones del servicio de persistencia están basadas en la clase StorageObjectBase.

La existencia de StorageObject, hace que los usuarios del servicio de persistencia estén obligados

a atarse a una implementación del servicio, si es que quieren utilizar las definiciones abstractas. Esto

contradice uno de los principales objetivos de PSDL, que es el de liberar al usuario final del servicio de

usar una implementación particular del mismo.

Gran parte de las inconsistencias surgen de tratar de dar soporte de persistencia transparente y

por PSDL, en forma simultánea. Si bien la idea de la persistencia transparente es potencialmente muy

atractiva, al tener que coexistir con el mundo de PSDL, crea un modelo híbrido difícil de compatibilizar.

Las excepciones, códigos de error, o condiciones de error no están especificadas en todos los

casos, cada implementación es libre de generar los tipos de error que considere apropiados. Por ejemplo,

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

43

¿qué sucede cuando se trata de registrar una clase como factory de un tipo de objeto almacenado que no

es compatible con la implementación del servicio o conector?

Otro tipo de errores que también son muy importantes, son los relacionados con la integridad

referencial y el ciclo de vida de los objetos. ¿Qué pasa cuando se trata de eliminar un objeto que esta

referenciado en otro como un estado del mismo?. ¿Qué pasa si se quiere asignar un objeto empotrado

como un atributo de otro objeto almacenado? De acuerdo con la especificación, los estados no pueden

ser compartidos. Esto implica que cuando se asignan objetos empotrados a otros objetos, se debería

realizar una copia implícita de dicho objeto, el modo e implicancia de la copia no se encuentra definido,

por lo que cada implementación puede tener formas distintas de realizarlo.

Existe también una indefinición en cuando a los tipos de datos que soportan los estados. Un

estado puede ser de una interfase o estructura IDL definida dentro del archivo PSDL compilado. Como se

almacena dicha interfase o estructura no se encuentra definido en la especificación.

Otro punto problemático, son los mapeos de los estados al lenguaje Java. En Java existe una

convención que la definición de los métodos que permiten acceder a los atributos que definen el estado

de los objetos. Si un atributo se llama “nombre”, el método para acceder al nombre, se debe llamar

getNombre(), y el método para modificarlo debe llamarse setNombre(nombre). En el mapeo de PSLD a

Java, se define que un estado se lee por un método generado por el compilador, que para el caso de un

estado llamado nombre, será nombre(), y para modificarlo el método deberá llamarse nombre(nombre).

Esta diferencia entre Java estándar, y el mapeo de PSDL, puede traer problemas al integrar los objetos

generados por el compilador con otras herramientas que esperen métodos estándar.

La especificación es amplia en muchos sentidos, dejando puntos libres que pueden ser resueltos

de diferentes formas en distintas implementaciones del servicio. Este tipo de situaciones, hacen más

complicada la portabilidad entre distintas implementaciones del servicio, y van en contra del objetivo

inicial de lograr una interfase común con la que se pueda lograr independencia de la implementación del

servicio.

NOTAS FINALES SOBRE EL SERVICIO DE ESTADO PERSISTENTE

La especificación de PSS está definida solamente para dos lenguajes de programación: C++ y

Java, por lo que el campo de trabajo está limitado a estos dos lenguajes y no a múltiples lenguajes como

se promueve a CORBA.

Algunos conceptos que promueve PSS, tales como los identificadores globales de objetos y los

almacenes, no pertenecen a POO, esto se debe a que el modelo está basado en SQL3 y no en el modelo

de Objetos. Para una persona que no conoce SQL3, estos conceptos resultarán desconocidos e

incrementarán la complejidad de uso del servicio. PSS tampoco respeta algunas de las buenas prácticas

reconocidas en Java, tal como la forma de acceder a los atributos de los objetos. Estas características que

hace que el modelo de PSS se aleje del modelo de objetos incrementan la complejidad de uso del

servicio.

El servicio de estado persistente de CORBA es una solución parcial para obtener soporte de

persistencia de objetos, tiene sus limitaciones y no es necesariamente la opción más simple de utilizar,

pero sin embargo dentro del mundo CORBA, es una estándar lo cual le aporta un gran valor, y para el

objetivo de obtener estado persistente dentro de un servant es completo y funcional.

ALTERNATIVAS A PSS EN JAVA

Al utilizar el lenguaje de programación Java existen varias alternativas para obtener persistencia

de los objetos, algunas de ellas serán analizadas en la siguiente sección. Entre las alternativas se

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

44

encuentran Hibernate [KingG01], JDO [RoodsR01], entre otras. Estas dos herramientas forman parte de

un gran grupo herramientas similares que permiten persistir objetos en una base de datos Relacional,

llamadas genéricamente ORM (Object Relation Mapping o Mapeo objeto - relación). Este tipo de

herramientas no están directamente relacionadas con CORBA, por lo que su integración con CORBA

requiere de trabajo específico del programador sobre la aplicación. Por ejemplo, la integración con el

servicio de transacciones de CORBA deberá ser programada directamente.

Existe otra alternativa en Java denominada J2EE (Java2 Enterprise Edition o edición empresarial

de Java2). J2EE es una especificación de SUN que extiende a Java agregando una serie de servicios entre

los que se encuentran un modelo de componentes (en el que se base CCM [CCM01]) que permiten lograr

un estado persistente. Este modelo de componentes está integrado con CORBA y el servicio de

transacciones. Permiten funcionalidades mucho más avanzadas que PSS, como por ejemplo, acceso

remoto a los objetos almacenados mediante diferentes protocolos entre los que se encuentran GIOP. La

única desventaja de este modelo es que se requiere utilizar un contenedor de componentes, no es

suficiente con la máquina virtual misma. Este modelo debe ser comparado con el modelo de

componentes CORBA CCM.

También sería posible utilizar una base de datos orientada a objetos tal como DB4O [DBO401]

directamente sin PSS, pero nuevamente esta alternativa queda fuera del alcance de CORBA, por lo que la

integración debe ser implementada explícitamente por el programador. Esta y otras alternativa será

analizada en más detalle en la siguiente sección.

Servicio de Estado Persistente CORBA (PSS) Pablo M. Ilardi

45

REFERENCIAS

PSS01 – Persistence State Service V2.0 OMG 2002. http://www.omg.org/cgi-bin/doc?formal/02-

09-06

CORBA01 – Common Object Request Broker Architecture, Core Specification OMG.

http://www.omg.org/technology/documents/corba_spec_catalog.htm

POS01 – Persistent Object Service POS, OMG 2000.

http://www.omg.org/technology/documents/corbaservices_spec_catalog.htm

TS01 – Transaction Service Specification, OMG 2003.

http://www.omg.org/technology/documents/corbaservices_spec_catalog.htm

BgVaDk – Gerald Brose, Andreas Vogel, Keith Duddy – Java Programming with CORBA, Advanced

Techniques for Building Distributed Applications. 3rd Edition.- Wiley 2001.

KjPfTp – Jan Kleindienst, František Plášil, Petr Tuma – What we are missing in the CORBA

Persistent Object Service Specification.

EisenbergMelton01 – SQL:1999, formerly known as SQL3. Andrew Eisenberg, Sybase, Concord,

MA 01742 [email protected]; Jim Melton Sandy UT 84093 [email protected]

Ramakanth01 – Object-Relational Database Systems - The Road Ahead; Ramakanth S.

Devarakonda; http://www.acm.org/crossroads/xrds7-3/ordbms.html

Gamma01 – Design Patterns: Elements of Reusable Object-Oriented Software - Addison-Wesley

Professional Computing Series - by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,

January 15, 1995

CCM01 – CORBA Component Model (CCM) Specification, OMG

http://www.omg.org/technology/documents/formal/components.htm

CCM02 – CORBA Component Model Tutorial, Yokohama OMG Meeting Wednesday, April 24th,

2002. http://www.omg.org/docs/ccm/02-04-01.ppt

KingG01 – Hibernate in Action, Christian Bauer, Gavin King; Manning, 2005. ISBN: 1932394-15-X.

http://www.hibernate.org

RoodsR01 – Java Data Objects, Robin M. Roods; Addison-Wesley, 2003. ISBN 0-321-12380-8.

http://java.sun.com/javaee/technologies/jdo

DBO401 – Database for Objects. http://www.db4o.com/about/productinformation

DEVQTC01 – Developing Quality Technical Information. A handbook for Writers and Editors.

Second Edition. G. Hargis, M. Carey, A. Hernandez, P. Hughes, D. Longo, S. Rouiller, E. Wide. ISBN

0-13-1477490.

46

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

47

IMPLEMENTACIÓN DEL SERVICIO DE ESTADO PERSISTENTE CORBA - PSS

Consideraciónes iniciales .......................................................................................... 51

Partes ....................................................................................................................... 51

Persistencia Transparente..................................................................................... 51

El lenguaje de definición PSDL .............................................................................. 52

Compilador PSDL ..................................................................................................... 52

Bases para el Compilador...................................................................................... 52

Partes del Compilador .......................................................................................... 53

Entender el código de Entrada .............................................................................. 53

Generación del Parser por JAVACC ................................................................... 54

Estructura del árbol generado ........................................................................... 56

Errores a nivel del Parser .................................................................................. 57

Generar el código de Salida .................................................................................. 57

El patrón de diseño Visitor ................................................................................ 57

Identificadores...................................................................................................... 58

Proceso de Compilación........................................................................................ 59

Cómo funciona el compilador ............................................................................... 60

Clases fundamentales que definen al compilador ............................................. 60

PSDLCompiler ............................................................................................... 60

PSDLParser .................................................................................................... 60

NodeProcessor .............................................................................................. 60

CompileProcess............................................................................................. 61

FullTypeDefinition ......................................................................................... 61

BaseStorageElementBuilder .......................................................................... 61

TargetCompiler ............................................................................................. 61

IdentifierProcessor ........................................................................................ 61

AbstractIdentifierProcessor .......................................................................... 62

ModuleIdentifierProcessor ........................................................................... 62

HolderGenerator y HelperGenerator............................................................. 62

CompilerDefinitionWriter ............................................................................. 62

Primera Fase – Procesamiento .......................................................................... 62

Primer ejemplo de generación de una definición: ......................................... 64

Pasos requeridos ....................................................................................... 65

Segunda Fase – Validación ................................................................................ 67

Fallos en el proceso de validación ................................................................. 67

Validación del primer ejemplo de generación de una definición: .................. 68

Tercer Fase – Generación. ................................................................................. 69

¿Qué generar?, generación de código fuente ................................................ 69

“Propio del Servicio”, configuración del Compilador ..................................... 71

Lo que es común a todo servicio ................................................................... 72

Generación de código para el primer ejemplo ............................................... 73

Pasos ......................................................................................................... 73

Conclusiones del Compilador ................................................................................ 74

Conexión del servicio de persistencia con la ORB .................................................... 74

Configuración de la ORB para el acceso a sus referencias iniciales. ....................... 75

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

48

Registro de Conectores para el servicio de persistencia ........................................ 76

Implementación del servicio provisto por diferentes conectores ............................ 77

El conector del Servicio de Persistencia ................................................................ 77

Operaciones del conector ................................................................................. 79

Problemas que presenta la creación de un conector ............................................. 81

Creación de objetos .......................................................................................... 81

El patrón de diseño Template Method .......................................................... 81

Almacenes y Objetos almacenados ............................................................... 82

Almacenes ................................................................................................ 83

Modelo de Delegación .......................................................................... 84

Objetos Almacenados ............................................................................... 84

StorageObjectSpec ................................................................................ 86

StorageObjectIdentifier ......................................................................... 86

Operaciones de búsqueda de Objetos ............................................................... 87

Validación de Claves ......................................................................................... 88

Destrucción de objetos almacenados ................................................................ 89

Diferentes Conectores .......................................................................................... 90

Conector de persistencia temporal ................................................................... 90

Funcionamiento ............................................................................................ 91

Manejo de identificadores ........................................................................ 91

Almacenes temporales .............................................................................. 92

Operaciones de búsqueda ......................................................................... 92

Conector de persistencia durable...................................................................... 92

Requerimientos para un conector de persistencia durable ........................... 92

Distintas alternativas para obtener persistencia durable ............................... 93

Persistencia mediante archivos ................................................................. 93

Persistencia mediante una base de datos relacional .................................. 93

Persistencia mediante una base de datos orientada a objetos .................. 95

DB4O .................................................................................................... 95

Conector persistente con soporte de DB4O .................................................. 99

Registro del Conector ................................................................................ 99

Funcionamiento ........................................................................................ 99

Relación con DB4O ...............................................................................100

Objetos con estado compartido ...........................................................100

Identificadores .....................................................................................101

Operaciones atómicas ..........................................................................101

Almacenes ............................................................................................103

Operaciones de consulta ......................................................................104

Creación de objetos almacenados ........................................................104

Acceso remoto al repositorio ...............................................................105

Transacciones .......................................................................................106

Construcciones Resultantes del Trabajo .................................................................107

Un ejemplo concreto ..............................................................................................108

Código PSDL.....................................................................................................108

Resultado de la compilación del código PSDL ...................................................108

Persona ........................................................................................................109

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

49

PersonaHome ..............................................................................................109

PersonaBase ................................................................................................110

PersonHomeBase .........................................................................................112

PersonaBaseImpl .........................................................................................112

Uso del modelo en una aplicación CORBA .......................................................113

Referencias .............................................................................................................117

Referencias a Bibliotecas ........................................................................................119

50

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

51

CONSIDERACIÓNES INICIALES

Mi idea original para este trabajo fue utilizar C++, pero después de un tiempo de trabajar en un

prototipo del servicio, desistí y comencé a utilizar Java. Las razones fueron sencillas ya que la

especificación se adapta mucho más fácil a Java que a C++ y el modelo de objetos es análogo al de Java.

Java provee de muchas más herramientas de uso libre que C++. Por ejemplo, por el sólo hecho de utilizar

Java, se cuenta con una ORB lista para ser utilizada que es parte de la especificación del lenguaje.

El servicio de estado persistente de CORBA tiene como objetivo principal proveer estado

persistente a los servants. Mi implementación cumple con ese cometido, pero también plantea la forma

de compartir ese estado desde distintas ubicaciones y servants.

PARTES

El trabajo se puede dividir en dos partes principales. La primera se fundamenta en que el servicio

de persistencia requiere que el usuario sea quién defina los tipos o clases que estarán disponibles. Y la

segunda es el servicio de persistencia propiamente dicho. Para la primer parte, el estándar define las dos

alternativas: Persistencia Transparente y uso del lenguaje PSDL.

PERSISTENCIA TRANSPARENTE La persistencia transparente es atractiva ya que parece más simple, sólo es necesario definir los

objetos tal cual se haría en cualquier aplicación. Sin embargo no es completa, tiene algunas limitaciones

tales como la definición de las claves que definen la unicidad de los objetos, la definición de operaciones

que pueden realizar los almacenes de objetos (storage homes), etc. El otro inconveniente que trae

acarreado esta alternativa es el cómo se integra en tiempo de ejecución.

Según la especificación [PSS01] existen cuatro alternativas para integrar la persistencia

transparente:

1- Un pre procesador que agregue las construcciones necesarias del servicio de persistencia al código fuente Java. Por ejemplo, tome un atributo de la clase y modifique el código donde se utilice dicho estado.

2- Un compilador Java especial que haga algo similar al pre procesador antes mencionado. 3- Un pos procesador que realice modificaciones similares al código binario resultante de la

compilación (llamado bytecode en Java), pero en forma binaria. 4- O una máquina virtual Java especial que intercepte los accesos a los atributos de las clases.

El problema de las primeras dos opciones es que requieren que se tenga a mano el código fuente

de las clases, lo cual no es siempre posible. Por consiguiente, las únicas dos opciones posibles son la 3 y 4.

A partir de la versión 5 de Java se han incorporado una serie de alternativas que le permiten a la

máquina virtual exportar puntos de control, que podrían ser utilizados por un posible servicio de

persistencia que implemente persistencia transparente.

Uno de ellos se denomina JVMTI o JVM Tool interface [JVMTI01] (interfase de herramienta para

la máquina virtual), que consiste en que la máquina virtual permite configurar en el momento del inicio

de la misma, una biblioteca nativa llamada agente, que implementará dicha interfase. A grandes rasgos

dicha interfase exporta puntos de control de ejecución del código que se ejecuta dentro de la máquina

virtual. De esta forma, se podría seguir utilizando la misma máquina virtual para lograr lo mismo que lo

descrito en el punto 4. La desventaja es que se trata de una biblioteca implementada en código nativo,

que debe entre otros, soportar las convenciones de las llamadas a funciones definidas en C y C++. Esto

agrega complejidad a la solución ya que no solo se trata de una solución escrita en Java.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

52

Otra alternativa también agregada a partir de a versión 5 de Java se denomina JVM

Instrumentation (Instrumentación) [JVMI01]. Esta alternativa le permite a la maquina virtual utilizar una

biblioteca externa que modificará el código binario de las clases cargadas antes de ser utilizadas. Este

método, entre otros, sirve para interceptar las llamadas a todos los métodos de las clases que se

requiera. La ventaja es que se trata de una herramienta que permite utilizar el mismo lenguaje Java para

modificar el código a ejecutar en tiempo de ejecución. Pero al igual que JVMTI, tiene que ser indicada al

momento de iniciar la máquina virtual y la modificación al código binario o bytecode deberá hacerse cada

vez que se inicie la máquina virtual. La desventaja que comparten estas dos alternativas mencionadas

anteriormente, es que requieren utilizar una máquina virtual que sea compatible con la versión 5 de Java

o superior.

El principal inconveniente de cualquiera de las soluciones planteadas, es que requieren tener

control de la ejecución de la máquina virtual. Las bibliotecas tienen que ser pasadas como parámetro en

la línea de comando que inicie la máquina virtual. No siempre se tiene control de ejecución de la máquina

virtual. Por ejemplo, en el ámbito de ejecución de las aplicaciones web, por lo general, se utilizan los

llamados contenedores web, como pueden ser el Tomcat o Jetty. En dichos entornos coexisten múltiples

aplicaciones para una misma máquina virtual. Y dependiendo de donde se ejecute dicha máquina virtual

es posible que por diversas razones no se tenga el control de la misma o que ni siquiera se conozca el

servidor donde se ejecuta la máquina virtual.

EL LENGUAJE DE DEFINICIÓN PSDL Esta segunda alternativa para la definición de los tipos a utilizar por el servicio de persistencia

requiere de la construcción de un compilador de PSDL que genere las clases en el lenguaje de

construcción del servicio. Es probable que requiera de mucho más trabajo que cualquiera de las

soluciones planteadas en la persistencia transparente, pero es más útil a los efectos del usuario final del

servicio, dado que provee muchas más funcionalidades. Otro punto a favor que tiene, es que podría ser

utilizada en cualquier entorno, sin importar si se tiene el control de la ejecución de la máquina virtual o

no. Además, vale notar que ninguna de las dos alternativas invalida a la otra, es decir, que se puede tener

un servicio de persistencia que provea las dos alternativas o incluso la segunda podría incluir a la primera.

Haciendo un balance de todas las alternativas posibles, la construcción del compilador PSDL es la

que aporta mayor valor agregado, dado que brinda más funcionalidad y tiene menos limitaciones.

Por esta razón, la primer parte del trabajo consiste en la construcción del compilador, y la

segunda en la implementación del servicio de persistencia.

COMPILADOR PSDL

El compilador del servicio de persistencia tiene que transformar el código fuente PSDL en código

fuente Java que cumpla con la especificación del servicio y además debe hacer que las construcciones

concretas construidas por éste, cumplan con alguna implementación del servicio.

Según el objetivo de mi trabajo, el compilador debe proveer la funcionalidad necesaria para

interactuar con el servicio de persistencia. Si bien, un compilador PSDL podría ser utilizado para compilar

archivos IDL, mi compilador se limitará a compilar las construcciones específicas del PSDL junto con

algunas otras del IDL que podrían ser requeridas por PSDL. Si se quiere utilizar IDL siempre está

disponible el compilador IDL de la ORB que se utilice. De todas formas el compilador necesita entender

las construcciones IDL para cumplir con el estándar, aunque no genere código Java para las mismas.

BASES PARA EL COMPILADOR

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

53

El lenguaje PSDL es una extensión al lenguaje estándar de CORBA, IDL. La especificación del

servicio define completamente el mapeo de las construcciones abstractas como almacenes y objetos,

mientras que las concretas son más abiertas y dependen de la implementación del servicio.

La especificación del servicio contiene la gramática del lenguaje. Dicha definición utiliza la

notación EBNF (Extended Backus-Naur form), la cuál es una extensión a BNF (Backus-Naur form).

BNF es la notación formal estándar, definida por John Backus, utilizada para definir la gramática

de los lenguajes [Garshol00]. Algol 60 fue el primer lenguaje popular en utilizarla. EBNF agrega pocos

cambios, pero que simplifican mucho su uso, entre los que se destacan los operadores: ‘?’, ‘*’ y ‘+’ que

reducen la necesidad de utilizar definiciones recursivas.

Debido a que BNF realiza una definición formal del lenguaje (con fundamentos matemáticos y sin

ambigüedades), es posible a partir de ella construir de forma mecánica un parser del lenguaje sin hacer

prácticamente cambios a la definición.

Un parser es una herramienta que toma una entrada de texto realizando un análisis gramatical

del mismo y validando su sintaxis.

PARTES DEL COMPILADOR El proceso de compilación que realiza el “compilador” se puede dividir en dos pasos que tienen

objetivos muy distintos [CooperRice00]. El primer paso es el de entender el código de entrada, y el

segundo es el de traducirlo al código de salida.

Entender Código deEntrada

Generar Código deSalida

RepresentaciónInterna

CódigoFuente

Código SalidaErrores

COMPILADOR 1 - PROCESO DE COMPILACIÓN

Estos dos pasos se comunican mediante una “Representación Interna” del código fuente que

ambos conocen, el primero la genera y el segundo la utiliza. Ambos pasos pueden generar errores que

impidan o no, la generación del código de salida. Estos errores son, por lo general, de distinta naturaleza,

el primero detecta errores de sintaxis mientras que el segundo detecta errores semánticos que no son

visibles sintácticamente.

Dependiendo de la complejidad del compilador, se pueden agregar más pasos intermedios al proceso. Por ejemplo, un paso que realice una optimización de la representación interna que permita generar un código de salida más óptimo que la solución trivial.

ENTENDER EL CÓDIGO DE ENTRADA Para entender el código de entrada es necesario leer el código e interpretarlo para así poder

generar la “Representación Interna”. Este es un proceso común a cualquier compilador y existen formas

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

54

estándar de realizar este paso [CooperRice00]. Se realiza con dos herramientas, la primera es un

analizador lexicográfico y la segunda es un parser.

Un analizador lexicográfico, descompone una secuencia de caracteres en sub-secuencias de los

mismos llamadas tokens (símbolo en inglés) y a su vez clasifica estos tokens según la categoría a la que

correspondan. A esta herramienta también se lo denomina scanner.

El parser toma estas secuencias de tokens, y las analiza para determinar la estructura del código

fuente. El resultado del parser es un árbol que refleja esta estructura.

CódigoFuente

Errores

Analizador Léxico Parser

Entender Código de Entrada

RepresentaciónInterna

COMPILADOR 2 - PRIMER PASO

Tanto el parser como el analizador lexicográfico son responsables de generar los errores cuando el código fuente de entrada no respete las reglas sintácticas del lenguaje.

Existen dos técnicas ampliamente utilizadas en la construcción de parsers, TOP-DOWN y BOTTOM-UP. La mayoría de los parsers utilizan alguna de estas dos técnicas.

- TOP-DOWN: (de arriba hacia abajo en inglés) estos parsers tienen la estrategia de separar la secuencia de entrada en grupos de caracteres grandes, para luego ir separándolos sucesivamente en los elementos fundamentales o tokens. Es básicamente un juego de prueba y error donde se plantea la hipótesis de que una secuencia de caracteres representan una determinada construcción del lenguaje.

- BOTTOM-UP: (de abajo hacia arriba) estos parsers tratan de ubicar los elementos fundamentales o tokens, y en base a ellos tratan de enmarcarlos en las construcciones más grandes definidas en el lenguaje. La complejidad de los parsers radica en como tomar la decisión de que construcción es la que

corresponde a una secuencia de caracteres determinados.

Por lo general, la construcción tanto del parser como del analizador lexicográfico no es una tarea

simple y puede llevar mucho tiempo. Ambas estrategias de procesamiento, se pueden escribir en forma

de algoritmo y en base a él construir un programa que realice el proceso sobre un archivo.

Existen herramientas que tienen el objeto de generar el analizador lexicográfico y el parser en

base a una definición del lenguaje de forma más o menos automática. Esta definición se realiza en

notación EBNF, con algunos agregados propios de la herramienta.

GENERACIÓN DEL PARSER POR JAVACC

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

55

Para la construcción del parser decidí utilizar una herramienta denominada javacc (Java Compiler

Compiler) [Javacc00]. Javacc es una herramienta de uso libre bajo licencia BSD (Berkely Software

Distribution), publicada en la comunidad abierta: java.net, que es patrocinada por la empresa SUN,

creadora del lenguaje Java. Al tener licencia BSD se puede construir y distribuir cualquier tipo de software

construido mediante esta herramienta (tanto comercial como libre) sin tener que pagar por ello.

Javacc es una herramienta que permite generar un parser de tipo TOP-DOWN a partir de una

definición gramatical. Javacc toma como entrada un archivo fuente (generalmente con extensión jj) y lo

transforma en uno o más archivos Java que representan un programa que es capaz de procesar y

entender archivos fuente escritos mediante el lenguaje que defina la gramática. Este programa no tiene

utilidad práctica, tan solo se limita a reportar si existe un error en el archivo fuente procesado.

Javacc utiliza la notación EBNF junto con agregados que le indican a su pre procesador algunos

parámetros que son utilizados para generar las dos rutinas a construir: el parser y el analizador

lexicográfico [JavaccTu00].

Si bien teóricamente con la gramática del lenguaje es posible definir un parser de forma

sistemática [Garshol00], en la práctica es posible que el tiempo en determinar la validez o no de un

archivo o secuencia de caracteres relativamente grande, sea prohibitiva para un parser generado en

forma genérica. Un parser genérico utiliza habitualmente la técnica de backtracking [Dasgupta00] para

analizar la validez de una secuencia de caracteres. Backtracking se aplica cuando al analizar una

secuencia de caracteres se llega a un punto donde se debe tomar una decisión sobre dos o más caminos a

tomar. Se toma la decisión de seguir un camino, luego de un tiempo de seguirlo se llega a un punto donde

se determina que la secuencia de caracteres es inválida, pero entonces es necesario considerar que la

decisión tomada originalmente podría haber sido incorrecta. Se debe volver a dicho punto para

considerar la otra alternativa. Básicamente, cada punto de bifurcación debe ser registrado para poder

volver a él en caso de que se llegue a un punto que determine un error. Esta técnica requiere de mucho

tiempo de procesamiento y memoria, y en muchos casos eso lleva a un programa o sistema que consume

recursos inadmisibles.

Por esto los parsers generados por javacc no utilizan la técnica de backtracking, lo que hacen es

tratar de recaudar la mayor cantidad de información para tomar la decisión correcta, de forma tal de no

tener que volver atrás. Javacc lee los N tokens siguientes en la secuencia para determinar si es válido o no

el árbol. La cantidad de niveles del árbol a procesar por cada nodo, es determinada por el parámetro

LOOKAHEAD del pre procesador. Un valor muy bajo puede determinar que el parser de cómo inválido

algo que no lo es, en cambio, uno muy alto puede hacer que el parser consuma mucha memoria y tiempo

en analizar y procesar el archivo. Por lo general, se define un LOOKAHEAD global y para ciertas

definiciones se pueden aumentar este valor, esto es necesario sobre todo en las definiciones recursivas.

Javacc toma como entrada un archivo de texto que contiene las directivas al pre procesador, la

notación EBNF que define el lenguaje y código Java que será ejecutado por cada construcción que el

parser detecte en tiempo de ejecución. Al ejecutar javacc sobre el archivo, se generan algunas clases Java

que serán utilizadas internamente por el parser y una clase Parser, que es la base para la construcción del

compilador.

La clase Parser generada solamente valida la sintaxis del archivo. Una forma práctica de utilizar el

Parser es utilizar javacc combinado con otra herramienta denominada jjtree. JJtree es un pre procesador

para javacc, que toma como entrada un archivo fuente javacc, y lo adapta de forma tal que el parser

generado por javacc genere un árbol con la estructura del archivo fuente procesado. Este pre procesador

inserta acciones que construirán el árbol a medida que el documento de entrada con código fuente es

procesado.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

56

PSDL.jjtjjtree javacc

PSDL.jj

Node*,javaPSDLParserVisitor.java

PSDLParser,javaUsa

PrimerPaso

SegundoPaso

COMPILADOR 3 - JAVACC

El árbol generado por jjtree estará compuesto por nodos. El tipo de nodo variará en función de la

estructura que el parser haya detectado en el archivo fuente. Se le puede indicar a la herramienta que

genere una clase para cada tipo de nodo que pueda existir en el árbol, este es el caso de mi compilador.

Por lo general, el tipo de nodo se corresponde uno a uno con las definiciones de la gramática del

lenguaje. Por ejemplo:

<psdl_state_type_spec> ::= <base_type_spec>

| <string_type>

| <wide_string_type>

| <abstract_storagetype_ref_type>

| <scoped_name>

Es la definición en notación BNF del tipo de atributo de un estado de un objeto almacenado. En

la definición del árbol, esto se traduce como un nodo de tipo: psdl_state_type_spec, que puede contener

como nodo hijo, algún nodo de los siguientes tipos: base_type_spec, string_type, wide_string_type,

abstract_storagetype_ref_type, o scoped_name. Entonces, jjtree generará una clase para cada uno de

estos nodos.

A veces no es útil tener en el árbol algunos nodos que no serán utilizados por el compilador, en

esos casos se le puede definir al pre-procesador de jjtree que los ignore, por lo que éste no generará una

clase para dicho nodo y obviamente no existirá en tiempo de ejecución un nodo que tenga como hijo a un

nodo de este tipo.

Volviendo al esquema inicial del compilador en dos fases, se puede pensar que el árbol resultado

del procesamiento del parser, puede ser la “Representación Interna” con la que se comunicarán las dos

partes del compilador.

ESTRUCTURA DEL ÁRBOL GENERADO

Como ya mencioné anteriormente la estructura del árbol depende fundamentalmente de:

- La definición que se le haya otorgado a jjtree, y javacc para que generen el parser. - El archivo fuente procesado

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

57

Según mi definición del lenguaje PSDL en notación EBNF, todo árbol generado por el parser tendrá

como raíz un nodo de tipo: Nodepsdl_specification. Este nodo contendrá nodos hijos con las

construcciones encontradas en el archivo procesado. En el caso de PSDL, las construcciones serán las

básicas definidas por el lenguaje más las existentes en IDL.

Todos los nodos generados por jjtree heredan de una clase base generada llamada SimpleNode.

Esta clase tiene como atributos el nodo padre y los nodos hijos junto con operaciones que permitirán

construir el árbol a medida que se procese el archivo de entrada. Además, se le pueden agregar atributos

que podrán ser utilizados cuando se procese el árbol para reportar errores, por ejemplo, los tokens que

definieron la creación del nodo. Los tokens aportan información importante como el número de línea y

columna donde se encuentran en el archivo de entrada.

ERRORES A NIVEL DEL PARSER

La clase Parser se encarga de detectar los errores lexicográficos y sintácticos. Los errores

lexicográficos se traducen en excepciones de tipo TokenMgrError, y los sintácticos en excepciones de tipo

ParseException. Estas dos clases de excepciones pueden reportar precisamente la ubicación del error

detectado en el archivo de entrada, además generan un mensaje de error que describe la causa del

mismo, por ejemplo, “se esperaba un token {...} y no X”.

Los errores lexicográficos ocurren cuando el parser encuentra un carácter no esperado en una

secuencia. Por ejemplo, dada la siguiente gramática para definir un número:

DEFINICION := NUMERO (+ NUMERO)*

, la siguiente secuencia de entrada:

45 – 12

, dará como resultado un error lexicográficos diciendo que se esperaba un carácter “+” después del 12 .

Los errores de tipo sintáctico ocurren cuando los caracteres de entrada son válidos, pero sin

embargo se encuentran ubicados de forma tal que no corresponden a una construcción válida. Por

ejemplo, dada la misma definición anterior de un número, ante una secuencia de entrada de tipo:

45 + + 12

, el parser generará un error sintáctico diciendo que se esperaba un token de tipo NUMERO después del

símbolo “+”.

El proceso de parsing puede detectar sólo esta clase de errores descriptos. Existen otros errores

que no son derivados de la definición gramatical del lenguaje y que no serán detectados por el parser. Por

ejemplo, que un identificador único en el documento se encuentre duplicado. Esta clase de errores

tendrán que ser detectados en la segunda fase del proceso de compilación.

GENERAR EL CÓDIGO DE SALIDA Generar el código de salida en lenguaje Java es el último paso del compilador. La salida será el

resultado de procesar el árbol generado por el parser. La herramienta que provee jjtree para simplificar

este proceso es una opción que se basa en el patrón de diseño Visitor [Gamma01]. Cuanto la opción es

activada el procesador generará una interfase Visitor (en inglés visitante), que permitirá recorrer el árbol.

EL PATRÓN DE DISEÑO VISITOR

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

58

El patrón de diseño Visitor permite que un árbol de nodos sea recorrido o visitado por diferentes

objetos sin que los nodos tengan que estar al tanto de ello. Un ejemplo, sería recorrer el árbol para

generar una representación del mismo en texto o para validar la estructura y la relación entre los nodos,

o generar el código de salida en base al mismo.

Este patrón es utilizado para recorrer estructuras, en este caso un árbol y su beneficio radica en

que la estructura o los elementos que la conforman no tienen que estar al tanto de que se hace con ese

procesamiento. Por otro lado, quien visita la estructura tampoco requiere conocer como está compuesta.

Podrían existir diferentes implementaciones del visitante, sin que los nodos sean afectados por ellos. El

compilador representa una implementación del visitante, y a la par podrían existir otras

implementaciones, por ejemplo, un optimizador del árbol o para buscar errores en el mismo.

class Visitor

compiler::PSDLCompiler

«interface»

parser::PSDLParserVisitor

parser::PSDLParser

«interface»

parser::Node

parser::SimpleNode

parser::Nodepsdl_module

parser::Nodepsdl_directiv e

«realize»

-parser -parser

-parent-chi ldren

«real ize»

COMPILADOR 4 - DIAGRAMA GENERAL DEL COMPILADOR

Básicamente, existe un objeto Visitor, que visitará los nodos del árbol. Todos los nodos heredan

de una clase común o implementan la misma interfase, que define un método aceptar(Visitor), el cual

será implementado por cada tipo de nodo invocando al método visitar en el Visitor pasándose a sí mismo

como parámetro: visitor.visitar(this). El Visitor tendrá que implementar un método visitar distinto por

cada tipo de nodo que quiera manejar de forma distinta o particular.

En el contexto del compilador, el Visitor está representado por la interfase PSDLParserVisitor, la

clase PDLCompiler es una implementación del mismo. La interfase Node define todo aquello que puede

ser visitado. Para cada nodo que sea visitado, existirá una clase concreta que implementará el método

aceptar, que solamente deberá llamar al método visitar del Visitor que reciba como parámetro.

IDENTIFICADORES La gramática de PSDL contiene múltiples definiciones que referencian identificadores. Este

concepto es compartido o heredado de la definición del IDL y por consiguiente del PSDL. Muchos tipos de

construcciones del IDL y PSDL utilizan el concepto de identificador, pero la definición del mismo varía en

función del lugar donde se lo utilice. Por ejemplo, un identificador puede identificar a un tipo de objeto

almacenado o a un método en un almacén. En otros casos, como por ejemplo, cuando se hace referencia

a una entidad definida anteriormente en el código fuente (como en la declaración de un estado), es

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

59

posible utilizar múltiples identificadores para identificarla, dado que la referencia puede ser dada tanto

en forma absoluta como relativa.

Existen múltiples reglas que determinan cuando un identificador es válido y cuando no. Las

mismas están asociadas a la definición que utilice el identificador, y no son explícitas en la definición

gramatical del lenguaje.

El hecho de que el concepto de identificador esté presente, es un buen punto para definir un

diseño que permita un grado alto de reutilización de funcionalidades, ya que será utilizado prácticamente

en todas las construcciones que procese y/o genere el compilador.

PROCESO DE COMPILACIÓN La recorrida del árbol generado plantea algunos obstáculos que tuvieron una gran influencia en

mi diseño del compilador:

1 Era necesario detectar aquellos errores que no fueran detectados por el parser 2 El árbol puede requerir incluir archivos externos, que se definen como “#include” en el código

fuente de entrada. Este proceso puede ser recursivo y además puede llevar a ciclos, que deben ser contemplados.

3 El código a generar para las construcciones concretas depende fuertemente de la implementación del servicio de persistencia, mientras que el código a generar para las construcciones abstractas es independiente de la implementación (según la especificación del servicio).

4 Las referencias a otros elementos puede hacerse tanto en forma absoluta como relativa mediante en uso de los identificadores. Por ejemplo, en Java se puede referenciar a la clase String por java.lang.String o simplemente por String. El compilador de Java determina las referencias relativas en base a los imports de la clase. En PSDL, las referencias relativas se resuelven en base al módulo en el que se esté referenciando a esta otra entidad.

5 Las definiciones pueden hacerse en forma directa, o en dos pasos mediante las declaraciones forward o hacia adelante. Las definiciones forward permiten que la definición sea referenciada, sin estar completa su definición, por ejemplo, para objetos almacenados que tengan declarados estados de su mismo tipo.

Los errores que no detecta el parser son diversos y a veces no son simples de detectar. Algunos

de ellos son:

- Contemplar la existencia de identificadores repetidos en el mismo archivo y / o en los archivos incluidos.

- Validar que las referencias a los identificadores estén disponibles - Validar que la jerarquía tanto de los objetos almacenados como de los almacenes sea

consistente, tanto para las construcciones concretas como para las abstractas. - Validar algunos requerimientos de la definición de los objetos en PSDL, que no están expuestos

en la definición gramatical.

Para procesar los archivos incluidos por el archivo fuente original, es necesario procesar cada

uno de ellos de forma tal que se recaude la información necesaria para las validaciones antes descriptas.

Este proceso debe ejecutar el parser sobre estos archivos y no debe generar el código de salida, además

el proceso ejecutado por el parser debe lidiar con referencias circulares de archivos incluidos.

La existencia de definiciones forward, requiere al menos de dos pasos para poder recolectar toda

la información necesaria para determinar cuando una definición es válida y cuando no.

Por último, pero no menos importante, el código de salida depende fuertemente del servicio de

persistencia, se requiere que el compilador o una parte de él conozca el servicio de persistencia para el

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

60

que generará código. Por ende, se requiere tener un servicio de persistencia totalmente definido para

poder construir el compilador. Como este no era mi caso, yo fui construyendo el compilador y la

implementación del servicio en paralelo, por consiguiente tuve que desacoplar las partes del compilador

que requerían generar código particular de la implementación del servicio.

CÓMO FUNCIONA EL COMPILADOR El gráfico siguiente muestra una división arbitraria de los pasos en los que se divide el proceso de

compilación. El paso 0 es procesar el archivo de entrada, dado que esta es una tarea que es realizada

mayormente por javacc, descripto anteriormente.

De esta forma, el proceso de compilación será descripto en tres fases, la primera fase

“Procesamiento”, la segunda “Validación” y por último “Generación”. Cada una de estas fases requiere

de múltiples clases. A continuación haré una breve descripción de algunas de las más importantes.

composite structure CompilePhases

ArchivoFuente

Objetos Almacenados Almacenes

1 Procesamiento 3 Generación2 Validación

0 Parseo

COMPILADOR 5 - FASES DEL COMPILADOR

CLASES FUNDAMENTALES QUE DEFINEN AL COMPILADOR

Definí algunas clases fundamentales que están encargadas de resolver los problemas

anteriormente descriptos. Algunas de ellas son abstractas y tienen múltiples clases que heredan de ella,

por lo general, me limitaré a describir los objetivos de cada una y no los detalles de su implementación.

PSDLCOMPILER

Esta clase modela al compilador en sí mismo, es la cara visible. Toma como entrada un archivo

fuente que será compilado y da como resultado los archivos fuentes generados. El archivo fuente es

procesado por el parser, el cual genera el árbol que describe el archivo fuente original. Este árbol será

procesado por esta clase mediante el patrón Visitor descripto anteriormente, dado que PSDLCompiler

implementa la interfase PSDLParserVisitor.

PSDLPARSER

Esta clase es la encargada de procesar el archivo de entrada generando el árbol que lo

represente. El compilador tiene una instancia de esta clase como atributo.

NODEPROCESSOR

Algunos nodos del árbol requieren de varios niveles del mismo para completar una definición, por

lo general, para cada nodo del árbol de este tipo que el compilador visite, éste creará una instancia del

procesador de nodo que es responsable de tomar acciones con la rama del árbol que se esté visitando.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

61

Estas clases implementan la interfase NodeProcessor. Cuando se termine de procesar este nodo, el

compilador se lo hará saber al procesador con un mensaje de tipo ‘finalizarProceso’.

COMPILEPROCESS

El compilador creará un CompileProcess inicial para procesar el árbol. Esta clase modela el

proceso de compilación de un archivo. El proceso tiene como objetivo recolectar las definiciones

declaradas en el árbol. Estas definiciones serán instancias de FullTypeDefinition. Existirá una instancia de

CompileProcess por cada archivo procesado, es decir, si se incluye un archivo en el archivo fuente

original, esto resultará en la creación en otro proceso de compilación para dicho archivo. Adicionalmente,

el proceso mantiene información de estado del procesamiento del árbol, que es muy importante para

realizar las validaciones.

FULLTYPEDEFINITION

Es una superclase que modela una construcción en el lenguaje Java. Existen dos clases concretas

que heredan de ella una es InterfaceDefinition, y la otra ClassDefinition. La primera modela una interfase

y la segunda una clase. Esta clase consta de los atributos básicos que puede tener un objeto Java, tales

como: definición de métodos y atributos.

Estas definiciones serán construidas a medida que se procese el árbol por objetos de tipo

BaseStorageElementBuilder. Los builders serán notificados de los atributos de las definiciones a medida

que el árbol sea procesado.

BASESTORAGEELEMENTBUILDER

Builder (constructor en inglés) es un patrón de diseño que permite encapsular la construcción de

un objeto complejo permitiéndole al objeto que lo utiliza deslindarse de la responsabilidad de cómo

construir dicho objeto [Gamma01].

Existen diferentes subclases de BaseStorageElementBuilder, una para cada tipo de construcción:

objetos almacenados abstractos que generan interfases Java, objetos almacenados concretos que

generan clases Java, almacenes abstractos que generan interfases y almacenes concretos que generan

clases.

Dichos builders tratan con el problema de la generación de definiciones que se correspondan con

la implementación del servicio de persistencia, para ello utilizan otra clase muy importante llamada

TargetCompiler.

TARGETCOMPILER

TargetCompiler es una interfase que define las operaciones requeridas para generar definiciones

concretas (clases Java) que dependen de la implementación del servicio. Por ejemplo, de cual clase debe

heredar una clase que represente un objeto almacenado concreto que deba ser utilizado por una

implementación del servicio de persistencia que se basa en archivos.

IDENTIFIERPROCESSOR

Cada proceso de compilación necesita de un objeto que será el encargado de procesar los nodos

que representen un identificador en el árbol. Esta clase es necesaria debido que los identificadores

pueden ser definidos tanto en forma absoluta como relativa. Cuando están en forma relativa, “qué

identifican”, estará dado por el módulo que el proceso esté navegando.

Cada proceso tiene un procesador de identificadores que lleva la cuenta de los identificadores que

no fueron procesados realmente, es decir, si este objeto tiene que procesar un nodo de identificador es

porque se produjo un error interno en el compilador, y alguna definición no fue contemplada o fue mal

procesada. Cuando el compilador visite un nodo que defina una entidad, el objeto que procese ese nodo

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

62

será responsable de indicarle al proceso cual es el objeto que procesará el siguiente nodo de identificador

en el árbol.

De esta forma cada vez que se visite un nodo que represente un identificador, el procesador que

esté definido en el proceso será notificado de ello.

ABSTRACTIDENTIFIERPROCESSOR

Existen múltiples nodos del árbol que dependen de un identificador para concretar una

definición válida. Esta clase base permite tomar del proceso el identificador para luego hacer con él lo

que corresponda al tipo de nodo.

MODULEIDENTIFIERPROCESSOR

Esta clase es una subclase de AbstractIdentifierProcessor y además implementa la interfase

NodeProcessor, cuando un nodo que identifique un módulo sea visitado, el compilador le indicará al

proceso que el procesador de identificadores a utilizar será una instancia de esta clase. Cuando esta clase

procese un identificador le indicará al proceso que el paquete de Java que se está procesando en ese

momento, es el anterior más el nuevo identificador. Cuando se termine de procesar el nodo que define al

módulo, se restaura en el proceso el paquete que se estaba procesando anteriormente.

HOLDERGENERATOR Y HELPERGENERATOR

Según la especificación, las interfases generadas deben generar sus respectivas clases Helper y

Holder acordes con la especificación de CORBA [IDL2Java]. Todas las definiciones abstractas del servicio

requieren de sus respectivas clases Holder y Helper. HolderGenerator y HelperGenerator se encargan, en

base a una instancia de FullTypeDefinition, de generar las clases Helper y Holder respectivamente. Una

clase holder se utiliza como objeto que permite pasar parámetros a métodos IDL, tanto de entrada como

de salida. Dicha clase almacena un objeto del tipo que transporta. Soporta operaciones para leer o

escribir el objeto que almacena en un stream de bytes (una secuencia de bytes que pueden definir un

conjunto de objetos en su representación binaria). Las clases Helper, se utilizan para interactuar con el

objeto genérico Any de CORBA. Any es un contenedor de cualquier objeto definido en IDL. Se necesita de

una clase Helper, para insertar y sacar un objeto de un tipo específico del contenedor.

COMPILERDEFINITIONWRITER

Esta clase es la responsable de plasmar en archivos fuente Java, las definiciones generadas por el

proceso de compilación. El compilador tiene un atributo de este tipo que será invocado una vez por cada

definición recolectada por el proceso al finalizar el proceso de compilación. Para generar el archivo

fuente, esta clase creará una instancia de un objeto de tipo FileBuilder, que procesará la definición a

construir interrogándola sobre cada uno de sus atributos.

PRIMERA FASE – PROCESAMIENTO

El objetivo de esta fase es generar un CompileProcess, que contendrá todas las definiciones que

tendrán que ser generadas en código fuente en la tercera fase, junto con otras que son necesarias para

generar y/o validar las definiciones. Para ello se parte del árbol generado por el parser, cuya raíz, en el

caso de mi definición del lenguaje, será siempre de tipo ‘Nodepsdl_specification’.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

63

class ClassDefinitions

parser::SimpleNode

+ childrenAccept(PSDLParserVisitor, Object) : Object+ dump(String) : void+ getFirst() : Token+ getLast() : Token+ j j tAddChild(Node, int) : void+ j j tClose() : void+ j j tGetChild(int) : Node+ j j tGetNumChildren() : int+ j j tGetParent() : Node+ j j tOpen() : void+ j j tSetParent(Node) : void+ setFirst(Token) : void+ setLast(Token) : void+ SimpleNode(PSDLParser, int)+ toString() : String+ toString(String) : String

parser::Nodepsdl_specification

+ j j tAccept(PSDLParserVisitor, Object) : Object+ Nodepsdl_specification(PSDLParser, int)

COMPILADOR 6 - NODOS DE ÁRBOL

Todos los nodos del árbol heredan de la clase SimpleNode. Las dos clases que se muestran en el

diagrama anterior, fueron generadas por el procesador jjtree. A los efectos de procesar el árbol, los

únicos métodos que importan son ‘jjAccept’ y ‘childrenAccept’. El primero tiene que estar implementado

en cada clase de nodo en particular, dado que así lo requiere el patrón Visitor. El segundo será invocado

por el compilador para procesar el siguiente nivel del árbol. La implementación es tan simple como tomar

cada nodo hijo e invocar al método jjAccept sobre él.

Entonces el proceso comienza cuando se invoca al método jjAccept sobre el nodo raíz, con dos

parámetros, el primero el compilador mismo y el segundo el proceso de compilación. Para cada nodo los

pasos son:

o Se visita el nodo. El nodo le indica al compilador que se está visitando un nodo del tipo que corresponde.

o El compilador realiza una operación en base al tipo de nodo, o Se visitan a los hijos del nodo, donde el proceso comienza nuevamente, hasta que no queden

nodos por visitar.

En este punto, el proceso de compilación habrá recolectado todas las definiciones generadas al

visitar nodos de definición.

El siguiente diagrama de objetos muestra la interacción entre los objetos para concretar la

primera fase, donde se tendrá como resultado el proceso de compilación.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

64

object SimpleCompileProcess

aCompiler :PSDLCompiler

aParser :PSDLParser

process :CompileProcess

endUser

«interface»target :

TargetCompiler

registrySingleton :TargetCompilerRegistry

w riter :CompilerDefinitionWriter

aSpecification :Nodepsdl_specification

identifierProcessor :CounterIdentifierProcessor

1: PSDLCompi ler(fi leName,targetName) :aCompiler

1.1: forName(name) :target

1.2: lookup

1.3: PSDLParser(fi leName) :aParser

1.4: CompilerDefinitionWriter(aCompi ler) :writer

2: compi le(targetFolder)

2.1: parse() :aSpeci fication

2.2: Nodepsdl_speci fication(aParser,id)

2.3: CompileProcess(aCompiler,idProcessor)

2.4: new

3: j jtAccept(visi tor,process)

3.1: visit(node,process) :Object

3.2: childrenAccept(aCompiler, process)

3.3: finishProcess(compi ler,process)

COMPILADOR 7 - PRIMERA FASE

En mi implementación existen cuatro tipos de nodos que agregan definiciones al proceso de

compilación, los cuales son: nodos de definiciones abstractas de objetos almacenados y almacenes, y los

nodos de las respectivas definiciones concretas.

Cada tipo de nodo que genere una definición tiene una subclase de BaseStorageElementBuilder,

que se encargará de acumular la información necesaria para construir la definición. Por ejemplo, en el

caso de un objeto almacenado abstracto, el builder que se crea es de tipo: AbstractStorageTypeBuilder.

PRIMER EJEMPLO DE GENERACIÓN DE UNA DEFINICIÓN:

A continuación voy a ilustrar la secuencia de ejecución para llegar a construir un proceso de

compilación que contenga la definición de un objeto almacenado. El código PSDL a procesar será el

siguiente:

En el código anterior se define un objeto almacenado dentro de un módulo con un único estado

llamado nombre y de tipo string (secuencia de caracteres).

module ejemploFaseUno { abstract storagetype Prueba { state string nombre; }; };

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

65

El árbol generado por el parser será el siguiente:

PASOS REQUERIDOS

1- El compilador genera un CompileProcess, inicialmente con un IdentifierProcessor que llevará la cuenta de los identificadores no procesados, una instancia de CounterIdentifierProcessor.

2- Se inicia el proceso de recorrido del árbol con el nodo raíz Nodepsdl_specification, llamando al método jjAccept con el proceso creado anteriormente.

3- El nodo Nodepsdl_specification le indica al compilador que se visita a este tipo de nodo llamando al método visit con sí mismo como parámetro y el proceso que recibió anteriormente. Como el compilador no necesita realizar otra tarea adicional, sigue el proceso de recorrido del árbol llamando al método childrenAccept en el nodo visitado.

4- El primer nodo hijo es Nodepsdl_module, el cual termina llamando al método visit del compilador. Al visitar este nodo el compilador instancia un objeto de tipo ModuleIdentifierProcessor. Cuando el ModuleIdentifierProcessor es creado, éste le indica al proceso que el nuevo procesador de identificadores será él. Luego de esto, se sigue procesando la rama del árbol debajo del módulo.

5- El primer nodo debajo del nodo del módulo es Nodeidentifier. Al visitar este tipo de nodo el compilador le pide al proceso, el procesador de identificadores para procesar este nodo, que en este caso será ModuleIdentifierProcessor (definido en el paso anterior). Esta clase toma el identificador y genera una definición de paquete Java indicándole al proceso actual cual es el paquete que corresponde, en este caso: será ejemploFaseUno. Luego restaura el procesador anterior y sigue procesando los nodos hijos. Como el nodo de identificador no tiene nodos hijos, se vuelve a procesar los nodos hijos del nodo que definió el módulo.

6- El siguiente nodo hijo es Nodeabstract_storagetype. Cuando el compilador visita este tipo de nodos, crea una instancia de AbstractStorageTypeBuilder. Cuando este tipo de nodo es creado, éste le pide al proceso actual el paquete Java procesado (definido en el paso anterior), inicializando la definición del objeto con él. Además le indica al proceso actual que él mismo será el procesador de identificadores. Luego de ello se procesan el nodo con sus hijos.

7- El primer nodo hijo será Nodeidentifier. Cuando se visite este nodo se terminará utilizando el identificador de procesos definido en el proceso, que es justamente el nodo Nodeabstract_storagetype, definido en el paso anterior. Este nodo tomará el identificador definido, en este caso Prueba, y lo asociará a la definición que se esté construyendo del objeto almacenado. Restaura nuevamente el procesador de identificadores anterior y sigue procesando los nodos hijos.

8- El siguiente nodo hijo de Nodeabstract_storagetype es Nodepsdl_state_dcl, en este caso el compilador creará una instancia de objeto de tipo StateMemberBuilder, y luego procesará los nodos hijos de éste.

9- El primer nodo hijo será Nodepsdl_state_type_spec, el cuál define el tipo de dato del estado que se está definiendo. En este caso, el compilador instancia un objeto de tipo StorageTypeStateDeclarationProcessor, que al ser creado le indica al proceso actual que él será

Nodepsdl_specification |-->Nodepsdl_module |-->Nodeidentifier(ejemploFaseUno) |-->Nodeabstract_storagetype |-->Nodeidentifier(Prueba) |-->Nodepsdl_state_dcl |-->Nodepsdl_state_type_spec | |-->Nodestring_type |-->Nodesimple_declarator |-->Nodeidentifier(nombre)

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

66

el objeto encargado de registrar los próximos identificadores a procesar del árbol. Luego de esto se procede a procesar los nodos hijos del nodo.

10- El único nodo hijo de Nodepsdl_state_type_spec, en este caso es Nodestring_type, que al ser procesado por el compilador lo único que hace es indicarle al builder actual que el tipo de dato es STRING.

11- Como no hay más nodos hijos del Nodepsdl_state_type_spec, se le indica a este nodo que se ha completado el procesamiento de sus hijos. En este punto, el nodo tiene la oportunidad de armar la definición completa del tipo de dato del estado. Como en este caso es un STRING no hay más nada que hacer, pero si se tratara de un tipo de dato, como por ejemplo, una referencia a otro tipo de objeto almacenado definida en forma relativa al paquete actual, se transformaría en una referencia a un tipo absoluto. Luego se restaura el procesador de identificadores anterior. Y se siguen procesando los nodos hijos de Nodepsdl_state_dcl.

12- El próximo nodo hijo es Nodesimple_declarator. Cuando el compilador detecta este tipo de nodos instancia un objeto de tipo SimpleDeclaratorIdentifierProcessor, que simplemente se pone a sí mismo como procesador de identificadores. Al procesar el siguiente nodo de identificador se almacenará el valor nombre.

13- Como no hay más nodos por procesar, StateMemberBuilder tomará el identificador generado por SimpleDeclaratorIdentifierProcessor, y definirá el nombre del estado. En este punto, el nodo puede realizar una validación simple, que es que no exista otro estado con ese nombre dentro de la lista de estados que se pueden definir agrupadamente. Este tipo de validaciones no pueden ser realizadas por el parser, ya que no son parte de la definición gramatical del lenguaje.

14- Luego de que se procesaron todos los nodos por AbstractStorageTypeBuilder, éste tomará como resultado de visitar la rama, lo que define al StateMemberBuilder, y lo agregará como definición de un estado. Además, realizará una serie de validaciones, por ejemplo, si el tipo de estado es una referencia a otro tipo de objeto, éste debe estar definido previamente. Nuevamente estas validaciones se hacen en este punto ya que no es están definidas en la gramática del lenguaje que define al parser. De todas formas esta validación será parcial ya que es también necesario validar la unicidad de los estados dentro de la jerarquía a la que pertenezca el objeto almacenado. Esta validación será realizada en la siguiente fase, dado que en este punto no está definida dicha jerarquía.

15- En este punto AbstractStorageTypeBuilder terminó de procesar todos los nodos hijos y puede registrarse con el proceso actual como un tipo de objeto almacenado definido durante el proceso. Al procesar el registro, el proceso tiene la oportunidad de realizar algunas operaciones, como validar definiciones duplicadas o reemplazar una definición anterior parciales (forward). El caso de las definiciones parciales se da cuando en el archivo fuente se encuentra una definición con solamente un identificador, de forma tal que pueda ser referenciada en otras definiciones sin estar aún definida completamente en el documento.

16- Se han terminado de procesar todos los nodos. El compilador le indica al procesador de identificadores actual, que se ha completado el proceso. CounterIdentifierProcessor sabrá en este punto si alguno de los identificadores no ha sido considerado, en cuyo caso generará un error interno de compilación.

17- Finalmente, el proceso de compilación está completo y la primera fase del compilador.

El proceso de la fase anterior en el ejemplo da como resultado un objeto de tipo CompileProcess,

que contendrá un objeto que, en principio, representa la posibilidad de generar el código para la

definición del objeto almacenado ejemploFaseUno.Prueba. Este objeto es de tipo

AbstractStorageTypeBuilder y representa la posibilidad de genera código porque su uso depende de la

siguiente fase. En algunos casos, el proceso de compilación puede ser generado como resultado de la

inclusión de un archivo en el documento original que se esté compilando, y su utilidad es la de permitir

validar las definiciones que se declaren en el archivo original y no la de generar código fuente.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

67

Como puede verse, la compilación de una estructura simple como la descripta anteriormente

requiere de múltiples objetos e interacciones entre los mismos.

Resumiendo, el resultado siempre es un objeto de tipo CompileProcess. Dicho objeto almacena la

lista de definiciones existentes en el archivo original. Estas definiciones se concretan siempre en objetos

que heredan de la clase BaseStorageElementBuilder. Esta familia de clases sigue el patrón Builder

(constructor) definido en Gamma01. Estos objetos serán utilizados en la última fase para generar los

archivos fuente resultantes.

SEGUNDA FASE – VALIDACIÓN

Esta fase se caracteriza por realizar todas las validaciones necesarias que no pudieron ser

concretadas en las fases anteriores. A medida que se va procesando el árbol se va recolectando

información que sirve tanto para construir el resultado, como para realizar todas las validaciones que

describo a continuación.

Al invocar el método validate sobre el proceso de compilación generado en la fase anterior se

inicia esta fase. Cada una de las definiciones acumuladas será validada. Cada definición generada hereda

de la clase abstracta BaseStorageElementBuilder, la cual define un método validate que será invocado

para cada definición registrada en el proceso. Cuando la fase sea exitosa el resultado de la validación del

proceso será la lista de nombres de las definiciones que deberán ser generadas. Esta lista será la entrada

para la siguiente fase. Cuando la fase falle, su resultado será una excepción de tipo

PSDLCompilerException.

FALLOS EN EL PROCESO DE VALIDACIÓN

Existen múltiples circunstancias por las cuales esta fase puede fallar. Cada tipo de falla tiene su

representación en una subclase de PSDLCompilerException, a continuación enumeraré las más comunes:

- DefinitionNotFoundException: esta excepción indica que en alguna definición PSDL se hace referencia a otra definición, y ésta no pudo ser encontrada. Las causas pueden ser múltiples, por ejemplo hacer referencia a una definición que se encuentra más adelante en el archivo sin haberla declarado como forward, o simplemente un error de tipografía en el nombre.

- DuplicateDefinitionFoundException: ocurre cuando se trata de definir una entidad que ya se encontraba definida anteriormente, en el mismo archivo o en otro incluido anteriormente.

- IllegalFactoryMethodException: esta excepción representa que el compilador encuentra una definición inválida de un método factory. Esto ocurre cuando se definen nombres de estados no existentes como parámetros al método, o bien cuando se agrega más de una vez el nombre del estado al método.

- IllegalKeyDefinitionException: representa una definición invalida de una clave. Puede ser tanto por contener un estado no existente o tener más de una vez el mismo estado como parte de la clave.

- IllegalStateDefinitionFound: se trata de una excepción que se produce cuando la definición de un estado esta dada por un tipo inválido, como por ejemplo, un almacén en vez de un tipo de objeto almacenado.

- IncludeFileException: es la excepción que se genera cuando se trata de incluir un archivo y este no fue encontrado.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

68

class exceptions

DefinitionNotFoundException

+ DefinitionNotFoundException(ful lTypeDefinition, referencingDefinition)

DuplicateDefinitionFoundException

+ Dupl icateDefini tionFoundException(definition, message)

IllegalDefinitionFoundException

+ IllegalDefinitionFoundException(fullTypeDefinition, message)

IllegalFactoryMethodException

+ Il legalFactoryMethodException(factoryName, message, referencer)

IllegalKeyDefinitionException

+ Il legalKeyDefinitionException(keyName, message, referencer)

IllegalStateDefinitionFound

+ Il legalStateDefinitionFound(parent, stateDefinition, message)

IllegalStateException

+ IllegalStateException(message)+ IllegalStateException(message, e)

IncludeFileException

+ IncludeFileException(message)+ IncludeFileException(message, cause)

RuntimeException

PSDLCompilerException

+ PSDLCompilerException(message)+ PSDLCompilerException(message, cause)

COMPILADOR 8 - EXEPCIONES DE VALIDACIÓN

Existen dos clases de excepciones de validación que representan errores mas generales de la

definición, que son: IllegalDefinitionFoundException y IllegalStateException.

La primera excepción se genera cuando se llega a un punto del procesamiento en el que la

definición que se esta procesando debe ser marcada como inválida dado que viola alguna regla. Pero esto

no se debe a, por ejemplo, un estado mal definido o un estado duplicado, sino a que la suma de atributos

de la definición la hacen inválida. Por ejemplo, cuando en la definición de una herencia de almacenes se

detecta que el tipo almacenado de un almacén hijo no es un subtipo de tipo almacenado por alguno de

los almacenes padres. O cuando se trata de agregar una clave primaria a un nodo que no es la raíz de la

familia de almacenes. Por lo general, como este tipo de errores no se pueden atribuir a una parte de la

definición, el usuario final tendrá que verificar a que corresponde el error.

La segunda excepción IllegalStateException está asociada a potenciales errores internos del

compilador. Por ejemplo, tratar de construir definiciónes en base a definiciones no validadas, o tratar de

agregar una definición más de una vez a un proceso de compilación, etc. Este tipo de errores es probable

que se produzcan cuando se agreguen extensiones al compilador para generar código para otros servicios

de persistencia.

A continuación voy a ejemplificar la serie de pasos para la validación del ejemplo de la fase

anterior.

VALIDACIÓN DEL PRIMER EJEMPLO DE GENERACIÓN DE UNA DEFINICIÓN:

1- El compilador invoca al método validate del CompileProcess. 2- Para cada definición acumulada, el proceso ejecuta el método validate. En este caso la única

definición acumulada es de tipo AbstractStorageTypeBuilder. a. AbstractStorageTypeBuilder verifica que no se trate de una definición forward. Esto sería un

error ya que se referencia a una definición no existente en el documento. b. AbstractStorageTypeBuilder verifica que no existan operaciones o método declarados

dentro de la jerarquía del objeto. Para ello visita todas las definiciones que sean parte de su jerarquía recolectando todos los métodos definidos. Todas las definiciones de sus supertipos tienen que ser accesibles desde el proceso de compilación o alguno de los procesos de compilación resultantes de los archivos incluidos en el proceso.

c. AbstractStorageTypeBuilder verifica que no existan nombres de estados duplicados en la jerarquía. El proceso es similar al punto anterior. Como la definición de un objeto almacenado abstracto soporta la herencia en forma diamante, pueden existir estados o método duplicados en la jerarquía, pero lo importante es que estas definiciones duplicadas provengan del mismo origen, en este caso de la misma definición de objeto almacenado.

d. AbstractStorageTypeBuilder se marca como validada y el proceso de validación concluye.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

69

TERCER FASE – GENERACIÓN.

Una vez validado el proceso de compilación generado en la fase uno, se deben generar todos los

archivos fuente Java que resulten de compilar el archivo PSDL. Esta fase da comienzo cuando se invoca al

método generate del objeto CompileProcess resultante de la fase uno.

Como mencioné anteriormente según la especificación del servicio, las definiciones abstractas

debieran ser independientes de la implementación del servicio, mientras que para las concretas se

recomienda mantenerlas lo más separadas posible de los detalles de implementación del servicio. Esto

implica que el código a generar dependerá en algunas circunstancias de la implementación del servicio y

en otras será dependiente de la especificación del mismo.

Hasta este punto, ninguna parte del compilador dependía de la implementación del servicio. Es

una prioridad mantener la separación del compilador de la impementación del servicio de persistencia, ya

que me permite tener dos o más implementaciones del servicio de persistencia sin tener que modificar al

compilador. De igual forma le permite a otra persona utilizar el mismo compilador para su propia

implementación del servicio de persistencia. En definitiva, promueve un bajo acoplamiento entre el

compilador y la implementación del servicio.

Esto requiere que al menos una parte del compilador sea configurable o extensible en función del

servicio de persistencia que se quiera utilizar. Cualquiera de los posibles servicios de persistencia que

utilicen el compilador, requerirán también generar el código fuente resultante, parte del cuál será

compartido y otra será propia.

Al tener un único compilador para múltiples servicios, traté de lograr el mayor grado de

reutilización de código, de manera de minimizar el trabajo requerido. Para ello dividí esta fase en tres

partes, expresadas en el gráfico siguiente:

composite structure Generación

Propio del Serv icio Común a todo Serv icio

Generación de Código

COMPILADOR 9 - TRES PARTES

GENERACIÓN DE CÓDIGO FUENTE

Cualquier implementación del servicio requerirá generar código fuente. La forma de lograr un alto

grado de reutilización entre diferentes implementaciones del servicio es definir un modelo común que

puedan utilizar todas las implementaciones. El gráfico 10 muestra el “qué generar”, básicamente es un

modelo casi completo de cualquier elemento que se quiera definir en lenguaje Java, una clase, interfase,

etc.

Se trata de una serie de clases que modelan las distintas partes que podrían componer un archivo

fuente. Las clases representadas en el gráfico son la base para un generador de código fuente

independiente del compilador. A continuación, daré una breve descripción de algunas de las clases más

importantes.

� Type Definition: la definición de un tipo de objeto en Java está dada por un paquete y un nombre. � Import Declaration: todo archivo fuente Java puede importar otras definiciones para poder

utilizarlas directamente. Considero que siempre se importan tipos de objetos, por lo cual, una declaración de import es simplemente una referencia a una definición de un tipo. También se podrían importar paquetes enteros pero no creo que sea algo necesario, por lo que esta definición no lo soporta.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

70

� Instance Definition: es simplemente una definición de un tipo nombrada, de forma tal que puede ser utilizada como parámetro a un método o en la declaración de un atributo.

class generationLite

NestedGenerator

AccessibilityDefinition

AttributeDefinition

ClassDefinition

ConstructorDefinition

DefinitionFactory

ElementTypeKeyw ord

FullTypeDefinition

Comparable

ImportDeclaration

InstanceDefinition

InterfaceDefinition

Keyword

MethodDefinition

OperationDefinition

NestedGenerator

PackageDefinition

TypeDefinition

COMPILADOR 10 - QUÉ GENERAR

� Accessibility Definition: es la modelización de los niveles de accesibilidad que pueden definirse en Java, por ejemplo público, estático, etc.

� Attribute Definition: se define como la definición de una instancia con un nivel de accesibilidad. Esto permite, por ejemplo, modelar un atributo de una clase.

� Operation Definition: Una operación es una superclase, que sirve como base para definir métodos y constructores. Una operación está definida por una lista de instancias que serían los parámetros, junto con una visibilidad y una definición de un tipo que representa el tipo de valor que retorna la operación. Un método solo agrega un nombre a la superclase.

� FullTypeDefinition: Se trata de otra superclase que representa la definición completa de un tipo en Java. Es la base para definir una interfase o una clase. Se compone de una lista de atributos, una lista de métodos y la visibilidad del tipo. También tiene una lista de interfase que implementa o extiende. La definición de una clase agrega la posibilidad de heredar de otra clase.

� Keyword: es cualquier palabra clave reservada en el lenguaje. � DefinitionFactory: Es una clase basada en el patrón de diseño Factory [Gamma01]. Básicamente se

trata de una clase que permite generar definiciones basadas en clases reales de Java.

Cada implementación del servicio, será responsable de generar sus clases mediante el uso de

este modelo.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

71

Desde la perspectiva del CompileProcess, cada builder generado en la primer fase generará al

menos un FullTypeDefinition cada uno. Cada una de estas definiciones de tipos genera un archivo fuente.

Como se construyen estos tipos es transparente para el compilador y será la tarea del TargetCompiler.

class fileBuilding

«interface»

Generator

+ addTo(TypeDefinitionAppender) : void+ wri teYouSelfInto(Fi leBui lderColaborator) : void

TextAppender

«interface»

FileBuilderColaborator

+ attributeEnd() : void+ attributeStart() : void+ bodyEnd() : void+ bodyStart() : void+ getFileProperties(Generator) : Map<String, Object>+ getStringRepresentation(TypeDefini tion) : String+ methodEnd(boolean) : void+ methodStart(boolean) : void+ wri teAccesibi l ity(Accessibi l i tyDefinition) : void+ wri teBlock(String, Generator, Map<String, Object>, boolean) : void+ wri teExceptions(List<TypeDefini tion>) : void+ wri teInstance(InstanceDefini tion) : void+ wri teKeyword(Keyword) : void+ wri teLiteral(String) : void+ wri teOperationWithParameters(String, List<InstanceDefini tion>, boolean) : void+ wri teSuperClass(TypeDefini tion) : void+ wri teSuperInterfaces(Keyword, List<TypeDefinition>) : void+ wri teTemplateText(URL, Map<String, Object>, boolean) : void+ wri teTypeDefini tion(TypeDefinition) : void

T:extends FullTypeDefinition

FileBuilder

+ bui ld(URL, Fi le) : void+ bui ld(Fi le) : void+ Fi leBui lder(T , Map<String, Object>)+ with(D) : Fi leBui lder<T>

FileBuilder::LocalFilebuilderColaborator

generation::CompilerDefinitionWriter

+ COMPILER_KEY: String = "compi ler" {readOnly}- headerTmpl: URL {readOnly}- prop: Map<String, Object> {readOnly}

+ Compi lerDefini tionWri ter(PSDLCompi ler)# getCompi lerProperties() : Map<String, Object># getFileHeaderTmpl() : URL+ wri te(T, Fi le) : void

FullTypeDefinition

compiler::CompileProcess

«real ize»

«real ize»

#types

«instantiate»

«instantiate»

«instantiate»

COMPILADOR 11 - GENERACIÓN DE ARCHIVOS

El gráfico anterior define como a partir del modelo, se procede a generar los archivos fuentes.

Cada parte del modelo implementa la interfase Generator. Esta interfase define que el objeto que la

implementa sabe como agregarse a sí mismo a un objeto encargado de construir un archivo, mediante el

método writeYourSelfTo. Este método recibe como parámetro un objeto que implementa la interfase

FileBuilderColaborator. Una instancia de FileBuilder, será la encargada de tomar cada una de las

definiciones construidas en el CompileProcess y mediante una llamada al método writeYourSelfTo,

construirá el archivo fuente para cada tipo definido.

“PROPIO DEL SERVICIO”, CONFIGURACIÓN DEL COMPILADOR

La implementación del servicio de persistencia debe proveerle al compilador una clase que le

permita a éste generar código propietario. Esta clase deberá implementar la interfase TargetCompiler.

Esta interfase tiene definidas todas las operaciones que requieran construir definiciones particulares para

la implementación del servicio. Por ejemplo, el método generateRead, indica que debe generarse la

implementación de un método para leer un estado de un objeto almacenado. El compilador conoce los

TargetCompilers que existen por medio de la clase TargetCompilerRegistry, que modela un registro al

que se accede por nombre del target. Cuando el compilador es creado se le indica a éste cual es el

servicio de persistencia para el que se generará el código. Con este nombre se accederá al registro del

compilador para recuperar el TargetCompiler necesario.

¿Cómo se definen los posibles targets para el compilador?. TargetCompilerRegistry es un

Singleton (Gamma01), cuya única instancia de esta clase es creada por demanda. Dicha instancia es

configurada mediante un archivo de propiedades llamado pss_pmi_compiler.properties. En Java todos

los archivos de propiedades son simples archivos de texto. Dichos archivos representan un mapa con

entradas definidas por una clave y valor. Cada línea que no sea un comentario, comienza con el valor de

la clave, seguida por un carácter ‘=’, finalizado con el valor de dicha clave. En este archivo debe existir

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

72

una entrada con el nombre “registered”, cuyo valor debe contener la lista de “targets” registrados en el

compilador. Por ejemplo: “registered=transient, persistent”. Por cada una de los targets registrados, tiene

que haber otra entrada en dicho archivo que contenga el nombre completo de la clase que implementa la

interfase TargetCompiler, para dicho target.

class compileGenerationLite

AbstractGenerator

AbstractTargetCompilerGenerationTemplates

CompilerTemplates{leaf}

HelperGenerator HolderGenerator

«interface»

TargetCompiler

+ generateConstraintValidations(process, homeBui lder, defini tion, keyIdentifier, stateNames) : void+ generateCreateFour(process, homeBuilder, definition, methodDefini tion) : void+ generateCreateOne(process, homeBui lder, defini tion, methodDefinition) : void+ generateCreateThree(process, homeBuilder, definition, methodDefini tion) : void+ generateCreateTwo(process, homeBui lder, defini tion, methodDefinition) : void+ generateFactoryMethod(process, homeBuilder, factoryMethodBuilder, definition, methodDefini tion) : void+ generateFinderMethod(process, homeBuilder, definition, methodDefinition, keyBui lder) : void+ generateLifeCycleListener(process, storageHome, definition, stateBuilder) : void+ generateRead(process, builder, definition, attribute) : void+ generateReadRef(process, bui lder, defini tion, attribute) : void+ generateReadWri te(process, storageTypeBuilder, bui lder, defini tion, attribute) : void+ generateRefFinderMethod(process, homeBui lder, defini tion, methodDefini tion, keyBuilder) : void+ generateStorageHome(process, storageHome, definition, isAbstract) : void+ generateStorageObject(process, storageType, defini tion, isAbstract, properStates) : void+ generateWrite(process, storageTypeBui lder, bui lder, definition, attribute) : void+ generateWriteRef(process, storageTypeBuilder, bui lder, defini tion, attribute) : void+ getCompi leInstance() : TargetCompi ler+ getDescription() : String+ getName() : String+ getTemplates() : Compi lerTemplates+ ini t(name, registry) : void

TargetCompilerRegistry

+ defaultOne() : TargetCompi ler+ forName(name) : TargetCompiler+ getInstance() : TargetCompilerRegistry+ getProperty(key) : String+ getRegistered() : Set<String>

memory::TransientTargetCompiler

persistent::PersistentTargetCompilerpss_pmi_compiler.properties

-instance

«realize»

-templates

configures«flow»

COMPILADOR 12 - CÓMO GENERARLO

LO QUE ES COMÚN A TODO SERVICIO

El modelo presentado anteriormente para la representación un archivo fuente, permite que las

distintas implementaciones de TargetCompiler, no tengan que tratar directamente con la generación de

los archivos. Dicho modelo es parte de módulo que permite generar código fuente mediante la utilización

de plantillas (templates) que en realidad no es parte del compilador. Este módulo podría ser utilizado por

cualquier aplicación Java que requiera generar archivos de código fuente en forma dinámica.

En el gráfico 12 se muestra la clase AbstractTargetCompiler. Su único objeto es el de tener un

punto común donde poner todas las partes que podrían ser comunes a todo TargetCompiler, como por

ejemplo, todos los métodos para acceder a los atributos que definen el estado en los objetos

almacenados. Todos estos métodos comparten la definición de los mismos, pero varían en su

implementación. En este punto es donde entra en juego la clase CompilerTemplates.

CompilerTemplates, es una especie de registro donde se configura cual archivo de plantilla o

template, se utiliza para determinada operación o método. A simple vista podría pensarse que es

suficiente con tener diferentes plantillas para diferentes implementaciones de servicio, pero no es así. Por

ejemplo, muchas partes de las clases a generar requieren diferentes implementaciones de los métodos en

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

73

función del tipo y cantidad de atributos persistentes que tengan los objetos almacenados. Esto requiere

que los métodos sean construidos dinámicamente en función de lo que se este compilando.

GENERACIÓN DE CÓDIGO PARA EL PRIMER EJEMPLO

Para mostrar como interactúan todas estas partes, voy a continuar con el ejemplo de la fase

anterior, pero esta vez para esta tercera y última fase.

En el código PSDL anterior, se define un objeto almacenado abstracto. Según la especificación

PSDL, en Java esto se traduce a una interfase Prueba, en el paquete ejemploFaseUno y con dos métodos

que permitirán acceder al estado nombre, tanto como para lectura como escritura. Adicionalmente al

tratarse de una interfase, se deben generar los clases Helper y Holder, para la misma.

Esta fase parte de un proceso de compilación (CompileProcess) ya validado, que se encuentra

listo para generar el código resultante.

PASOS

1- Para cada uno de los builders registrados en el CompileProcess de llama al método generate. En este caso sólo existe un builder registrado que representa al objeto almacenado abstracto. Y se trata de un AbstractStorageTypeBuilder.

2- AbstractStorageTypeBuilder construirá una instancia de InterfaceDefinition. a. Para cada tipo heredado, se agregará una extensión a la definición de la interfase construida.

Como en este caso, no se hereda de ninguna otra definición, se agregará automáticamente la extensión de la interfase StorageObject.

b. Para cada estado definido en el tipo, se llamará al método addTo, pasando como parámetro la definición de la interfase construida. Sólo existe una definición de estado “nombre”, que está representada por un objeto StateMemberBuilder. i. StateMemberBuilder en el método addTo, como primer medida, determinará si se trata de

una definición concreta o abstracta (como en este caso) para saber que tipo de métodos se deben generar. Construye una definición de atributo (AttributeDefinition) con el nombre del estado, luego llamará al método addReadAccessorFor con el atributo construido.

ii. El método addReadAccessor construye una instancia de MethodDefinition, en base al tipo de objeto del atributo construido anteriormente. Además le indica al TargetCompiler del proceso que se está ejecutando, que le agregue un encabezado al método construido. Este encabezado será a modo de comentario en sintaxis Java Doc. Si el método debiera ser implementado por tratarse de un tipo concreto, se llamaría al método generateRead del TargetCompiler. De esta forma, TargetCompiler es el único objeto que puede referenciar partes dependientes del servicio de persistencia para el cual se está compilando. Finalmente, StateMemberBuilder agrega el método a la definición.

iii. StateMemberBuilder llama al método addWriteAccessorFor. De manera análoga a addReadAccessor, se construye una definición de método. TargetCompiler le agrega un encabezado y finalmente se agrega el método a la definición que se está construyendo.

c. Para cada operación definida en el tipo almacenado se llama al método addTo del builder (OperationBuilder). En este caso no se han definido operaciones.

d. Finalmente, AbstractStorageTypeBuilder agrega al proceso la definición de la interfase construida, con el método addGenerated, indicándole que se deben generar la clase Holder para la interfase.

module ejemploFaseUno { abstract storagetype Prueba { state string nombre; }; };

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

74

e. El método addGenerated registra la definición construida y le indica al HolderGenerator del compilador que genere la clase Holder registrándola de igual forma en el proceso.

3- El CompileProcess toma cada una de las definiciones registradas y le indica al CompilerDefinitionWriter del compilador que las escriba en el directorio destino mediante el método write. En el caso de este ejemplo, las definiciones registradas son dos, la interfase y la clase Holder para la misma.

4- CompilerDefinitionWriter crea una instancia de FileBuilder con la definición a escribir, llamando al método build.

a. El método build de FileBuilder construye un PrintStream, en base a la definición a construir y el directorio donde se crearán las definiciones. En este caso, apuntará a un archivo llamado Prueba.java, en el sub directorio ejemploFaseUno. El primer paso a escribir será el encabezado para el archivo con información del compilador y la fecha de generación.

b. Luego se escribirá el paquete Java al que corresponda el archivo, mediante la definición de paquete (PackageDefinition) del tipo a escribir.

c. Los imports del archivo son generados dinámicamente mediante el uso del patrón Visitor (Gamma01). Los imports de un archivo en Java permiten utilizar referencias a clases sólo con su nombre sin tener que indicar el paquete. De esta forma si se importa el paquete java.util, se podrá hacer referencia a cualquier clase que esté contenida en él, como por ejemplo Collection. Con el patrón Visitor lo que se hace es recorrer todos los generadores de código (que implementen la interfase Generator) que constituyan la definición a construir, pasando como parámetro un objeto de tipo TypeDefinitionAppender. Cada generador de código que utilice una definición de tipo, la registrará en este objeto. Al final del proceso, este objeto conoce todos los tipos de objetos utilizados en el archivo y puede generar en forma ordenada y sin duplicados todos los imports que requiera la clase. El otro beneficio, es que el builder del archivo puede determinar en base a esto si necesita utilizar el nombre con el paquete de un tipo de objeto o solo el nombre. Esto permite que los archivos generados por el compilador contengan solo los imports requeridos por la clase, y además que sólo utilicen referencias absolutas a clases cuando existan ambigüedades con los nombres simples de las mismas (por ejemplo que se use una clase String que esté en otro paquete que no sea java.lang).

d. Finalmente, sólo resta escribir la definición en el PrintStream. Para ello todo Generator tiene un método writeYouSelfInto, que le permite escribirse a sí mismo dentro del archivo.

CONCLUSIONES DEL COMPILADOR El compilador cumple con el objetivo inicial de generar código Java en base a un archivo PSDL de

entrada. Por la forma en que está construido, permite reutilizar el mismo núcleo para diferentes

implementaciones del servicio de estado persistente, tal como lo ejemplificaré en la siguiente sección.

El grado de reutilización que propone el compilador, obedece principalmente a dos razones: la

primera es lograr un alto grado de reutilización. Esta es una buena práctica cuando se desarrolla software,

dado que permite optimizar el trabajo. En segundo lugar, porque al momento de la construcción del

compilador no tenía definido el servicio de estado persistente y al plantearlo como procesos separados

me permitió construir ambos en paralelo.

Antes de plantearme la posibilidad de construir el compilador, analicé la posibilidad de extender

algún compilador IDL, pero la mayoría de los compiladores (incluido el mío) tienen el mismo problema.

Los parsers son construidos con herramientas como Javacc que generan código Java que es

prácticamente imposible extender o modificar (por su elevado costo de hacerlo manualmente) si no es

por medio de la herramienta, por lo que esta posibilidad quedó descartada.

CONEXIÓN DEL SERVICIO DE PERSISTENCIA CON LA ORB

En CORBA todo servicio perteneciente a la especificación se accede de la misma forma, mediante

una referencia inicial a la ORB. Por ejemplo, uno de los servicios más utilizados, se llama NameService

(servicio de nombres), para acceder a este servicio se llama al método resolve_initial_references del

objeto ORB, pasándole como parámetro el nombre del servicio que se quiere acceder:

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

75

El nombre del servicio es, por lo general, una cadena de caracteres bien definida por la

especificación del servicio, en este caso se trata de NameService, mientras que en el caso del servicio de

persistencia se trata de PSS. A esta cadena de caracteres se la denomina objectId (identificador de

objeto).

La razón por la que los servicios son referenciados o accedidos por su nombre y no por un

método es simple: los servicios que están disponibles en la ORB dependen de la configuración de la

misma y no de la definición de ella. Los servicios de CORBA son contribuciones adicionales a la ORB que

pueden estar o no presentes en ella. Existen identificadores de objetos reservados por CORBA para

referenciar a los servicios bien conocidos (pág. 4-29 de CORBA01 de la especificación) entre los que se

encuentran NameService y PSS, pero mediante este mecanismo se puede acceder a cualquier objeto que

esté registrado como referencia inicial a la ORB.

La pregunta que se plantea aquí es ¿cómo sabe la ORB que objeto le corresponde a cada

nombre?. La respuesta es parte de la especificación de la ORB [CORBA01].

CONFIGURACIÓN DE LA ORB PARA EL ACCESO A SUS REFERENCIAS INICIALES. La ORB provee referencias a estos objetos que serán recolectadas durante el proceso de

inicialización. Los objetos pueden ser tanto referencias a objetos locales como remotos. En el caso del

servicio de persistencia, la referencia que se provee es una instancia de la clase

org.omg.CosPersistentState.ConnectorRegistry, que al implementar la interfase

org.omg.CORBA.LocalInterface, se trata de una referencia local. Las referencias remotas son objetos que

residen en otra ORB que en general está en otra máquina. Básicamente se resuelve pasándole la

representación en forma de cadena de caracteres (string) a la ORB.

Las referencias locales son construidas mediante el uso de los interceptors (interceptores). Un

interceptor es un mecanismo que permite que los servicios de CORBA y otros objetos, puedan intervenir

en el procesamiento de la ORB. Estos interceptores son registrados en la ORB durante el proceso de

inicialización de la misma. La interfase org.omg.PortableInterceptor.ORBInitializer permite definir un

interceptor que participará del proceso de inicialización de la ORB. Existen otros interceptores que

permiten intervenir en otros procesos de la ORB, como la invocación remota a un método.

En la especificación de la ORB para Java (CorbaJava00), este tipo de interceptores se registran

pasándole una propiedad como parámetro a la máquina virtual, con el siguiente formato:

org.omg.PortableInterceptor.ORBInitializerClass.XXX

, donde XXX será el nombre completo de la clase que implementa ORBInitializer. Las propiedades se

pasan a la máquina virtual de Java anteponiendo –D a la propiedad en la línea de comando. También se

admite definir estas propiedades como entradas en el archivo orb.properties.

El archivo orb.properties es la forma más simple de configurar una instalación Java que requiera

CORBA. La máquina virtual Java (JVM) contiene una implementación completa de la ORB. Mediante el

archivo orb.properties se puede configurar, tanto la implementación de ORB a utilizar, como por ejemplo

JacORB, como los servicios que estarán disponibles en la misma.

ORB orb = .....; Object objSrv = orb.resolve_initial_references(“NameService”); NamingContextExt context = NamingContextExtHelper.narrow(objSrv);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

76

void pre_init (org.omg.PortableInterceptor.ORBInitInfo info); void post_init (org.omg.PortableInterceptor.ORBInitInfo info);

class ORBInitialization

pss::ConnectorRegistryImpl

+ ConnectorRegistryImpl()+ find_connector(String) : Connector+ is_registered(String) : boolean+ post_init(ORBInitInfo) : void+ pre_init(ORBInitInfo) : void+ register_connector(Connector) : Connector+ unregister_connector(String) : void

LocalInterface

«interface»

pss::ConnectorRegistryInitializer

+ post_init(ConnectorRegistry, Properties) : void+ pre_init(ConnectorRegistry, Properties, String) : void

Object

«interface»

PortableInterceptor::ORBInitializer

_ConnectorLocalBase

connector::ConnectorBase

pss_pmi_registry.properties

ConnectorRegistryOperationsorg.omg.CORBA.LocalInterface

org.omg.CORBA.portable.IDLEntity

«interface»

CosPersistentState::ConnectorRegistry

org.omg.CORBA.LocalObject

CosPersistentState::_ConnectorRegistryLocalBase

+ _ids() : String[]

orb.properties

«realize»

configures

«flow»

«realize»

configures

«flow»

DISEÑO DEL SERVICIO 1 - CONFIGURACIÓN DE LA ORB

En el gráfico anterior se muestran las partes que interactúan para que el servicio de persistencia

de CORBA esté disponible en la ORB. El proceso que permite al servicio de persistencia estar disponible

en la ORB es el siguiente:

REGISTRO DE CONECTORES PARA EL SERVICIO DE PERSISTENCIA La ORB leerá de la línea de comandos o del archivo orb.properties la propiedad que identifica a

un inicializador:

, en este caso: ar.uba.fi.pmi.corba.pss.ConnectorRegistryImpl es la clase que representa al registro de

conectores del servicio de persistencia y es a su vez la puerta de entrada al servicio.

ConnectorRegistryImpl implementa la interfase ORBInitializer de CORBA. No es necesario que sea el

mismo registro el que implemente dicha interfase, podría ser cualquier objeto que sea capaz de generar

el registro, para luego registrarlo en la ORB, como una referencia inicial. Para simplificar opté por utilizar

el mismo registro como inicializador. ORBInitializer expone dos métodos:

, el primero identifica el comienzo de la etapa de inicialización de la ORB y el segundo su fin. El

parámetro que reciben ambos métodos es una instancia de la clase ORBInitInfo, la cuál expone un

org.omg.PortableInterceptor.ORBInitializerClass.ar.uba.fi.pmi.corba.pss.ConnectorRegistryImpl

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

77

método register_initial_reference(String, org.omg.CORBA.Object), que es el que permite indicarle a la

ORB que objeto representa la referencia inicial.

El registro de conectores permite acceder a los distintos conectores que estarán disponibles en

función de la configuración de la instalación. El proceso de registración de un conector es similar al que

permite a la ORB registrar las referencias iniciales en forma dinámica. Por ello, opté por definir un

mecanismo muy similar para configurar el registro.

El registro es configurado, o bien, por variables pasadas por la línea de comandos o por un

archivo de propiedades, que en este caso se llama: pss_pmi_registry.properties. El registro tomará las

propiedades que comiencen con el prefijo: PSSPMIConnector.:

, en este caso se identifica el conector Transient. Cada vez que se le pida al registro, un conector

identificado por la cadena de caracteres Transient, se hará referencia al conector provisto por la clase

ar.uba.fi.pmi.corba.pss.connector.TransientConnector. La única condición para estas clases es que

implementen la interfase:

ar.uba.fi.pmi.corba.pss.ConnectorRegistryInitializer:

, como se aprecia, al igual que ORBInitializer expone dos métodos que representan la fase de

inicialización, en este caso, del registro de conectores. De forma análoga, el objeto que hace las veces de

inicializador del registro, no tiene por que ser el mismo que representa al conector. Sin embargo, para

simplificar opté por utilizar el mismo. Se espera que en la implementación del método post_init, se

registre un conector, mientras que el método pre_init se utilice para recolectar y configurar toda la

información requerida por el conector a registrar. Existe una implementación parcial de

ConnectorRegistryInitializer que puede ser utilizada como clase base para la implementación de

cualquier conector.

De esta forma mi servicio de persistencia propone un mecanismo de configuración que permite a

cualquier implementación de conector, incorporarse al registro por medio de una configuración.

IMPLEMENTACIÓN DEL SERVICIO PROVISTO POR DIFERENTES CONECTORES

Toda implementación del servicio requiere de dos partes por lo menos. La primera es su

contribución al compilador, que permita generar código fuente para el servicio. La segunda es la parte

interna al servicio que será utilizada en tiempo de ejecución y que se presenta al usuario final como el

conector (org.omg.CosPersistentState.Connector). En esta sección me focalizaré en el conector

propiamente dicho.

EL CONECTOR DEL SERVICIO DE PERSISTENCIA

PSSPMIConnector.ar.uba.fi.pmi.corba.pss.connector.TransientConnector=Transient

public void pre_init(ConnectorRegistry registry, Properties initialzationProperties, String creationProperty); public void post_init(ConnectorRegistry registry, Properties initialzationProperties);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

78

class connector

«interface»

CosPersistentState::ConnectorOperations~ create_basic_session(access_mode, additional_parameters) : org.omg.CosPersistentState.Session~ get_pid(obj) : byte[]~ get_short_pid(obj) : byte[]~ implementation_id() : java.lang.String~ register_session_factory(session_factory) : Class~ register_storage_home_factory(storage_home_type_name, storage_home_factory) : Class~ register_storage_object_factory(storage_type_name, storage_object_factory) : Class

org.omg.CORBA.Local Interfaceorg.omg.CORBA.portable.IDLEnti ty

«interface»

CosPersistentState::Connector

_ConnectorLocalBase

ConnectorBase

+ create_basic_session(access_mode, additional_parameters) : Session+ get_pid(obj ) : byte[]+ get_short_pid(obj ) : byte[]+ getStorageHomeFactory(storage_home_id) : Class+ getStorageObjectFactory(storage_object_id) : Class+ implementation_id() : String+ onUnregistered(registry) : void+ post_init(registry, properties) : void+ pre_init(registry, properties, creationProperty) : void+ register_session_factory(session_factory) : Class+ register_storage_home_factory(storage_home_type_name, storage_home_factory) : Class+ register_storage_object_factory(storage_type_name, storage_object_factory) : Class+ toString() : String

PersistentConnector

+ getConnection(session, connectionParameters) : DataStoreConnection+ PersistentConnector()

TransientConnector

+ T ransientConnector()

Local Interface

«interface»

UnregistrableConnector+ onUnregistered(registry) : void

«realize»

DISEÑO DEL SERVICIO 2 - CONECTORES

El gráfico anterior muestra las principales clases que están relacionadas con el conector. Mi

modelo de registro dinámico de conectores extiende la interfase Connector de CORBA mediante la

interfase: ar.uba.fi.pmi.corba.pss.connector.UnregistrableConnector. La cual permite que los conectores

registrados tengan un callback (llamada) representado por el método onUnregistered, que les permite a

los conectores liberar los recursos que tengan utilizados, cuando la ORB donde se registró el conector,

finalice.

El hecho de que en Java las clases están representadas por objetos de tipo java.lang.Class

simplifica enormemente el desarrollo. En Java solo se necesita de un objeto de tipo Class para poder

construir un objeto de dicha clase, mientras que en C++, la única forma de construir una clase es

mediante una llamada explicita a un constructor de la misma mediante código escrito por el

programador. Por esto, las definiciones IDL nativas que hacen referencias a Factory se traducen a objetos

Class en Java, mientras que en C++, se traducen a una clase concreta para cada factory.

La simplicidad del modelo de Java, me permitió crear una implementación básica de la interfase

Connector, que es la clase ar.uba.fi.pmi.corba.pss.connector.ConnectorBase y que es funcionalmente

completa. Sin embargo, esta clase está declarada como abstracta, dado que para poder funcionar

correctamente se requiere que el conector conozca el tipo de sesiones que debe crear. Aunque no es

obligatorio heredar de ella para implementar un conector, si es conveniente.

En el diagrama se muestran dos implementaciones del conector TransientConnector y

PersistentConnector, las cuales explico en detalle más adelante. Básicamente se trata de las dos

implementaciones que provee mi servicio de persistencia. La primera con soporte de persistencia en

memoria y la segunda en un repositorio persistente ante el reinicio del servidor.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

79

Ambos conectores heredan de la implementación básica del conector, agregándole el registro

automático del tipo de sesión que utiliza cada una. Por registro automático se entiende que el usuario no

necesita registrar la clase que se utilizará para crear sesiones.

OPERACIONES DEL CONECTOR

El conector es la puerta de entrada para el servicio de persistencia. Sus operaciones están

definidas en la interfase ConnectorOperations. En el conector se deberán registrar los tipos o clases que

estarán disponibles para el servicio en tiempo de ejecución.

Estos métodos permiten registrar clases para diferentes propósitos en el conector. El primer

método registra la clase que lo representa para un nombre de objeto almacenado. Para estos nombres se

adopta una nomenclatura similar que los nombres de IDL, o sea, la cadena de caracteres comienza con

PSDL:, es seguida por el nombre completo de la clase y finalmente por la versión de la misma. Por

ejemplo, para registrar la clase ar.fi.uba.pmi.corba.pss.test.Persona se deberá utilizar un nombre:

PSDL:AR.FI.UBA.PMI.CORBA.PSS.TEST.PERSONA:1.0

, en este caso se registrará la versión 1.0 de la clase Persona.

Para simplificar la generación de estos nombres proveo la clase ar.uba.fi.pmi.corba.pss.psdl

.PSDLUtils:

class connector

psdl::PSDLUtils{leaf}

+ getReposi toryID(entityClass) : String+ getReposi toryID(entityClass, majorVersion, minorVersion) : String+ getReposi toryID(packageName, entityName, majorVersion, minorVersion) : String+ getReposi toryID(entityName, majorVersion, minorVersion) : String~ PSDLUtils()+ versionToString(majorVersion, minorVersion) : String

DISEÑO DEL SERVICIO 3 - PSDLUTILS

, que permite generar los nombres en base a la clase que se quiera registrar.

El segundo método register_ del conector es análogo al primero, permitiendo registrar la clase

que representa un almacén para un nombre dado.

Y finalmente, el tercero de ellos permite registrar la clase que representa una sesión en el

conector. En general, se puede suponer que el conector conoce el tipo de sesiones que puede manejar,

sin tener que indicárselo programáticamente. Sin embargo, no es cierto para las clases que representan

almacenes y objetos almacenados. En mi esquema, donde cada tipo de conector tiene su clase que lo

representa, este método no sería necesario. Probablemente sería necesario, si se tiene una única clase

Connector para todo conector. Al tratarse de un detalle de implementación del mismo, no debería ser un

Class register_storage_object_factory(java.lang.String storage_type_name, Class storage_object_factory);

Class register_storage_home_factory(java.lang.String storage_home_type_name, Class storage_home_factory);

Class register_session_factory(Class session_factory);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

80

método público del conector dado que el usuario final del servicio no tiene por que conocer cual es la

clase que representa la sesión.

El conector tiene, además, las siguientes cuatro operaciones:

El primer método (implementation_id) devuelve el identificador con el que se encuentra

registrado el conector en el registro de conectores.

El segundo y tercer método (get_pid, get_short_pid) permiten obtener a partir de un objeto

almacenado, el identificador que lo representa globalmente y localmente respectivamente, dentro del

almacén. Estos dos métodos se utilizan generalmente en el esquema de trabajo de persistencia

transparente, donde los objetos que se persisten no tienen ninguna relación con el servicio de

persistencia.

class Parameters

org.omg.CORBA.portable.IDLEntity

Parameter{leaf}

+ name: java.lang.String = ""+ val : org.omg.CORBA.Any

+ Parameter()+ Parameter(java.lang.String, org.omg.CORBA.Any)

connector::SessionParametersExtractor

+ getFileName() : String+ getHostName() : String+ getParams() : Map<String, Object>+ getPort() : Integer+ getUserName() : String+ getUserPassword() : String+ hasFi leName() : boolean+ hasHostName() : boolean+ hasPort() : boolean+ hasUserName() : boolean+ hasUserPassword() : boolean+ isEmpty() : boolean+ isRemote() : boolean+ SessionParametersExtractor(Parameter[])+ toMessage() : Message+ toString() : String

pss::SessionParameterBuilder

# appendParameter(String, Serializable) : SessionParameterBui lder+ build(ORB) : Parameter[]# createAny(ORB, DynAnyFactory, Map.Entry<String, Serializable>) : DynAny+ withFileName(String) : SessionParameterBui lder+ withHostName(String) : SessionParameterBuilder+ withPort(Integer) : SessionParameterBuilder+ withPropertiesFi le(String) : SessionParameterBui lder+ withUserName(String) : SessionParameterBuilder+ withUserPassword(String) : SessionParameterBuilder

DISEÑO DEL SERVICIO 4 - PARAMETERS

Y por último, el método más importante del conector (create_basic_session) es el que permite

generar una sesión de trabajo en el servicio de persistencia. Este método requiere de dos parámetros, el

primero es el tipo de sesión a crear, que puede ser READ_ONLY o READ_WRITE. La primera sólo permitirá

lecturas sobre los objetos, y la segunda lectura y escritura. El segundo parámetro es un array de objetos

Parameter.

La función de estos parámetros es permitirle al conector acceder a la información necesaria para

generar la sesión. Por ejemplo, si se trata de una sesión que se guarda en disco, un parámetro podría ser

el nombre del directorio y archivo donde se almacena la sesión. Si el conector se conecta a un repositorio

remoto, podría ser el nombre de usuario y password necesarios para conectarse.

java.lang.String implementation_id(); byte[] get_pid(Object obj); byte[] get_short_pid(Object obj); org.omg.CosPersistentState.Session create_basic_session(

short access_mode, org.omg.CosPersistentState.Parameter[] additional_parameters);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

81

La clase Parameter está definida por dos atributos, el primero es el nombre del parámetro, y el

segundo es su valor, que está representado por un objeto de tipo org.omg.CORBA.Any. La clase Any se

define en CORBA como un contenedor de cualquier tipo de dato u objeto. Esta clase provee operaciones

para transportar el objeto que contenga como valor [CORBA01]. Para trabajar con los objetos de tipo Any

se requiere utilizar algún servicio de CORBA, como por ejemplo, el servicio DynAnyFactory [CORBA01]

(que es una referencia inicial a la ORB). Para mantener encapsulada la creación y lectura de estos

parámetros proveo dos clases, la primera ar.uba.fi.pmi.corba.pss.SessionParameterBuilder, que

representa la realización del patrón de diseño builder de Gamma [Gamma01]. Esta clase captura los

parámetros necesarios para la creación de la sesión y finalmente con el método build construye un array

de objetos Parameter que podrá ser pasado como parámetro, al método create_session del conector.

La otra clase es ar.uba.fi.pmi.corba.pss.connector.SessionParametersExtractor, que permite

recuperar los valores de un array de objetos Parameter. Esta clase es usada internamente por el conector

para extraer los parámetros necesarios para la creación de la sesión.

Es el tipo de conector quien define cuales nombres de parámetros aceptará (el atributo name de

la clase Any). Este tipo de pasaje de parámetros no restringe cuales son los parámetros válidos y

requeridos por cada conector. Cada conector deberá validar en tiempo de ejecución, si los parámetros

recibidos son correctos o no, en caso de que no lo sean, lanzará una excepción de tipo

org.omg.CORBA.PERSIST_STORE.

Tanto la clase que construye la lista de parámetros (SessionParameterBuilder), como la que la

procesa (SessionParametersExtractor), restringen los nombres y tipos de parámetros a los dos

conectores que conocen. Otro conector deberá, o bien extender estas dos clases, o proveer otra forma de

generar los parámetros para sí mismo.

PROBLEMAS QUE PRESENTA LA CREACIÓN DE UN CONECTOR Antes de entrar en el detalle de cada uno de los conectores, voy a describir algunos de los

problemas generales que plantea la construcción de un conector.

CREACIÓN DE OBJETOS

El primer objeto que se necesita crear es la sesión de trabajo y el conector es el encargado de

crearla. En mi implementación base del conector, la sesión se crea utilizando la clase registrada para tal

efecto. Se requiere solamente que dicha clase tenga un constructor sin parámetros. La clase registrada

debe heredar de la clase ar.uba.fi.pmi.corba.pss.catalog.SessionBase, también provista por mí. Dicha

clase provee un método initialize, que es llamado inmediatamente luego de la creación del objeto por el

conector.

EL PATRÓN DE DISEÑO TEMPLATE METHOD

Todas las clases base que proveo exponen el comportamiento que deben proveer pero no lo

implementan completamente, es por ello que están definidas como abstractas. El diseño de estas clases

sigue el patrón de diseño Template Method [Gamma01]. Dicho patrón permite definir un esqueleto de

una clase dejando métodos que las subclases deberán implementar para completar el comportamiento.

Estos métodos son los llamados template. En el caso concreto de la clase SessionBase, el método

initialize está declarado como final. Internamente initialize llama a otro método protegido de la clase

denominado do_initialize (el template method). Las subclases deben sobrescribirlo, si lo requieren, para

poder realizar alguna inicialización adicional tal como una conexión a la base de datos. El principal

beneficio del uso de este patrón es que permite un alto grado de reutilización de código. En mi caso, me

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

82

permitió definir varias clases base que pueden ser utilizadas en distintas implementaciones de conectores

del servicio de persistencia.

En el diagrama que se muestra a continuación se observan todos los template methods que

provee la clase SessionBase. Son todos aquellos que comienzan con el nombre do_xxx.

class ConnectorBases

_SessionLocalBase

C:extends ConnectorBase

catalog::SessionBase

# log: Log = LogFactory.getL... {readOnly}

+ access_mode() : short# assertExist(Object, byte[]) : void+ canWrite() : boolean# check_can_write(String) : void# check_not_closed(String) : void# checkInitial ized() : void+ close() : void# do_close() : void# do_find_by_pid(byte[]) : Object# do_find_storage_home(String) : StorageHomeBase# do_flush() : void# do_free_all() : void# do_initial ize(C, Parameter[]) : SessionBase# do_refresh() : void+ find_by_pid(byte[]) : Object+ find_storage_home(String) : StorageHomeBase+ flush() : void+ free_al l() : void# getAccessMode() : AccessMode+ getConnector() : C+ initial ize(AccessMode, C, Parameter[]) : SessionBase+ isClosed() : boolean+ refresh() : void+ SessionBase()

_ConnectorLocalBase

connector::ConnectorBase

+ create_basic_session(short, Parameter[]) : Session+ get_pid(Object) : byte[]+ get_short_pid(Object) : byte[]+ getStorageHomeFactory(String) : Class+ getStorageObjectFactory(String) : Class+ implementation_id() : String+ onUnregistered(ConnectorRegistry) : void+ post_ini t(ConnectorRegistry, Properties) : void+ pre_ini t(ConnectorRegistry, Properties, String) : void+ register_session_factory(Class) : Class+ register_storage_home_factory(String, Class) : Class+ register_storage_object_factory(String, Class) : Class+ toString() : String

C:extends ConnectorBaseH:extends AbstractStorageHomeBase

catalog::CommonSessionBase

# homes: Map<String, H> = CollectionFacto...

# after_ini tial ize(H) : H# afterCreateSO(SO) : void# createStorageObjectIdenti fier(H, byte[], byte[]) : StorageObjectIdentifier# do_find_storage_home(String) : StorageHomeBase# getHomes() : Map<String, H># next_pid() : byte[]+ register(SO, StorageObjectSpec, byte[], H) : SO

DISEÑO DEL SERVICIO 5 - CONECTOR Y SESIONES

ALMACENES Y OBJETOS ALMACENADOS

Una vez creada la sesión se puede comenzar a trabajar con ella. Para poder trabajar con los

objetos, es necesario poder crearlos. Dentro del servicio de persistencia, los tipos de objetos que definen

los usuarios están divididos en dos categorías, los almacenes y los objetos almacenados. La creación de

estos dos tipos de objetos siempre está ligada a la sesión de trabajo. Una instancia de objeto pertenece

siempre a la sesión en la que fue creada.

En el modelo PSDL, los almacenes son siempre los encargados de la creación de los objetos

almacenados. Entonces el siguiente problema a resolver, es: ¿cómo obtener una referencia a una

instancia de un almacén dentro de una sesión?. La interfase org.omg.CosPersistentState.CatalogBase de

PSDL provee el método:

, que en base a un identificador de almacén (o nombre) permite obtener una referencia al almacén que lo

representa. Dos o más llamadas consecutivas con los mismos parámetros al método find_storage_home

deben devolver la misma instancia de almacén, dado que los almacenes son singletons [Gamma01]

dentro de una sesión. Esto requiere que la sesión además de crear los almacenes, lleve un registro de

aquellos almacenes que fueron creados. Esta funcionalidad está provista de forma simple por una clase

llamada ar.uba.fi.pmi.corba.pss.catalog.CommonSessionBase, que contiene un mapa interno de los

almacenes creados en ella. Este comportamiento no está implementado en SessionBase, dado que es un

StorageHomeBase find_storage_home(String storage_home_id) throws NotFound;

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

83

detalle de implementación de la sesión y es muy probable que algunos conectores requieran de

mecanismos más complejos para resolverlo.

Como el conector conoce la clase que le corresponde a un nombre, la clase SessionBase provee

una referencia al conector donde fue creada, permitiéndole a CommonSessionBase acceder a las clases

de almacenes para poder construirlos.

ALMACENES

En este punto cabe plantearse, ¿qué implica crear un almacén? Siendo que los almacenes son

objetos que están ligados lógicamente al repositorio donde fueron creados originalmente, ya que

almacenan objetos que pertenecen a un único repositorio. Los almacenes creados en el contexto de una

sesión, son en realidad vistas de los almacenes existentes en el repositorio. Si dos sesiones están

conectadas con el mismo repositorio deberán obtener cada una, una instancia distinta del mismo

almacén. Entonces los almacenes creados son vistas que están conectadas en tiempo de ejecución a la

sesión donde fueron creados y mediante ella al repositorio que conecta la sesión.

Existe otra característica de los almacenes que hacen complicada su creación. Los almacenes

tiene una estructura jerárquica, o sea, dentro de una familia de almacenes, el primer almacén (la raíz) en

la jerarquía es capaz de manejar cualquier objeto almacenado dentro de la misma jerarquía.

class storagehome hierarchy

S:extends SessionBaseDH:extends ExtendedStorageHome

storageHome::AbstractChildStorageHome

# AbstractChildStorageHome()# afterDestroy(StorageObject) : void# beforeDestroy(StorageObject) : void+ destroy(StorageObject) : void# destroyReference(StorageObject, String, byte[]) : void+ exist(StorageObject) : boolean+ find_by_short_pid(byte[]) : Object+ find_by_short_pid(ExtendedStorageHome, byte[]) : Object# find_ref_by_spec(StorageObjectSpec) : byte[]# getDelegatee() : DH+ home_fami ly_id() : String# initial izeChildHome(S, String, String, String) : AbstractChi ldStorageHome# initial izeMe(S, String, DH, String) : AbstractChi ldStorageHome# initial izeRootHome(S, String, String) : AbstractChildStorageHome# newSpec() : StorageObjectSpec

«interface»

storageHome::ExtendedStorageHome

+ belongs(Class<T>) : boolean+ destroy(StorageObject) : void+ exist(StorageObject) : boolean+ find_by_short_pid(ExtendedStorageHome, byte[]) : Object+ getStorageType() : Class<I>+ home_family_id() : String+ ini tial ize(Session, String) : StorageHomeBase+ registered_storage_home_id() : String

S:extends SessionBase

storageHome::AbstractRootStorageHome

_StorageHomeBaseLocalBase

S:extends SessionBase

storageHome::AbstractStorageHomeBase

StorageHomeBaseOperationsorg.omg.CORBA.Local Interface

org.omg.CORBA.portable.IDLEnti ty

«interface»

CosPersistentState::StorageHomeBase

«realize»

DISEÑO DEL SERVICIO 6 - MODELO BASE DE ALMACENES

Lo anteriormente dicho y el hecho de que los almacenes son clases totalmente definidas y

construidas por el compilador PSDL, me llevó a definir el siguiente modelo base para todo almacén que

pueda existir dentro de mi servicio de persistencia.

org.omg.CosPersistentState.StorageHomeBase es la interfase definida por CORBA como

almacén, la cual he extendido agregando operaciones adicionales mediante la interfase

ar.uba.fi.pmi.corba.pss.storageHome.ExtendedStorageHome. Estas operaciones me permitieron definir

el comportamiento requerido para cualquier almacén que exista dentro de mi servicio de persistencia. Se

trata de una interfase y no de una clase, ya que cualquier conector es libre de implementarla como

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

84

corresponda. De todas formas, yo he implementado algunas clases base de esta interfase para maximizar

la reutilización de código entre mis dos implementaciones de conector. La clase

AbstractStorageHomeBase, es una implementación base de dicha interfase. Además, proveo otras dos

clases, que resuelven el problema de la jerarquía de almacenes y que son:

ar.uba.fi.pmi.corba.pss.storageHome.AbstractChildStorageHome

ar.uba.fi.pmi.corba.pss.storageHome.AbstractRootStorageHome

MODELO DE DELEGACIÓN

Estas dos clases resuelven el problema de la herencia de almacenes mediante la delegación. La

jerarquía de los almacenes está parcialmente definida por el compilador. Si es el usuario quien define

algún almacén con operaciones, es él mismo quien es responsable de crear una clase que herede de la

clase provista por el compilador, quedando así fuera del control del compilador. Por otro lado, dentro de

una jerarquía pueden existir

múltiples nodos hijos, tal como

muestra el gráfico a la izquierda.

En mi modelo, todos los

nodos de la jerarquía están

conectados mediante el atributo

delegatee, de la clase

AbstractChildStorageHome.

Cualquier almacén definido por el

usuario hereda de esta clase en

algún punto. El método

getDelegatee de la misma clase

devuelve el almacén que estará

encargado de resolver las

operaciones que requieran del

conocimiento del almacén raíz.

Cual es objeto delegatee, será

definido en tiempo de ejecución

cuando se cree el almacén. El único requisito para dicho objeto es que implemente la interfase extendida

de almacén propuesta por mi servicio: ExtendedStorageHome.

Este modelo de delegación simplifica el proceso de compilación, ya que gran parte de las

operaciones que deben implementar los almacenes son delegadas y el compilador sólo debe preocuparse

de generar las llamadas al almacén donde se delega. La diferencia entre un almacén que sea raíz y otro

que no lo es, está dada por como es inicializado cuando es construido y por el almacén donde se delega.

En el caso que el almacén sea raíz de la jerarquía, el almacén donde se delega es una instancia de

AbstractRootStorageHome. Por consiguiente, lo único que debe considerar el compilador a la hora de

generar el mecanismo de inicialización de cada almacén, es el método de la superclase que debe llamar

para inicializar dicho almacén.

Este modelo contribuye a la simplicidad del compilador, ya que las clases que deben conocer los

conectores, están limitadas a las provistas por ellos mismos y no a las clases definidas por los usuarios

mediante el lenguaje PSDL. La delegación es una técnica de POO, distinta de la herencia, que permite

también un alto grado de reutilización [BM0].

OBJETOS ALMACENADOS

class Home Incomplete Hierarchy

UserDefinedClasses

CompilerDefinedClasses

PersonStorageHome

ClientStorageHome

ACMEPersonStorageHome

VendorStorageHome

XXXVendorStorageHome YYYClientStorageHome

DISEÑO DEL SERVICIO 7 - DELEGACIÓN

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

85

Los almacenes permiten acceder a los objetos almacenados, ya sea creándolos o recuperando

aquellos que fueron creados anteriormente. Dentro del servicio, los almacenes son los únicos objetos que

permiten crear objetos almacenados que puedan ser persistidos en un repositorio.

El mecanismo de delegación es necesario también para la creación de objetos, dado que el

compilador no sabe cuales clases serán las registradas por el usuario del servicio, como la que represente

al objeto almacenado para un determinado almacén. Se evita que el compilador genere código para

construir objetos, generando solamente los métodos que recolectarán los atributos necesarios para la

construcción del objeto almacenado.

Cuando se construye un objeto almacenado también existe el problema de su identidad. CORBA

provee dos formas de identificar un objeto almacenado. La primera es un identificador global dentro del

repositorio (Pid) y la segunda es un identificador dentro de la familia de almacenes a la que pertenece

(ShortPid). Los objetos almacenados pueden ser creados como entidades primarias, pero también existen

aquellos objetos almacenados que son parte de otros objetos almacenados que los contienen (Embedded

Storage Objects) como atributos (estado) de los mismos. En este último caso, según la especificación de

CORBA, estos objetos almacenados no tendrán identidad propia, es decir que no tienen Pid ni ShortPid.

Dichos objetos se denominan Embedded o encastrados. Estos objetos embedded serán objetos creados

manualmente por el usuario y no por medio de un almacén.

class Extended Storage Obj ect

«interface»

CosPersistentState::StorageObject

+ destroy_object() : void+ get_pid() : byte[]+ get_short_pid() : byte[]+ get_storage_home() : StorageHomeBase+ object_exists() : boolean

«interface»

storageObject::ExtendedStorageObject

+ get_identi fier() : StorageObjectIdentifier+ ini tia lize(StorageObjectIdenti fier, StorageObjectSpec) : void+ is_ini tia lized() : boolean

Serializable

«interface»

storageObject::StorageObjectIdentifier

+ attach(CatalogBase) : void+ embed(String, ExtendedStorageObject) : void+ equals(StorageObject, StorageObject) : boolean+ equals(byte[], byte[]) : boolean+ get_pid() : byte[]+ get_short_pid() : byte[]+ get_storage_home() : StorageHomeBase+ is_destroyed() : boolean+ is_embedded() : boolean+ refToStorageObject(byte[]) : StorageObject+ set_destroyed() : void+ storageObjectToRef(StorageObject) : byte[]

«interface»

storageObject::StorageObjectSpec

+ addState(String, O) : void+ count() : int+ forEach(EntryProcessor) : void+ get_boolean(String) : boolean+ get_byte(String) : byte+ get_float(String) : float+ get_int(String) : int+ get_long(String) : long+ get_reference(String) : byte[]+ get_short(String) : short+ get_storageObject(String) : S+ get_string(String) : String+ getSpeci ficationType() : Class<? extends StorageObject>+ getState(String) : O+ hasValue(String) : boolean+ isAssignable(StorageObject) : boolean+ set_boolean(String, boolean) : void+ set_byte(String, byte) : void+ set_float(String, float) : void+ set_int(String, int) : void+ set_long(String, long) : void+ set_reference(String, byte[]) : void+ set_short(String, short) : void+ set_storageObject(String, S) : void+ set_string(String, String) : void

storageObj ect::BaseStorageObj ectIdentifier

storageObj ect::EmbeddedStorageObj ectIdentifier

storageObj ect::StorageObj ectSpecImpl

«real ize»«realize»

-parent

«real ize»

DISEÑO DEL SERVICIO 8 - OBJETOS ALAMACENADOS

Entonces los objetos almacenados presentan dos problemas, el primero es la creación mediante

almacenes o como objetos embedded y el segundo es su identidad. Para tratar con estos problemas

propongo el esquema expuesto en el diagrama anterior.

En el centro del diagrama se muestra la interfase StorageObject provista por CORBA, la cual

debe ser implementada por todo objeto almacenado abstracto definido en PSDL. Yo he extendido dicha

interfase mediante: ar.uba.fi.pmi.corba.pss.storageObject.ExtendedStorageObject. Cualquier objeto

almacenado, ya sea embbeded o no, debe implementar StorageObject y en el caso de los embedded

prácticamente todos los métodos de la interfase carecen de sentido. Mi interfase extendida agrega tres

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

86

boolean is_initialized(); void initialize(StorageObjectIdentifier identifier, StorageObjectSpec specification); StorageObjectIdentifier get_identifier();

operaciones que permiten trabajar con objetos almacenados que puedan ser utilizados tanto como

embedded o no. La única restricción que agrega mi servicio de persistencia para poder persistir objetos

almacenados, es que implementen esta interfase extendida, quedando a criterio de cada conector del

servicio, como hacerlo. Las operaciones extendidas que agrega mi interfase son:

Estas operaciones presuponen que la creación de un objeto en el contexto del servicio de

persistencia tiene dos etapas. La primera es la construcción física del objeto y la segunda una fase de

inicialización del mismo. Cualquier objeto almacenado de mi servicio de persistencia, así sea embedded o

no, debe pasar por estas dos etapas. La diferencia radica en cómo y quién es el que inicializa el objeto.

El segundo método (initialize) tiene dos parámetros. El primero parámetro es un identificador de

objeto y el segundo, una especificación del objeto a inicializar. A continuación voy explicar estos dos

conceptos fundamentales de mi servicio.

STORAGEOBJECTSPEC

Esta interfase representa una especificación de un objeto almacenado y se utiliza tanto para la

creación como para la búsqueda de dichos objetos. La búsqueda será explicada más adelante. Es una

interfase de uso interno al servicio y los usuarios nunca deben lidiar con ella. Esta interfase provee la

información necesaria para construir o buscar un objeto almacenado, lo cuál incluye tanto al tipo de

objeto (la clase) como los valores de los atributos que lo definen (estados). La clase

AbstractStorageHomeBase provee un método newSpec, que permite crear un objeto que implementa

esta interfase. Este objeto puede ser

utilizado por los métodos internos

generados por el compilador. El

segmento de código a la izquierda

representa un factory method

[Gamma01] llamado createNamed de un

almacén generado por el compilador. Este

objeto almacenado tiene dos atributos

nameTwo y one que son pasados como parámetro al método. La primer línea crea una nueva

especificación que en las siguientes líneas es cargada con los parámetros de entrada del método.

Finalmente la creación del objeto es delegada en el método create que recibe tan solo la especificación

de que debe crear. Esta forma de comunicación simplifica y generaliza la lógica del compilador, ya que

toda la complejidad de la creación del objeto está resuelta en la superclase y no en la clase generada por

el compilador.

STORAGEOBJECTIDENTIFIER

La idea de un identificador de objetos es un concepto que agrega el servicio de persistencia de

CORBA. En el modelo de objetos de Java, los objetos no tienen identificadores. La identidad de los

objetos sólo está garantizada a nivel de instancia. Para el operador == de Java, un objeto es igual a otro, si

y solo si, se trata de la misma instancia. Aunque Java también provee un método equals (iguales en

inglés), que permite comparar dos objetos por algún criterio distinto de la instancia misma. De todas

formas para la clase Object de Java, el método equals, solo devuelve verdadero cuando se trata de la

misma instancia. Si se quiere utilizar otro criterio de comparación, las clases deben redefinir dicho

método.

public Two createNamed(String nameTwo, One one) { StorageObjectSpec spec = this.newSpec(); spec.set_string("nameTwo", nameTwo); spec.set_storageObject("one", one); return (Two)this.create(spec); }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

87

La interfase StorageObjectIdentifier está encargada de tratar con la identidad de los objetos,

tanto para los embedded como para los comunes, dentro del contexto de una sesión. Se trata igualmente

de una interfase interna al servicio y que los usuarios nunca utilizarán directamente.

Es recomendable que los compiladores para cada conector, generen un método equals en los

objetos raíz de la familia que delegue en su identificador la comparación con otro objeto.

En mi interfase extendida ExtendedStorageObject, se puede apreciar que el primer parámetro

del método initialize es una instancia de dicha interfase. Cada conector decide como implementar la

interfase. Por lo general, este objeto pasará a ser un atributo del objeto almacenado luego de la llamada

al método initialize, mientras que la especificación será descartada, ya que sólo se utiliza para inicializar

los valores de los atributos que son los estado del objeto almacenado que se está inicializado.

Algunas de las operaciones que expone esta interfase son:

Los primeros cinco métodos son importados de la interfase StorageObject. Esto permite que el

compilador delegue la implementación de la interfase StorageObject en el atributo que represente al

identificador del objeto almacenado. Los siguientes dos métodos, permiten tratar a un objeto

almacenado como embedded. El primero indica si el objeto identificado por este objeto es un objeto

almacenado embedded. El segundo le permite a un objeto almacenado inicializar a otro objeto

almacenado que representa un atributo embedded en él. Luego de llamar a este método, el objeto

storageObject, quedará marcado como embedded dentro del objeto identificado.

Los últimos tres métodos son útiles para tratar con copias/instancias de los objetos que fueron

persistidos en el repositorio. Los dos primeros métodos comparan objetos almacenados por sus

identificadores y el último permite conectar una instancia de objeto almacenada con el catalogo o sesión

donde existe o será utilizado. Esta última operación es necesaria, ya que cuando una instancia es

recuperada de un repositorio persitente es cuando se puede determinar a que catálogo pertenece. Esta

información solo es útil para una instancia de objeto activa dentro de un catálogo, por lo que no debe ser

persistida en el repositorio.

OPERACIONES DE BÚSQUEDA DE OBJETOS

CatalogBase:: StorageHomeBase find_storage_home(in string storage_home_id) raises (NotFound); StorageObjectBase find_by_pid(in Pid the_pid) raises (NotFound); StorageHomeBase:: StorageObjectBase find_by_short_pid(in ShortPid short_pid) raises (NotFound);

public byte[] get_pid(); public byte[] get_short_pid(); public StorageHomeBase get_storage_home(); public void set_destroyed(); public boolean is_destroyed(); public boolean is_embedded(); public void embed(String attribute_name, ExtendedStorageObject storageObject); public boolean equals(StorageObject source, StorageObject target); public boolean equals(byte[] sourceReference, byte[] targetReference); public void attach(CatalogBase catalog);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

88

El otro problema que tienen que resolver los conectores y también el servicio en sí mismo, es la

búsqueda de objetos. La búsqueda implica encontrar los objetos almacenados y los almacenes que ya

fueron creados previamente en el servicio. La especificación de CORBA define algunas operaciones de

búsqueda estándar, tales como las expuestas en el cuadro anterior. Estas operaciones son relativamente

simples de resolver. La búsqueda de almacenes se limita a buscar en un índice de almacenes creados

dentro de una sesión y si no existe, crearlo acorde al registro de almacenes. Esta funcionalidad está

implementada en la clase CommonSessionBase vista anteriormente. Las dos operaciones siguientes

dependen del conector.

Existe un tipo de operaciones de búsquedas más complejas. Son aquellas que se definen

automáticamente al definir claves dentro de un almacén. Cada definición de clave define implícitamente

dos operaciones de búsqueda por los atributos que conforman la clave. La primera busca el objeto

almacenado y la segunda una referencia al objeto que esté identificado por la clave.

Estas operaciones requieren que los conectores provean una forma eficiente de realizar

búsquedas genéricas por atributos de los objetos almacenados dentro de un almacén particular. Deben

ser genéricas, ya que los conectores no conocen las claves que pueden definir los almacenes en tiempo

de diseño. Más adelante explicaré como cada conector en particular, resuelve estas operaciones.

Estas operaciones de búsqueda son necesarias en el momento de creación de los objetos

almacenados para poder garantizar la unicidad de las claves.

VALIDACIÓN DE CLAVES

La especificación de CORBA define el concepto de clave, su significado y el código que se genera

en cada lenguaje. No especifica el comportamiento esperado. Por ejemplo, las claves definen la unicidad

de los objetos dentro de un almacén o familia de ellos, pero no se especifica que sucede cuando no se

cumple la restricción que impone alguna de las claves, ni tampoco, cuando deben verificarse estas

restricciones.

Dado que la especificación es particularmente amplia en este sentido, he optado por definir un

mecanismo de validación de las claves que puede ser disparado en diferentes momentos. Los conectores

pueden optar por redefinir o extender este comportamiento según sus necesidades, definiendo cuando

será utilizado. El modelo base obliga a que se verifiquen las claves cada vez que se construya un objeto,

generalmente, serán necesarias verificaciones adicionales.

Las cuatro clases representadas en el diagrama siguiente, modelan el comportamiento necesario

para la validación de las claves definidas por una familia de almacenes. StorageHomeKeySet es una clase

abstracta cuyo objeto es el de recolectar el conjunto de claves, cada una representada por la interfase

StorageHomeKey, definidas por una familia de almacenes. Para ello cuentan con el soporte de la clase

StorageHomeKeyBuilder (patrón de diseño Builder [Gamma01]), que permite construir las claves definida

en los almacenes.

La clase AbstractStorageHomeBase define el método:

, que se deberá ser sobrescrito en cada uno de los almacenes que defina al menos una clave.

protected void collectKeys(StorageHomeKeySet keySet)

S find_by_key_name(<parameter_list>) raises (CosPersistentState::NotFound); ref<S> find_ref_by_key_name(<parameter_list>);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

89

protected void collectKeys(StorageHomeKeySet keys) { super.collectKeys(keys); ///////// Key: name StorageHomeKeyBuilder keyname = keys.keyBuilder("name"); keyname.with("name"); keyname.build(); }

class storageHome

StorageObjectSpec

«interface»

StorageHomeKey

+ getName() : String

StorageHomeKeyBuilder

+ bui ld() : void+ with(state) : StorageHomeKeyBuilder

StorageObjectSpecImpl

StorageHomeKeyImpl

+ getName() : String+ StorageHomeKeyImpl(spec, name)+ StorageHomeKeyImpl(type, name)+ toString() : String

H:extends ExtendedStorageHome

StorageHomeKeySet

+ getOriginalSpec() : StorageObjectSpec+ keyBuilder(name) : StorageHomeKeyBuilder+ StorageHomeKeySet(spec)+ validate(operation, home) : void

«real ize»-set

DISEÑO DEL SERVICIO 9 - RESTRICCIONES DE CLAVES

El código que se muestra a

la izquierda representa

como un almacén agrega

una clave compuesta

únicamente por el atributo

‘name’. La implementación

del método collectKeys, es

generada por el compilador y el usuario nunca debe hacer uso de él directamente, por eso se trata de un

método declarado como protegido.

La clase StorageHomeKeySet no implementa el método valídate. Cada conector será

responsable de extender dicha clase proveyendo el mecanismo de validación para cada clave definida

dentro de la familia de almacenes.

En el caso concreto de la creación de un objeto, el método mencionado anteriormente, será

llamado por el mecanismo de creación de objetos almacenados dentro de cada almacén.

DESTRUCCIÓN DE OBJETOS ALMACENADOS

Otro punto donde la especificación es particularmente amplia, es que sucede en el momento de

destruir o eliminar objetos con los objetos que referencian al objeto que se está eliminando.

La especificación solamente indica que debe hacerse con las referencias de un objeto que es

eliminado. Las referencias a objetos de tipo embedded son directamente destruidas junto con su padre y

las referencias por identificadores son destruidas cuando están declaradas como strong o fuertes.

Siguiendo el modelo de delegación, he optado por centralizar las operaciones de destrucción de

los objetos en los almacenes, dado que es el almacén, en definitiva, quien tiene más información sobre el

estado de los objetos y es realmente la única clase que debe ser generada completamente por el

compilador. Por ello, el método destroy_object de la interfase StorageObject, es implementado por el

compilador mediante delegación en el almacén al que pertenece el objeto. La clase base

AbstractChildStorageHome define los siguientes métodos para la destrucción de los objetos

almacenados:

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

90

El método destroy está definido en mi interfase extendida de almacén. Se trata de un método

declarado final en esta clase, usando el patrón de diseño Template Method [Gamma01]. Los métodos

beforeDestroy y afterDesctroy son, en este caso, los template methods. Los almacenes que hereden de

esta clase, deberán redefinirlos para marcar como destruidas a aquellas referencias que estén declaradas

como fuertes en los objetos almacenados que manejen.

En el momento de la destrucción de los objetos, el identificador es marcado como destruido por

el almacén mediante la interfase StorageObjectIdentifier. Los identificadores de las referencias a objetos

de tipo embedded tienen su estado definido por medio del estado del identificador del objeto al que

pertenecen, por lo que estas referencias quedan automáticamente destruidas.

DIFERENTES CONECTORES Ya mencioné anteriormente que he construido dos conectores del servicio de persistencia. La

razón principal para ello es la forma que he desarrollado el servicio. El primer conector que provee

persistencia temporal, es la implementación más simple que cumple con todos los requerimientos del

servicio. Este conector se usó para construir el servicio y el compilador a la par.

CONECTOR DE PERSISTENCIA TEMPORAL

Este conector permite evaluar y comprender las funcionalidades de cualquier servicio de

persistencia. La limitación que tiene es que su estado es válido mientras la sesión esté activa. Sin

embargo, es funcional y puede ser usado siempre y cuando los requerimientos lo permitan.

class General Schema

ConnectorBase

connector::TransientConnector

+ TransientConnector()

CommonSessionBase

catalog::TransientSession

+ dettach(StorageObject) : void+ T ransientSession()

AbstractRootStorageHome

memory::TransientRootStorageHome

{leaf}

AbstractChildStorageHome

memory::TransientStorageHomeBase

+ do_after_create(SO, StorageObjectSpec, T ransientStoragetHome) : SO+ find_ref_by_spec(StorageObjectSpec, T ransientStoragetHome) : byte[]+ T ransientStorageHomeBase()

ExtendedStorageHome

«interface»

memory::TransientStoragetHome

+ do_after_create(SO, StorageObjectSpec, TransientStoragetHome) : SO+ find_ref_by_spec(StorageObjectSpec, TransientStoragetHome) : byte[]

StorageHomeKeySet

memory::TransientStorageHomeKeySet

+ val idate(String, T ransientStorageHomeBase) : void

ExtendedStorageObject

«interface»

memory::TransientStorageObject

+ matches(StorageObjectSpec, int) : boolean

AbstractTargetCompiler

memory::TransientTargetCompiler

«realize»«realize»

DISEÑO DEL SERVICIO 10 - ESQUEMA GENERAL DEL CONECTOR TEMPORAL

El diagrama anterior muestra el esquema general de las clases que componen al conector. Todo

conector debe proveer una contribución al compilador para que este pueda generar código para él. La

public final void destroy(StorageObject storageObject) protected void beforeDestroy(StorageObject storageObject) protected void afterDestroy(StorageObject storageObject)

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

91

contribución consiste en la clase TransientTargetCompiler, junto con las plantillas de código que utilizará

el compilador para generar el código fuente.

El compilador requiere, además, que la contribución sea registrada en su archivo de configuración

(pss_pmi_compiler.properties):

El conector debe tener su registro en el servicio de persitencia, con una clase que implemente la

interfase ConnectorRegistryInitializer. La interfase está implementada por el conector mismo, y es:

ar.uba.fi.pmi.corba.pss.connector.TransientConnector

El conector se registra en el archivo pss_pmi_registry.properties, mediante la siguiente línea:

Habiendo agregado esta última línea, el conector ya está listo para ser utilizado en tiempo de

ejecución.

FUNCIONAMIENTO

La funcionalidad del conector está provista mayormente por la sesión de trabajo que provee. La

clase que representa una sesión en el conector temporal es TransientSession. Esta clase hereda de la

implementación base de la sesión mencionada anteriormente: CommonSessionBase, que resuelve la

administración de almacenes de una manera simple, por lo que lo único que le queda resolver es la

construcción y manejo de objetos almacenados.

MANEJO DE IDENTIFICADORES

El primer problema que se necesita resolver, para la creación de objetos almacenados, es la

asignación de identificadores de objeto, tanto para el Pid como para el ShortPid. La especificación indica

que para Java los identificadores se traducen en un byte[], lo cual hace complicada su manipulación

directa. Para la generación de identificadores que están representados por un byte[], proveo la interfase

ByteArraySequencer, que permite obtener secuencias de identificadores en dicho formato.

class utils

BinaryUtils

+ bytesToLong(bytes) : long+ longToBytes(num) : byte[]

«interface»

ByteArraySequencer

~ next() : byte[]

SimpleByteArraySequencer

+ next() : byte[]+ SimpleByteArraySequencer()+ SimpleByteArraySequencer(initialValue)

«realize»

«use»

DISEÑO DEL SERVICIO 11 - GENERACIÓN DE IDENTIFICADORES

La implementación más simple de este secuenciador es SimpleByteArraySequencer, que se

limita a incrementar un número de tipo long, y traducirlo a su representación binaria mediante la clase

PSSPMIConnector.ar.uba.fi.pmi.corba.pss.connector.TransientConnector=Transient

registered=transient transient=ar.uba.fi.pmi.corba.pss.psdl.compiler.memory.TransientTargetCompiler transient.templateProperties=psdl_transient_templates.properties

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

92

BinaryUtils. La sesión del conector tiene una instancia de esta clase para generar los identificadores

globales (Pid) de los objetos almacenados y a su vez cada familia de almacenes que se use en la sesión,

tendrá otra instancia de este tipo de secuenciador.

ALMACENES TEMPORALES

El conector temporal extiende la interfase de almacén de CORBA mediante

TransientStoragetHome. Esto implica que todo almacén generado por este conector deberá implementar

dicha interfase. Existen dos clases base la implementan que son: TransientRootStorageHome y

TransientStorageHomeBase. La primera representa la raíz de una familia de almacenes y la segunda la

clase base para todo almacén generado por el compilador de este conector. El modelo de almacenes

temporales sigue el modelo de delegación propuesto inicialmente por mi servicio.

Cada almacén raíz de una familia de almacenes tiene un atributo SimpleByteArraySequencer,

que es utilizado para generar los identificadores objeto (ShortPid).

Para mantener los objetos almacenados dentro de la sesión cada almacén raíz tiene un mapa

donde almacena los objetos que fueron creados durante la sesión. Dicho mapa está indexado por el

identificador ShortPid.

OPERACIONES DE BÚSQUEDA

La clase AbstractChildStorageHome define el método:

, que permite buscar una referencia a un objeto almacenado que cumpla con la especificación dada. Los

conectores deben implementar dicha operación. En el caso de este conector, la implementación más

simple es iterar sobre todos los objetos almacenados dentro de la familia del almacén, representados por

los valores del mapa de la raiz y verificando si alguno de ellos cumple con la especificación dada. Para ello,

la interfase TransientStorageObject, define el método matches, que permite determinar si el objeto

almacenado cumple con la especificación dada.

Cualquier operación de búsqueda en este conector se traduce en construir una especificación

con los atributos que se estén buscando, para luego llamar al método find_ref_by_spec del almacén

donde se realizará el proceso anteriormente descrito.

La clase TransientStorageHomeKeySet es la encargada de realizar las validaciones de claves. Esta

clase utiliza el mismo mecanismo de búsqueda para buscar referencias a objetos que cumplan con la

definición de la clave.

CONECTOR DE PERSISTENCIA DURABLE

Si bien el conector temporal es funcional, muchas veces carece de utilidad práctica. En general,

lo que se busca con el concepto de persistencia es que sea prolongada en el tiempo e independiente de la

vida del proceso donde se creó al objeto. Por esto, mi servicio de persistencia provee otro conector que

cumple con este objetivo de persistencia durable.

Persistencia durable implica que la información debe ser almacenada en un repositorio

permanente y no volátil.

REQUERIMIENTOS PARA UN CONECTOR DE PERSISTENCIA DURABLE

El primer requerimiento es que permita almacenar objetos. Las operaciones deberán ser

perdurables, si se crea un objeto en una sesión, al cerrarla y abrir una nueva, se espera que el objeto siga

protected abstract byte[] find_ref_by_spec(StorageObjectSpec spec);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

93

existiendo en la nueva sesión. Todas las operaciones de consulta definidas por el servicio de persistencia

deben también estar soportadas. Y las claves definidas en el modelo deberán ser igualmente respetadas.

El repositorio deberá permitir más de una conexión o sesión concurrente.

Es importante recalcar que el servicio de persistencia de CORBA no tiene el objeto de reemplazar

una base de datos, por lo general los repositorios no contendrán una gran cantidad de objetos.

El acceso remoto al repositorio es un adicional, ya que el servicio de persistencia no lo impone

como requerimiento, aunque si constituye una cualidad muy útil.

Antes de entrar en los detalles de mi conector persistente, voy a realizar un análisis de las

distintas alternativas que consideré para lograr persistencia durable.

DISTINTAS ALTERNATIVAS PARA OBTENER PERSISTENCIA DURABLE

PERSISTENCIA MEDIANTE ARCHIVOS

La alternativa más primitiva es uno o más archivos almacenados en el sistema de archivos del

sistema operativo. Esta forma de lograr persistencia tiene como ventaja que no requiere nada más que el

conector para funcionar, dado que Java provee los mecanismos necesarios para utilizar archivos. Las

principales desventajas son: que requiere la construcción de mecanismos de indexación para acceder de

manera eficiente a el o los archivos, se debe hacer una traducción entre los objetos almacenados y el

formato de registro que se utilice en el archivo, se debe construir un mecanismo que permita el acceso

concurrente del archivo si es que se permite abrir más de una sesión por vez, y el acceso remoto a los

archivos requiere de algún soporte externo al servicio tal como el protocolo NFS (Network File System o

Sistema de Archivos en Red).

Java provee una forma estándar para almacenar objetos en archivos, denominada Serialization.

Este mecanismo permite almacenar en un archivo uno o más objetos junto con todas sus referencias.

Podría ser una alternativa válida, siempre y cuando se pueda garantizar que exista memoria suficiente

para mantener el respositorio de objetos, y no se requiera acceso concurrente al repositorio (en este

caso, el archivo serializado). Esta forma de almacenamiento podría pensarse como una extensión del

conector de memoria, donde la sesión de trabajo se almacena en un archivo, al momento de cerrase la

sesión.

Este modelo no requiere de mucho trabajo extra, ya que el único requerimiento que Java

impone para que un objeto puede ser serializado, es que el mismo implemente la interfase:

java.io.Serializable. Dicha interfase sigue el patrón de diseño Marker Interface [GrandM01] o interfase

marcadora. En este patrón, la interfase marcadora, que no declara métodos ni atributos, determina que

la clase que la implementa, cumple con una condición lógica (implementarla representa verdadero). En el

caso de Serialization, una clase que implemente Serializable, indica que el mecanismo de Serialization

debe considerar los objetos de dicha clase durante el proceso de serialización.

En conclusión, si sólo se necesita la funcionalidad del conector de memoria con persistencia

durable, Serialization es una buena alternativa.

PERSISTENCIA MEDIANTE UNA BASE DE DATOS RELACIONAL

Una forma de hacer un repositorio accesible en forma remota y concurrente, es mediante una

base de datos. Las bases de datos más difundidas son las relacionales.

PSS está basado en SQL3, que es una extensión al modelo relacional soportado por el SQL

tradicional. El problema que surge al utilizar una base de datos relacional, es que lo que se quiere

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

94

almacenar en este caso, no son datos relacionales, sino un modelo de objetos. Se requiere una

transformación bidireccional entre los objetos y el modelo relacional de tablas y registros.

Las bases de datos relacionales utilizan el lenguaje de consulta SQL. Las consultas deben

realizarse sobre un modelo que las soporte. Dicho modelo deberá ser generado por el compilador y más

específicamente, por la contribución del conector persistente relacional al compilador. Si bien el lenguaje

de consultas es generalmente estándar, para todas las bases de datos, el lenguaje de definición del

modelo (DDL o Data Definition Language) no lo es. Esto implica que no sólo se deberá construir un

conector para bases de datos relacionales, sino que el conector deberá considerar cual es la base de

datos que se quiere utilizar.

Dado que el compilador será quien defina el modelo relacional a utilizar, se requiere entonces

que dicho modelo sea creado en la base de datos propiamente dicha. Hasta que no esté creado el

modelo, no se podrá utilizar el servicio de persistencia con el conector. Se agrega entonces un paso

posterior a la compilación, para poder utilizar el servicio de persistencia.

Existen herramientas o bibliotecas que simplifican el uso de bases de datos relacionales para la

persistencia de objetos. Tal es el caso de: Hibernate [KingG01], Castor[Castor01], Toplink[Toplink01],

JDO [RoodsR01], entre otras. Son denominadas genéricamente ORM (Object-relation Mapping) o mapeo

de objeto-relación.

En estos modelos, por lo general, una instancia de una clase particular representa un registro en

una o más tablas, dependiendo de la jerarquía de la clase. Cada atributo persistente de la clase está

representado por una o más columnas en una tabla. Todas ofrecen un mecanismo de consulta de alto

nivel, que termina generando consultas SQL con las que se interactúa con la base de datos. De esta

forma, el usuario no está obligado a tratar en forma directa con SQL. Algunas de estas herramientas

resuelven las diferencias de sintaxis particular de DDL para cada base de datos mediante mecanismos de

extensión por el usuario y otras sólo funcionan con algunas bases de datos fijas. La relación entre el

modelo de objetos y el modelo relacional, el mapeo de objetos, está definida, por lo general, en una

configuración en forma de archivo XML, aunque algunas de ellas soportan este mapeo a través de

anotaciones en las clases Java [Annotations01].

Algunas herramientas como Hibernate o Toplink, soportan persistencia transparente, de forma

tal que no imponen restricciones o requerimientos sobre las clases que se pueden persistir. A este tipo de

persistencia se lo llama genéricamente POJO (Plain Old Java Objects o “Viejos y simples objetos Java”)

dado que cualquier objeto común Java puede ser persistido. JDO, en cambio, requiere de un pos-

compilador, que modifica las clases generadas por el compilador para que puedan ser persistidas.

O bien mediante el uso de alguna herramienta como las mencionadas, o mediante la

construcción de otra herramienta, un conector para una base de datos relacional es perfectamente

viable.

El principal problema del uso de una base de datos relacional, es tener que coexistir con dos

modelos distintos, pero que representan lo mismo [NewardT01]. Es necesario crear otro modelo

(relacional) análogo al modelo de objetos. Si se cambia el modelo de objetos, se debe cambiar el modelo

relacional, cada vez que se guarda un objeto se debe traducir al modelo relacional y cuando se lee del

modelo relacional se debe volver a transformar en objetos.

Otro punto a considerar es que el modelo en la base de datos relacional es visible al usuario, esto

expone los detalles de la implementación del conector. Lo que también podría fomentar que el usuario

interactué con el modelo relacional directamente, creando así una dependencia con la implementación

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

95

del conector. Esta dependecia de la implementación viola las políticas de CORBA y de la especifcación del

servicio de estado persistence.

PERSISTENCIA MEDIANTE UNA BASE DE DATOS ORIENTADA A OBJETOS

Si lo que se quiere guardar son objetos: ¿por qué no guardar objetos directamente? ¿Por qué es

necesario transformar los objetos a otro modelo? ¿Por qué no simplemente utilizar una base de datos

orientada a objetos?

Cuando se comparan las bases de datos orientadas a objetos con las relacionales, comúnmente

se realiza la siguiente analogía:

“Si la base de datos fuera un garaje, y los datos fueran un auto, en una base de datos orientada a

objetos simplemente se estaciona el auto, mientras que en una base de datos relacional cada vez que se

quiere estacionar el auto, se requiere desarmarlo es sus piezas elementales”. [CKA01]

En el contexto del servicio de persistencia, son mínimos los requerimientos que se tiene sobre

una base de datos orientada a objetos. Básicamente, que permita almacenar objetos y para luego poder

buscarlos. Los requerimientos particulares del modelo de objetos que propone el servicio, tales como los

almacenes y objetos almacenados, junto con sus identificadores en forma de array de bytes, son

agregados al modelo de objetos de Java y no tienen porque estar soportados por una base de datos

orientada a objetos.

En conclusión se requiere: una base de datos que soporte una cantidad de objetos no tan

grande, consultas no tan avanzadas, junto con acceso remoto y que sea de uso libre. Esta base de datos

debe soportar el modelo de objetos Java, correr en diversos entornos (tal como lo hace Java),

preferiblemente estar desarrollada en Java, para no estar ligado a una plataforma específica.

No son muchas las bases de datos orientadas a objetos que cumplen con estos requerimientos.

La única que cumple con todos ellos es: DB4O [DB4O01]. Existe otra base que cumple parcialmente con

estos requerimientos llamada Caché [Cache01], pero no posee una licencia de código abierto y solamente

está disponible para algunas plataformas como Windows y Linux.

Considerando las dos alternativas de base de datos, he optado por utilizar DB4O.

DB4O

DB4O o Data Base For Objects, sus siglas en inglés, que significan Base de datos para objetos. Se

trata de una base de datos orientada a objetos nativa, dado que no está implementada sobre una base de

datos relacional. Está desarrollada con licencia de código abierto de tipo GPL (GNU General Public

License).

Existen dos versiones, una para Java, y otra para .NET. Ambas permiten ser incrustadas en el

sistema que las use. Esto hace que no imponga ningún requerimiento adicional a mi servicio de

persistencia, más allá de la propia biblioteca que será incrustada en el servicio. Soporta acceso remoto,

transacciones y consultas avanzadas.

ACCESO A LA BASE DE DATOS

El acceso a la base de datos se realiza mediante la clase com.db4o.Db4o. Esta clase hace las

veces de fábrica o factory [Gamma01], de conexiones a la base de datos. Todas las operaciones son

realizadas mediante métodos estáticos en dicha clase.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

96

class db4o

Db4o

+ configure() : Configuration+ licensedTo(String) : void+ main(String[]) : void+ openClient(String, int, String, String) : ObjectContainer+ openFile(String) : ObjectContainer+ openServer(String, int) : ObjectServer+ version() : String

«interface»

ObjectServer

+ close() : boolean+ ext() : ExtObjectServer+ grantAccess(String, String) : void+ openClient() : ObjectContainer

«interface»

ObjectContainer

+ activate(Object, int) : void+ close() : boolean+ commit() : void+ deactivate(Object, int) : void+ delete(Object) : void+ ext() : ExtObjectContainer+ get(Object) : ObjectSet+ query() : Query+ query(Class) : ObjectSet+ query(Predicate) : ObjectSet+ query(Predicate, QueryComparator) : ObjectSet+ rollback() : void+ set(Object) : void

opens

opens

starts

DISEÑO DEL SERVICIO 12 - MODELO DE DB4O

El método openFile abre una conexión directa a un archivo que representa la base de datos. Esta

forma de conexión no admite interacción remota con la base datos. Para permitir que clientes remotos

accedan a la base de datos, se debe iniciar un proceso servidor, representado por la interfase:

com.db4o.ObjectServer.

El método openServer de la clase Db4o crea una instancia de servidor, que escuchará en el

puerto indicado, permitiendo conexiones a la base de datos que represente el archivo indicado, tanto

locales como remotas. Para obtener acceso local a la base de datos, la interfase ObjectServer, provee el

método openClient.

La única diferencia entre el acceso local y remoto, es cómo se establece la conexión. Una vez

establecida la conexión, no existe diferencia alguna para el usuario. La conexión está representada por la

interfase: com.db4o.ObjectContainer. Esta interfase provee métodos para almacenar, buscar y eliminar

objetos.

GUARDANDO OBJETOS

Para indicarle a la base de datos que un objeto debe ser almacenado, ObjectContainer provee el

método set, que recibe como parámetro el objeto a almacenar. Cada vez que se llame a este método con

un nuevo objeto, o sea que no fue obtenido de la base de datos mediante una consulta, se estará

almacenando un nuevo objeto.

Durante la vida de la conexión, la identidad del objeto está garantizada únicamente por la

instancia del objeto. Si se llama a set, con el mismo objeto dos veces, la segunda vez, se le indicará a la

base de datos que actualice el estado del objeto previamente almacenado.

RECUPERANDO OBJETOS

Se proveen diferentes mecanismos para recuperar objetos de la base de datos en función de la

complejidad de la consulta.

La forma más simple, se la denomina “Consulta por Ejemplo” o QBE (Query By Example). La idea

es crear un objeto “prototipo”, con los atributos de aquellos objetos que se quieren recuperar, llamando

al método get de ObjectContainer. Dicha operación devolverá una instancia de la interfase ResultSet, que

es un conjunto de resultados. Para todo objeto dentro del conjunto resultado, se cumple que los

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

97

atributos del objeto prototipo son iguales a los del objeto del conjunto. La condición evalúa aquellos

atributos del objeto prototipo que tienen un valor distinto del valor por defecto del tipo de atributo. Se

entiende por valor por defecto, al valor que asigna la máquina virtual de Java al inicializar un atributo de

dicho tipo. Por ejemplo, para un atributo de tipo int, el valor por defecto es 0, mientras que para un

atributo de tipo java.lang.Integer, el valor es null. Este tipo de consultas sólo permiten concatenar

condiciones por positivo, donde todos los atributos del objeto prototipo deben coincidir con los

resultados.

Para realizar consultas mas avanzadas, se provee otro mecanismo llamado Consultas Nativas o

NQ (Native Query). Se las denomina nativas, porque permite realizar consultas a la base de datos

mediante condiciones escritas en el lenguaje de programación, en este caso Java [CookWR01]. El

principal beneficio de este tipo de consultas, es que están escritas en el mismo lenguaje. Lo que implica

que son compiladas, haciendo que los cambios en el modelo produzcan un error de compilación cuando

la consulta no los refleja. A su vez, evitan introducir un nuevo lenguaje para expresar la consulta, tal como

en el caso de realizarse consultas SQL sobre una base de datos relacional, para obtener resultados que

luego serán traducidos a objetos. Las consultas nativas están representadas por la clase

com.db4o.query.Predicate (predicado).

Para realizar una consulta, tan sólo se requiere sobrescribir el método match de la clase

Predicate, que recibe como parámetro un objeto, el cual será comparado mediante las condiciones que

determine el predicado. En el ejemplo siguiente, se muestra un predicado en el que la condición evalúa

que las personas tengan un nombre igual a ‘Pablo’ o ‘PABLO’.

Esta forma de realizar consultas es muy atractiva en aquellos casos en los que la consulta esté

completamente definida, pero ¿qué pasa en aquellos casos en los que la consulta se define en tiempo de

ejecución, en función de diferentes parámetros?, donde se deben realizar consultas dinámicas. En estos

casos, se provee una interfaz más avanzada para la construcción de consultas dinámicas, basada en otro

proyecto de código abierto llamado S.O.D.A (Simple Object Database Access o acceso simple de objetos

en base de datos) [SODA01].

SODA trata de proveer una interfase orientada a objetos, que permite construir consultas

dinámicas. Tratando así de minimizar el uso de cadenas de caracteres para realizar consultas, y maximizar

las ventajas que el paradigma de objetos provee, como reutilización y encapsulamiento.

Las consultas están representadas por la interfase com.db4o.query.Query y son creadas por el

ObjectContainer. El método descend de Query, permite navegar los atributos de los objetos, de forma tal

de estableces restricciones (Constraint) sobre ellos. La interfase Constraint representa una restricción

que se aplica sobre un atributo de los objetos. El método constraint de Query recibe el valor del atributo

y alguna de las operaciones de Constraint determina el tipo de restricción que se aplica sobre el par

atributo-valor. Por ejemplo, el método greater (mayor) representa que el atributo navegado deberá ser

mayor que el valor utilizado como parámetro en la llamada a constraint (la cual creó originalmente el

objeto Constraint).

Predicate personasConNombrePablo = new Predicate<Persona>() { public boolean match(Persona persona) { return persona.getName().equals("Pablo") || persona.getName().equals("PABLO"); } };

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

98

class db4o query

«interface»

Query

+ constrain(constraint :Object) : Constraint+ constraints() : Constraints+ descend(fieldName :String) : Query+ execute() : ObjectSet+ orderAscending() : Query+ orderDescending() : Query+ sortBy(comparator :QueryComparator) : Query

«interface»

Constraints

+ toArray() : Constraint[]

«interface»

Constraint

+ and(with :Constraint) : Constraint+ contains() : Constraint+ endsWith(caseSensitive :boolean) : Constraint+ equal() : Constraint+ getObject() : Object+ greater() : Constraint+ identity() : Constraint+ like() : Constraint+ not() : Constraint+ or(with :Constraint) : Constraint+ smaller() : Constraint+ startsWith(caseSensitive :boolean) : Constraint

Serializable

«interface»

QueryComparator

+ compare(fi rst :Object, second :Object) : int

createssorts by

DISEÑO DEL SERVICIO 13 - MODELO DE CONSULTAS S.O.D.A

La interfase Constraint también permite construir condiciones más complejas de búsqueda, del

tipo (condicion1 o condicion2) y condicion3, mediante el uso de los métodos or y and.

El ejemplo de la consulta anterior, donde se buscaba personas con nombre Pablo o PABLO, se

escribiría:

ELIMINANDO OBJETOS

Cualquier objeto que esté almacenado en la base de datos puede ser eliminado mediante el

método delete de la interfase ObjectContainer. Eliminar un objeto no implica que sus referencias serán

eliminadas. Cuando un objeto tiene como atributos otros objetos (que no sean de tipo nativo o

java.lang.String), el manejo del ciclo de vida de las referencias debe ser indicado por el usuario. Esto

obedece a motivos de performance y de semántica de las referencias (¿cuándo un objeto se considera

parte de otro y cuándo es sólo una referencia?).

MANEJO DE REFERENCIAS

Las operaciones de borrado como las de actualización, no se propagan en forma automática a los

objetos referenciados (salvo por los objetos que sean de tipos nativos del lenguaje). Aunque en el caso de

la inserción la propagación se hace en forma automática. Si se tiene un objeto que tiene como atributo a

otro objeto, cuando se modifica un atributo del objeto referenciado y se llama al método set para el

objeto padre, la base de datos no registrará los cambios en el objeto hijo en forma automática, lo mismo

ocurre con el método delete. Para que así se haga, se le debe indicar explícitamente en la configuración

de la base, cuales operaciones deben propagarse para cada clase en particular.

Query cPersonas = objectContainer.query(); cPersonas.constrain(Persona.class); cPersonas.descend("nombre").constrain("Pablo").or( cPersonas.descend("nombre").constrain("Pablo") ); ObjectSet personasConNombrePablo = cPersonas.execute();

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

99

La configuración de la base se controla por medio del interfase: com.db4o.comfig.Configuration,

que se obtiene llamando al método configure, de Db4o.

class db4o Configuration

«interface»

ObjectClass

+ cal lConstructor(boolean) : void+ cascadeOnActivate(boolean) : void+ cascadeOnDelete(boolean) : void+ cascadeOnUpdate(boolean) : void+ compare(ObjectAttribute) : void+ enableRepl ication(boolean) : void+ generateUUIDs(boolean) : void+ generateVersionNumbers(boolean) : void+ maximumActivationDepth(int) : void+ minimumActivationDepth(int) : void+ objectField(String) : ObjectField+ persistStaticFieldValues() : void+ readAs(Object) : void+ rename(String) : void+ storeTransientFields(boolean) : void+ translate(ObjectTranslator) : void+ updateDepth(int) : void

«interface»

ObjectField

+ cascadeOnActivate(boolean) : void+ cascadeOnDelete(boolean) : void+ cascadeOnUpdate(boolean) : void+ indexed(boolean) : void+ queryEvaluation(boolean) : void+ rename(String) : void

«interface»

Configuration

+ activationDepth(int) : void+ objectClass(Object) : ObjectClass+ setOut(PrintStream) : void+ singleThreadedClient(boolean) : void+ testConstructors(boolean) : void+ timeoutCl ientSocket(int) : void+ timeoutPingCl ients(int) : void+ timeoutServerSocket(int) : void+ unicode(boolean) : void+ updateDepth(int) : void+ weakReferenceCol lectionInterval(int) : void+ weakReferences(boolean) : void

access access

DISEÑO DEL SERVICIO 14 - CONFIGURACIÓN DE DB4O

La configuración se puede realizar a nivel global de la base de datos desde la interfase

Configuration, a nivel de clase desde la interfase ObjectClass, y a nivel de atributo por medio de la

interfase ObjectField. Los métodos cascadeOnXXX, indican que la operación XXX será propagada a las

referencias. El método updateDepth de ObjectClass permite indicar hasta que nivel las referencias serán

actualizadas cuando se actualice un objeto. Como mencioné anteriormente, por defecto se actualizan las

referencias nativas, lo que representa el nivel 1.

CONECTOR PERSISTENTE CON SOPORTE DE DB4O

Para el conector con soporte persistente, el repositorio está representado por una base de datos

creada con DB4O.

REGISTRO DEL CONECTOR

La primer parte del conector está dada por su contribución al compilador. Para ello, agrega a la

configuración del servicio, las líneas que indican cual es la clase que implementa el TargetCompiler del

conector, que en este caso es PersistentTargetCompiler, junto con el archivo de configuración de las

plantillas de código.

El servicio de persistencia necesita además, de una configuración para soportar este conector.

Por esto, este conector agrega al archivo de configuración del servicio, la línea que indica la clase que será

responsable de procesar el registro del conector, que en este caso es: PersistentConnector.

FUNCIONAMIENTO

Las sesiones que crea el conector estarán representadas por la clase PersistenSession. Una

sesión se conecta a un DataStore por medio de la interfase DataStoreConnection. Existen dos clases

registered=persistent persistent=ar.uba.fi.pmi.corba.pss.psdl.compiler.persistent.PersistentTargetCompiler persistent.templateProperties=psdl_persistent_templates.properties

PSSPMIConnector.ar.uba.fi.pmi.corba.pss.connector.PersistentConnector=Persistent

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

100

concretas que implementan dicha interfase: LocalDataStoreConnection y RemoteDataStoreConnection.

La primera y más simple, es una conexión a un repositorio local representado por un archivo. La segunda

forma de conexión permite acceder a un repositorio remoto existente en otra máquina.

class Persistent Connection

AbstractDataStoreConnection

+ AbstractDataStoreConnection(factory)+ close() : void+ createQuery() : Query+ doAttomicOperation(operation) : void+ flush(object, introspectionLevel) : void+ getContainer() : ObjectContainer

DataStore

+ connectionClosed(connection) : void+ create() : ObjectContainer+ DataStore(fi leName)+ DataStore(fi leName, user, password, port)+ openConnection() : DataStoreConnection+ shoutDown() : void

«interface»

DataStoreConnection

+ close() : void+ createQuery() : Query+ doAttomicOperation(operation) : void+ flush(object, introspectionLevel) : void+ getContainer() : ObjectContainer

LocalDataStoreConnection

+ close() : void+ LocalDataStoreConnection(server)

«interface»

ObjectContainerFactory

+ create() : ObjectContainer

CommonSessionBase

PersistentSession

+ createQuery(operation) : Query+ destroy(storageObject) : void+ doAttomicOperation(purpose, operation) : void+ exist(storageObject) : boolean

RemoteDataStoreConnection

+ RemoteDataStoreConnection(host, port, userName, password)

«interface»

AttomicDataStoreOperation

+ doOperation(container) : void

-connection

-server

«real ize»«real ize»

-factory

DISEÑO DEL SERVICIO 15 - CONEXIÓN PARA ACCESO A LA BASE DE DATOS

Cuando se crea una sesión, ésta recibe como parámetro un objeto que implementa

DataStoreConnection, que será provisto por el conector. El conector se vale de las clases

SessionParameterBuilder y SessionParametersExtractor, para recibir e identificar los parámetros

(representados por la clase Parameter), que definen el tipo de conexión que debe crear.

La interfase DataStoreConnection permite que la sesión sea completamente independiente del

tipo de conexión que se utilice, dado que todas las operaciones contra el DataStore se realizan a través

de dicha interfase.

RELACIÓN CON DB4O

La conexión que tiene asociada una sesión, permite acceder al ObjectContainer de DB4O, que

será utilizado internamente para almacenar y recuperar los objetos. El hecho de que el conector utilice

DB4O para mantener el estado está oculto a los ojos del usuario final del servicio. Esto se debe a que un

usuario final siempre interactuará con las interfases provistas por el servicio de CORBA, sin tener que

lidiar nunca con mis implementaciones de ellas, ni con sus detalles.

OBJETOS CON ESTADO COMPARTIDO

Para acceder a un almacén registrado en el conector, será necesario considerar algunos

problemas que no se presentaban en el conector de memoria. Existe una diferencia fundamental entre el

conector de memoria y el persistente, si bien todo objeto tiene estado (definido por sus atributos), en el

conector persistente, los objetos pueden ser compartidos por múltiples sesiones. Esta característica hace

que se deba tener particular cuidado en como se accede a estos objetos en forma concurrente.

El repositorio en sí mismo, tiene atributos que definen un estado que está compartido por todas

las sesiones que lo acceden. El estado está definido fundamentalmente por tres atributos: cuales

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

101

almacenes fueron creados en el repositorio, cuales objetos almacenados fueron creados en los

almacenes del repositorio y una secuencia. La secuencia permite obtener un identificador global dentro

del repositorio, que es asignado a los objetos almacenados al momento de su creación.

IDENTIFICADORES

El conector persistente se basa en el mecanismo general propuesto por el servicio para identificar

los objetos, mediante la clase: PersistentStorageObjectIdentifier, que implementa la interfase

StorageObjectIdentifier.

Como mencioné anteriormente, el modelo de objetos de Java no provee el concepto de

identificador, por esto DB4O no recomienda el uso de identificadores y no provee mecanismos para su

generación automática. Las bases de datos relacionales proveen mecanismos eficientes para la

generación de secuencias de números, como pueden ser tipos de datos autoincrementados. Estos

mecanismos soportan acceso concurrente y transaccional a las secuencias de forma tal que dos o más

operaciones concurrentes obtengan identificadores distintos.

El modelo general del servicio de persistencia provee una clase que permite generar secuencias

de bytes, ByteArraySequencer. El conector persistente debe garantizar el acceso concurrente y

transaccional a dicha clase para proveer identificadores válidos.

La forma más simple de garantizar el acceso concurrente, es serializar el acceso a la operación

next del objeto secuencia. Esto se logra comúnmente mediante el uso de un semáforo [Conc01]. Un

semáforo es una construcción que garantiza el acceso único a un recurso o segmento de código, en este

caso a la secuencia. Cuando uno o más usuarios quieren acceder en forma simultánea a un recurso, el

semáforo sólo permitirá el paso de uno de ellos, bloqueando a los siguientes, hasta que el usuario actual

del recurso lo libere. En Java, los semáforos están encapsulados dentro del lenguaje, cada objeto tiene un

atributo (no visible al usuario) que representa un semáforo. Este semáforo se utiliza mediante la palabra

clave synchronized, que indica que sólo un Thread podrá ejecutar el código dentro del bloque

sincronizado.

La clase ByteArraySequencer garantiza el acceso concurrente a ella sincronizando el acceso al

método next. Pero sólo serán sincronizados los Threads que se ejecuten dentro de la misma máquina

virtual y sobre la misma instancia de objeto.

Para crear un objeto, se requieren dos identificadores provistos por dos secuencias distintas, el

primero es la secuencia global de identificadores del repositorio y el segundo la secuencia para la familia

de objetos almacenados a la que pertenezca el objeto. Esto último implica que las secuencias serán

compartidas por distintas conexiones a un mismo repositorio y dichas conexiones pueden ser incluso

realizadas desde otras máquinas. Por esto el mecanismo original de sincronización de las secuencia es

insuficiente en este escenario.

OPERACIONES ATÓMICAS

Para garantizar la atomicidad de este tipo de operaciones (generación de identificadores y otras),

proveo una interfase llamada AtomicDataStoreOperation. Esta interfase representa una operación que

podrá ser realizada en forma atómica en un DataStore. Una operación atómica es aquella que sólo tiene

dos posibles resultados. Exitoso en cuyo caso el contexto queda modificado y fallido, en donde el

contexto queda en el mismo estado que antes de realizar la operación. El objetivo de esta interfase es

permitir operaciones que no alteren el contexto actual, sino que hagan uso de información obtenida

como resultado de la operación.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

102

class content

I:extends IdentifiedContent

AtomicDataStoreOverContentOperation

+ AtomicDataStoreOverContentOperation(initial)# commitBeforeUnlocking() : boolean+ doOperation(container) : void# getInitialContent() : I# getWaiteTimeOut() : int# operate(container, restored, blocked) : boolean

ByteArraySequencerContent

+ ByteArraySequencerContent()+ ByteArraySequencerContent(id)+ ByteArraySequencerContent(id, content)+ next() : byte[]

ByteArraySequencerOperation

+ ByteArraySequencerOperation(id)+ getValue() : byte[]# operate(container, restored, blocked) : boolean

ContentLocator

+ locate(container, template) : I

C:extends Object

IdentifiedContent

# assertInitialized() : void# assertNotInitialzed() : void+ getContent() : C+ getId() : String+ IdentifiedContent()+ IdentifiedContent(id, content)+ isInitialized() : boolean+ setContent(content) : void+ setId(id) : void+ toString() : String

«interface»

persistent::AtomicDataStoreOperation

+ doOperation(container) : void

«real ize»

#locator

DISEÑO DEL SERVICIO 16 - GENERACIÓN ATOMICA DE IDENTIFICADORES

El diagrama que se muestra arriba, esquematiza las clases involucradas en la generación de un

nuevo identificador a partir de una secuencia. La interfase DataStoreConexion provee el método

doAtomicOperation, que recibe como parámetro una operación a realizar. Toda operación se realiza en

un nuevo contenedor de objetos (no sobre el contenedor que utiliza la sesión), que es provisto por un

objeto de tipo ObjectContainerFactory.

Existe una clase concreta llamada ByteArraySequencerOperation, que hereda de la clase base

AtomicDataStoreOverContentOperation, que provee la funcionalidad necesaria para que pueda

realizarse la operación en forma atómica. La idea general, es realizar una operación sobre un contenido

inicial que puede o no estar almacenado en el repositorio. La clase ContentLocator es la encargada de

ubicar el contenido en el repositorio, en base a un objeto prototipo. Este prototipo se utiliza para realizar

un tipo de consulta por ejemplo. El contenido será siempre un objeto que implemente la interfase:

IdentifiedContent, que básicamente permite que un objeto sea identificado por una secuencia de

caracteres.

DB4O provee semáforos con soporte nativo de la base de datos que garantizan concurrencia

entre los distintos clientes. Los semáforos se indetifican por una secuencia de caracteres. Para acceder a

los semaforos se utiliza la intefase ExtObjectContainer de DB4O.

Al inicio de la operación, la clase AtomicDataStoreOverContentOperation iniciará un semáforo

mediante el método setSemaphore con el identificador del contenido a utilizar. De esta forma cualquier

otra operación que utilice el mismo contenido (secuencia), deberá esperar que la operación en curso

libere el semáforo mediante la operación releaseSemaphore. Una vez obtenido el semáforo, se tratará de

public boolean setSemaphore(String name, int waitMilliSeconds); public void releaseSemaphore(String name);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

103

ubicar el contenido con el ContentLocator, si el contenido no existe, se creará para ser almacenado en el

contendor. Sobre el contenido se realiza la operación necesaria, mediante el método operate de la

operación. Se almacena el resultado como atributo del objeto, luego se finaliza la operación liberando el

semáforo y se cierra la conexión.

Esta forma de ejecución garantiza que una única operación pueda ser realizada sobre el mismo

repositorio y sobre el mismo contenido en forma concurrente. Las operaciones son genéricas y pueden

ser aplicadas a cualquier proceso que requiera acceso exclusivo sobre un contenido del repositorio.

Esta forma de garantizar acceso concurrente, se denomina genericamente bloqueo pesimista

[RoySeif01] dado que se realiza el bloqueo del semáforo cada vez que se quiere acceder al recurso

compartido. En un esquema optimista se tiene la premisa que la mayoría de las operaciones no

interferirán entre ellas, por lo que no es necesario realizar un bloqueo inicialmente. De todas formas, se

tiene que garantizar que en el caso de que las operaciones interfieran, los resultados serán correctos. En

este caso no se deberían tener dos identificadores iguales en distintas operaciones. En el esquema del

identificador global, es altamente probable que las operaciones interfieran entre sí, dado que existe un

único identificador global por repositorio. Y por otro lado, se entiende que el servicio de estado

persistence no tendrá una cantidad masiva de conexiones remotas concurrentes, por lo que no es tan

crítico que el tiempo consumido por la operación de creación sea mínimo. Además el bloqueo optimista

es más complicado de implementar, es por esto y lo anteriormente dicho que consideré al bloqueo

pesimista como la mejor opción para este caso.

ALMACENES

Este conector también sigue el esquema de delegación propuesto inicialmente por el servicio.

Todo almacén de objetos que utilice este conector debe implementar la interfase:

PersistentStorageHome. Esta interfase permite que el conector implemente el mismo mecanismo de

delegación que el conector de memoria, extendiendo a la interfase base del servicio:

ExtendedStorageHome.

class General Schema

ExtendedStorageHome

«interface»

persistent::PersistentStorageHome

+ create(PersistentStorageHome, PersistentStorageHomeKeySet) : SO+ find_by_spec(PersistentStorageHome, StorageObjectSpec) : SO

AbstractRootStorageHome

persistent::PersistentRootStorageHome

{leaf}

AbstractChildStorageHome

persistent::PersistentStorageHomeBase

+ create(PersistentStorageHome, PersistentStorageHomeKeySet) : SO+ find_by_spec(PersistentStorageHome, StorageObjectSpec) : SO+ PersistentStorageHomeBase()

StorageHomeKeySet

persistent::PersistentStorageHomeKeySet

+ validate(String, PersistentStorageHome) : void

AbstractTargetCompiler

persistent::PersistentTargetCompiler

BaseStorageObjectIdentifier

persitent::PersistentStorageObjectIdentifier

+ createConstraint(Query) : Constraint+ getLong_pid() : Long+ getLong_short_pid() : Long+ PersistentStorageObjectIdentifier(PersistentStorageHome)+ PersistentStorageObjectIdentifier(byte[], byte[], PersistentStorageHome)+ PersistentStorageObjectIdentifier(byte[], byte[], String, String)CommonSessionBase

persistent::PersistentSession

+ createQuery(String) : Query+ destroy(StorageObject) : void+ doAtomicOperation(String, AtomicDataStoreOperation) : void+ exist(StorageObject) : boolean

ConnectorBase

connector::PersistentConnector

+ getConnection(PersistentSession, Parameter[]) : DataStoreConnection+ PersistentConnector()

persistent::SpecToConstraintTransformer

+ transform(StorageObjectSpec) : Constraint

«realize»

«realize»

DISEÑO DEL SERVICIO 17 - ESQUEMA GENERAL DEL CONECTOR PERSISTENTE

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

104

Existen dos implementaciones base de dicha interfase, la primera es

PersistentStorageHomeBase que es utilizada como clase base por el generador de código del conector.

Todo almacén generado por el compilador heredará directa o indirectamente de esta clase. La otra

implementación es PersistentRootStorageHome, que representa a la familia de almacenes para un tipo

de objeto almacenado que no tenga un supertipo.

El único estado de los almacenes que se mantiene en los repositorios es la secuencia que

permite generar identificadores para la familia. Cada conexión a un repositorio tendrá sus propias

instancias de los almacenes, pero todas ellas compartirán los mismos juegos de secuencias mediante

objetos de tipo ByteArraySequencerOperation, que utilizarán secuencias identificadas por los

identificadores de las familias de almacenes. De manera análoga, cada sesión que se conecte a un

repositorio tendrá como atributo una operación atómica que permitirá generar identificadores globales

para el repositorio. Esta operación utilizará un identificador especial reservado para la secuencia global.

OPERACIONES DE CONSULTA

Las operaciones de consultas se realizan utilizando una de las interfases que provee DB4O para tal

efecto. Dado que el usuario final no conoce esta interfase, será necesario que el compilador genere el

código necesario para construir las consultas para el servicio.

Las consultas que debe generar el compilador están definidas principalmente por las operaciones

de búsqueda que se deriven de la definición PSDL compilada. Estas consultas son dinámicas en el sentido

de que varían en función de la jerarquía de objetos que presenten los almacenes. Por esto, no pueden ser

generadas en su totalidad por el compilador, ya que éste sólo tiene una vista parcial del esquema, que

está dada por el archivo PSDL que esté procesando.

En consecuencia, es solamente en tiempo de ejecución donde se pueden conocer todos los

parámetros necesarios para construir las consultas requeridas. Esto implica que el conector deberá

proveer al compilador un mecanismo lo suficientemente amplio como para generar las consultas en

tiempo de ejecución.

Las consultas requeridas, siguiendo el esquema general propuesto en el servicio, son las basadas

en las especificaciones de los objetos (StorageObjectSpec). Con relativamente poco trabajo, se puede

traducir una especificación de objeto en una consulta de DB4O, representada por la clase Constraint. La

clase SpecToConstraintTransformer está encargada de esta tarea. Toma una especificación de objeto,

itera sobre todos los atributos de la especificación agregando una cláusula (Constraint) por cada atributo

que defina la especificación.

Este mecanismo es lo suficientemente genérico como para que el compilador se encargue de

agregar las entradas necesarias en las clases generadas, como para construir especificaciones válidas y en

base a ellas construir las consultas necesarias por el servicio.

CREACIÓN DE OBJETOS ALMACENADOS

Los mecanismos de construcción de objetos almacenados son los mismos, tanto para este

conector como para el conector de memoria, ya que ambos siguen el esquema de delegación propuesto

por el servicio. La diferencia radica en los mecanismos que se utilizan para la generación de

identificadores (antes descripta) y para la validación de las claves.

El mecanismo general para la validación se dispara cuando se construye un objeto y se basa en la

clase StorageHomeKeySet. Dicha clase es extendida por el conector persistente, mediante

PersistentStorageHomeKeySet, en donde se delega la validación en una consulta generada a partir de

todas las claves que se definan en la especificación. La consulta se genera utilizando la clase

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

105

SpecToConstraintTransformer. El conjunto de claves que incluya la especificación determinarán la forma

de consulta.

ACCESO REMOTO AL REPOSITORIO

El tipo de repositorio y como se accede al mismo no es parte de la especificación del servicio,

por lo que toda implicación derivada de permitir este tipo de acceso no está contemplada en el servicio.

Como ya mencioné anteriormente, el modo de acceso está definido por los parámetros de

entrada que recibe el conector cuando crea la sesión. Dado que mi repositorio está representado por una

base de datos DB4O, el acceso remoto se basa en el modelo cliente / servidor que esta base de objetos

provee. Todo repositorio en DB4O, se almacena en un archivo administrado por la base de datos.

La primera implicación, es que para acceder en forma remota se requiere que exista un servidor

corriendo en la máquina donde está almacenado el repositorio. O sea, para que un cliente pueda acceder

al repositorio remoto, el proceso servidor deberá estar operativo.

Para resolver esta primera implicación he tomado una política simple, cualquier servicio de

persistencia que se ejecute es un potencial servidor, cualquier repositorio que sea utilizado por algún

servicio de persistencia, podrá ser accedido en forma remota con tan sólo unos parámetros al momento

de inicio de la sesión. El único requerimiento para que se mantenga activo el servidor es que el proceso

donde se creó la sesión se mantenga activo. El conector persistente es el encargado de mantener activos

a los servidores locales existentes.

La clase ar.uba.fi.pmi.corba.pss.catalog.persistent.DataStore representa un repositorio local. El

conector lleva el registro de todos los DataStore creados desde que se inicializó el conector. Cuando se

crea una nueva conexión a un DataStore, sólo se crea un DataStore, si es la primera vez que se accedió a

ese DataStore, en caso contrario se le solicita al ya existente una nueva conexión. El DataStore hace

además las veces de servidor, cuando el mismo es construido con la información necesaria: un puerto

donde escuchar por conexiones, un usuario y una clave para garantizar el acceso.

deployment Deployment Model

máquina2

máquina3

máquina1

CPU1

CPU2

CPU3

Repositorio1 - DB4O

Repositorio3 - DB4OPMIPSS1

PMIPSS2

PMIPSS3

Cliente1

Cliente2

Cliente3

remoto

«use»

«local»

«remoto»

«local»

«use»

«use»

«use»

DISEÑO DEL SERVICIO 18 - ACCESO REMOTO A LOS REPOSITORIOS

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

106

La segunda implicación es muy importante al utilizar un modelo cliente / servidor, en el cual un

proceso hace las veces de cliente y otro de servidor. Para que ambos procesos puedan compartir objetos

del mismo tipo, ambos deben compartir el mismo espacio de clases, es decir que tanto el cliente como el

servidor deberán tener disponibles en tiempo de ejecución las mismas clases de objetos que serán

almacenadas y recuperadas del repositorio. Esta segunda implicación está resuelta por la primera, ya que

es el servicio de persistencia mismo quien hace las veces de cliente y servidor. Para que el servicio pueda

funcionar como tal requiere tener disponibles las clases que representan los objetos almacenados junto

con sus almacenes.

El gráfico 18 muestra un escenario real posible, donde existen 2 repositorios ubicados en dos

máquinas distintas. Hay tres clientes que acceden a estos repositorios, por medio de mi conector

persistente. El repositorio 2 es accedido en forma local y remota por dos conectores, uno ubicado en la

misma máquina donde está el repositorio (acceso local) y otro ubicado en otra máquina. En todos los

casos los clientes no se modifican por hacer uso del acceso remoto o local.

TRANSACCIONES

Como se describió anteriormente, el servicio de estado persistente soporta transacciones a

través del servicio de transacciones de CORBA [TS01]. El conector persistente tiene soporte para

transacciones a través de la base de datos que utiliza DB4O.

DB4O soporta todas las características que definen las transacciones, llamadas propiedades

ACID, de sus siglas en inglés: Atomicity, Consistency, Isolation, Durability [GrayReuter01]. Atomicity o

Atomicidad implica que todas las operaciones que se ejecutan dentro del contexto de una transacción,

son realizadas en su totalidad cuando la transacción es exitosa y cuando falla ninguna es realizada. Todas

las operaciones se pueden ver como una unidad atómica, todas o ninguna. Consistency o Consistencia

implica que la ejecución de la transacción deja la base de datos en un estado consistente, o sea no existen

estados intermedios dentro de una transacción para operaciones que se realicen fuera del contexto de la

transacción. Isolation o Aislamiento implica que cada transacción ve a la base de datos como si fuera la

única transacción que se ejecuta en forma simultanea, o sea la base bloqueará operaciones de

modificación concurrente, de forma tal que se ejecuten secuencialmente. Y finalmente, Durability o

Durabilidad implica que una vez ejecutada la transacción en forma exitosa, los cambios en la base son

permanentes y las próximas transacciones podrán verlos.

El tipo de transacción que se quiera ejecutar dependerá de la transacción generada por el

servicio de transacciones. Cuando se accede al servicio de estado persistente, sin hacer uso del servicio de

transacciones, todas las operaciones que se ejecuten dentro del contexto de una misma sesión serán

demarcadas automáticamente dentro del contexto de una transacción. De todas formas PSS permite el

uso de sesiones sin soporte de transacciones mediante algunas operaciones explicitas en ella, como por

ejemplo las operación flush y refresh. Estas operaciones tienen el objeto de operar sobre el sistema de

cache que implemente el conector. La primer operación debería forzar la realización de los cambios y la

segunda refrescar el estado de la sesión actual. Para este conector he optado por traducir la operación de

flush en un commit sobre la transacción actual, haciendo persistentes todos los cambios realizados hasta

ese instante. En DB4O, la operación de commit hace persistentes los cambios e inmediatamente abre

otra transacción sobre la que se puede seguir trabajando. La operación close sobre un ObjectContainer,

fuerza automáticamente un commit. En el caso de la operación refresh, la especificación misma indica

que sería extremadamente raro que un usuario final necesite llamar a este método y carece de sentido en

el uso normal de DB4O.

Este manejo de transacciones, sin hacer uso del servicio de transacciones, es una buena solución

de compromiso para los posibles usos que se le pueden dar al servicio de persistencia. No se trata de

reemplazar una base de datos orientada a objetos con el servicio de persistencia, sino de proveer una

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

107

solución acotada para mantener el estado de un servant, que generalmente tendrá pocos objetos para

persistir e interactuar.

CONSTRUCCIONES RESULTANTES DEL TRABAJO

Se construyó un compilador de lenguaje PSDL. El compilador consta dos partes principalmente

o Un parser que fue construido mediante Javacc y JJtree o Un generador de código fuente. El código fuente generado depende de la

implementación del servicio de persistencia que se defina.

El compilador usa un sistema generador de código fuente que construí para este trabajo. Utiliza

un modelo de clases y plantillas de código. Además construí un intérprete de clases que permite generar

esqueletos de clases e interfases en base a un objeto real dado. Estas dos construcciones pueden ser

utilizadas en cualquier otro trabajo que pueda o no estar relacionado con éste. Las plantillas se traducen a

código fuente utilizando la biblioteca Jexl que permite incrustar secciones dinámicas en las plantillas que

se evalúan en base a un contexto provisto por mi generador de código fuente.

Se construyeron dos implementaciones de conectores para mi servicio de estado persistente de CORBA:

o Una implementación que guarda los objetos en memoria o Otra implementación que utiliza una base de datos orientada a objetos, DB4O, para

persistir estos objetos.

Todas las construcciones realizadas, incluyendo el compilador, cuentan con pruebas unitarias,

para garantizar el funcionamiento de cada una de las partes. Estas pruebas permiten que las

construcciones puedan ser mejoradas y completadas, asegurando que lo construido siga funcionando de

la forma esperada. Las pruebas unitarias se realizaron utilizando la biblioteca JUnit.

Algunas de estas pruebas unitarias requieren compilar y validar su resultado en tiempo de

ejecución, lo que equivale a compilar código fuente Java en tiempo de ejecución. Para realizar esta tarea

he utilizado un framework llamado Janino, que permite utilizar clases compiladas en tiempo de ejecución

a partir de su código fuente (generado en este caso por el compilador).

Tanto el compilador como los conectores, utilizan internamente algunas bibliotecas de Jakarta

Commons como Loggings y Collections. Jakarta Commons es un proyecto de Apache que tiene el

propósito de construir componentes de software reutilizables en todo tipo de aplicaciones Java.

El código fuente fue desarrollado utilizando el IDE Eclipse y la versión 5 de Java. El código fuente

contiene más de 300 clases que corresponden al generador de código fuente, al compilador, al servicio de

estado persistente y a los dos conectores. Además se elaboraron más de 100 pruebas unitarias para todas

las funcionalidades construidas.

Al final de esta sección se encuentra una lista con las referencias utilizadas en el trabajo.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

108

UN EJEMPLO CONCRETO

A continuación voy a presentar un ejemplo simple, mostrando el código generado por el

compilador para el conector persistente, y demostrando cómo este código es utilizado en el contexto de

una aplicación CORBA.

CÓDIGO PSDL

El código PSDL representa una definición de un objeto almacenado abstracto, un almacén

abstracto para este objeto, junto con sus respectivas implementaciones.

En este ejemplo el objeto almacenado es Persona, su almacén es PersonaHome, y PersonaBase es

su implementación, mientras que PersonaHomeBase es la implementación del almacén. Persona tiene

tres atributos: nombre, apellido y número de documento, junto con una operación de usuario llamada

nombreCompleto. El almacén de la persona define una clave única en base al número de documento de

la persona. Y expone una operación de fábrica llamada crear, que permite construir personas con

nombre, apellido y número de documento en una sola operación.

RESULTADO DE LA COMPILACIÓN DEL CÓDIGO PSDL

Al compilar un archivo PSDL que contenga el código mostrado anteriormente, se obtiene la

siguiente lista de archivos:

Persona.java: interfase que define el objeto almacenado abstracto Persona.

PersonaBase.java: clase concreta que implementa a la interfase Persona.

PersonaHome.java: interfase que define el almacén abstracto de la Persona.

PersonaHomeBase.java: clase que implementa el almacén abstracto de la Persona.

PersonaHomeHelper.java, PersonaHomeHolder.java, PersonaHomeOperations.java,

_PersonaHomeLocalBase.java: Estos últimos archivos son generados de acuerdo con la especificación

CORBA. Toda interfase IDL tiene separadas sus operaciones en otra interfase, además debe tener sus

respectivas clases Holder, Helper. El último archivo es también requerido por la especificación de CORBA,

y representa una clase que permite obtener los identificadores IDL de la interfase CORBA, que son

utilizados para ubicar la interfase en un registro.

abstract storagetype Persona { state string nombre; state string apellido; state long nroDocumento; string nombreCompleto(); }; abstract storagehome PersonaHome of Persona { key nroDocumento; factory crear(nombre, apellido, nroDocumento); }; storagetype PersonaBase implements Persona { }; storagehome PersonaHomeBase of PersonaBase implements PersonaHome { };

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

109

Para el caso de este ejemplo me voy a focalizar sólo en los cuatro primeros archivos, que son los

más importantes a los efectos prácticos del servicio de persistencia.

PERSONA

El cuadro de texto siguiente representa una versión simplificada del código fuente de la interfase

generada por el compilador: Persona. Se han eliminado parte de los comentarios para reducir el tamaño.

En el código fuente, se muestra que la interfase hereda de la interfase StorageObject de PSS, además,

cada uno de los estados tiene sus métodos para lectura y escritura, y la operación de usuario

nombreCompleto.

Tanto los comentarios de inicio del archivo como los comentarios de la interfase y cada uno de los

métodos son generados en base a las plantillas de código configuradas en el compilador.

PERSONAHOME

Las primeras operaciones permiten ubicar a una Persona por la clave definida por el número de

documento. El último método representa la operación de fábrica, que permite crear Personas por sus

atributos.

/** * …. * <b>F.I.U.B.A.</b> * ….. * <a href='mailto:[email protected]'>Pablo Maximiliano Ilardi</a> * * Persona.java: was generated by PMI-PSDL Compiler Version 1.0 * …. */ package ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple; import org.omg.CosPersistentState.StorageObject; /** * ….. * @author PMI-PSDL Compiler Version 1.0 - persistent */ public interface Persona extends StorageObject { public abstract String nombre(); public abstract void nombre(String s); public abstract String apellido(); public abstract void apellido(String s); public abstract long nroDocumento(); public abstract void nroDocumento(long l); public String nombreCompleto(); }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

110

PERSONABASE

…. package ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple; …. public abstract class PersonaBase implements Persona, StorageObject, ExtendedStorageObject { private static final long serialVersionUID = -1737034293L; private StorageObjectIdentifier identifier; private long nroDocumento; private String apellido; private String nombre; public PersonaBase() { } public final long nroDocumento() { return this.nroDocumento; } public final void nroDocumento(long l) { this.nroDocumento = l; } public final String apellido() { return this.apellido; } public final void apellido(String s) { this.apellido = s; } public final String nombre() { return this.nombre; } public final void nombre(String s) { this.nombre = s; } public final byte[] get_pid() { return this.get_identifier().get_pid(); } public final byte[] get_short_pid() { return this.get_identifier().get_short_pid(); } public final StorageHomeBase get_storage_home() { return this.get_identifier().get_storage_home(); } public final void destroy_object() { ((ExtendedStorageHome) this.get_storage_home()).destroy(this); } … public final boolean object_exists() { return ((ExtendedStorageHome) this.get_storage_home()).exist(this); } …..

package ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple; …. /** * .... * @author PMI-PSDL Compiler Version 1.0 - persistent */ public interface PersonaHome extends PersonaHomeOperations, LocalInterface, IDLEntity, StorageHomeBase { } package ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple; … public interface PersonaHomeOperations extends StorageHomeBaseOperations { public Persona find_by_nroDocumento(long nroDocumento) throws NotFound; public byte[] find_ref_by_nroDocumento(long nroDocumento); public Persona crear(String nombre, String apellido, long nroDocumento); }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

111

Al segmento de código anterior también se le han eliminado los comentarios. Algunas

características a resaltar de esta clase son: que representa un objeto almacenado concreto que

implementa una definición abstracta (Persona), esta clase es la primera en una jerarquía y no requiere

que herede de otra clase que no sea la clase Object en Java. En Java es importante no forzar la herencia

de alguna clase, dado que sólo se soporta herencia de una única clase. Desde el punto de vista de la

persistencia transparente, si bien no es ideal requerir implementar interfases, como StorageObject o

ExtendedStorageObject, el forzar la herencia de alguna clase impondría una limitación mucho más

grande al modelo que el usuario final pueda definir.

Otra característica importante es que muchos métodos están declarados como final, esto implica

que no pueden ser redefinidos en subclases de esta clase. Esta característica se debe en algunos casos al

uso del patrón de diseño template method [Gamma01] (como en el caso del método initialize), y en otros

casos para asegurar el correcto funcionamiento de algunas operaciones como las de lectura/escritura de

las propiedades persistentes.

El método after_initialize muestra como los objetos son inicializados a partir de una

especificación, en este caso, se trata de inicializar las tres propiedades persistentes del objeto

almacenado.

Como la operación de usuario nombreCompleto no está definida por el compilador, la clase está

declarada como abstracta.

… public final boolean is_initialized() { return this.identifier != null; } public final synchronized void initialize(

StorageObjectIdentifier identifier, StorageObjectSpec specification) { if (this.is_initialized()) { throw new PERSIST_STORE(

"The storage object " + this.identifier + ", was already been initialized"); } this.identifier = identifier; this.after_initialize(specification); } public final StorageObjectIdentifier get_identifier() { if (!this.is_initialized()) { throw new IllegalStateException("The storageobject " + this + " has not been initialized"); } return this.identifier; } protected void after_initialize(StorageObjectSpec specification) { if (specification.hasValue("nroDocumento")) { this.nroDocumento = specification.get_long("nroDocumento"); } if (specification.hasValue("apellido")) { this.apellido = specification.get_string("apellido"); } if (specification.hasValue("nombre")) { this.nombre = specification.get_string("nombre"); } } }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

112

PERSONHOMEBASE

Como el almacén fue compilado para el conector persistente, esta clase hereda del almacén

persistente PersistentStorageHomeBase y sólo implementa el almacén abstracto de la persona,

PersonaHome.

PersonaHomeBase representa además, el primer almacén de la jerarquía. Esto se refleja en el

método initialize, donde se indica que el almacén debe ser inicializado como raíz (root) de la jerarquía.

Las operaciones de búsqueda find_by_nroDocumento, se traducen en la construcción de las

especificaciones con los valores de las claves que estén definidas en el almacén, en este caso es el

número de documento.

La operación de fábrica crear se limita a crear una especificación con los valores de los atributos

del objeto almacenado a crear, para luego delegar la construcción efectiva en un método de la

superclase.

PERSONABASEIMPL

Cuando los objetos almacenados o los almacenes definen operaciones de usuario, el usuario es

responsable de proveer la implementación para dichas operaciones. La clase PersonaBaseImpl solamente

se limita a implementar el método nombreCompleto.

package ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple; public class PersonaHomeBase extends PersistentStorageHomeBase implements PersonaHome { … public PersonaHomeBase() {} public Persona crear(String nombre, String apellido, long nroDocumento) { StorageObjectSpec spec = this.newSpec(); spec.set_string("nombre", nombre); spec.set_string("apellido", apellido); spec.set_long("nroDocumento", nroDocumento); return (Persona)this.create(spec); } public Persona find_by_nroDocumento(long nroDocumento) throws NotFound { StorageObjectSpec spec = this.newSpec(); spec.set_long("nroDocumento", nroDocumento); return (Persona)this.find_by_spec(spec); } public byte[] find_ref_by_nroDocumento(long nroDocumento) {…} protected void collectKeys(StorageHomeKeySet keys) { super.collectKeys(keys); StorageHomeKeyBuilder keynroDocumento = keys.keyBuilder("nroDocumento"); keynroDocumento.with("nroDocumento"); keynroDocumento.build(); } …… public StorageHomeBase initialize(Session session, String registered_id) throws NotFound { return this.initializeRootHome((PersistentSession)session, registered_id, "PSDL:ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple/PersonaBase:1.0"); } }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

113

En este caso, la operación simplemente concatena el apellido y nombre de la persona.

USO DEL MODELO EN UNA APLICACIÓN CORBA

Hasta este punto se mostró el modelo PSDL, ahora se verá como una aplicación CORBA puede

hacer uso de este modelo de trabajo propuesto por el servicio de estado persistente, PSS.

Toda aplicación CORBA está compuesta por objetos que proveen servicios llamados servants

definidos en lenguaje IDL y por objetos cliente que hacen uso de los servants. Los clientes acceden a los

servants mediante la ORB, quien provee referencias de los servants, de forma tal que los clientes siempre

tratan con objetos que implementan la interfase IDL expuesta por el servant y no con el objeto real que

provee el servicio.

En este ejemplo, existe un servant que provee servicios para acceder a personas, definidas

mediante el modelo PSDL. El servant es una instancia de la interfase RegistroPersona, definida en el

siguiente segmento de código IDL.

El servant RegistroPersona permite tres operaciones básicas sobre las personas: creación,

eliminación y consulta del nombre completo de la persona (método expuesto por el objeto almacenado

Persona definido en PSDL).

Las personas son creadas por el método crearPersona, cuyos parámetros identifican a la persona

y son los siguientes: nombre, apellido y número de documento. Para eliminar a una Persona se debe

llamar al método eliminarPersona, con un parámetro que indica el número de documento que identifica

a la Persona. Finalmente, para obtener el nombre completo de una Persona, se debe llamar al método

nombreCompleto con el número de documento de la Persona a consultar.

El siguiente gráfico muestra la interacción de las partes, cliente y servidor. La clase

ServidorPersonas representa el proceso servidor, donde se instancia al servant, que en este caso, es la

clase RegistroPersonasImpl, que implementa las operaciones de la interfase definida inicialmente,

RegistroPersona.

exception PersonaNoExiste {}; exception PersonaYaExiste {}; interface RegistroPersona { void crearPersona(in string nombre, in string apellido, in long nroDocumento)

raises (PersonaYaExiste); void eliminarPersona(in long nroDocumento)

raises (PersonaNoExiste); string nombreCompleto(in long nroDocumento) raises (PersonaNoExiste); void cerrarRegistro (); };

public class PersonaBaseImpl extends PersonaBase { /** * @see ar.uba.fi.pmi.corba.pss.psdl.test.persistent.simple.Persona#nombreCompleto() */ public String nombreCompleto() { return this.apellido() + ", " + this.nombre(); } }

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

114

El cliente está representado por la clase ConsultaPersonas. Esta clase accede al registro

mediante una referencia obtenida de la ORB. El acceso a la ORB, tanto para el cliente como para el

servidor, está centralizado en la clase ORBAccessor. Esta clase es simplemente una utilidad provista por

mí, que permite centralizar todas las operaciones que requieran interacción con la ORB, como acceso a

las referencias iniciales, al servicio de persistencia o a la creación de la ORB.

Este modelo de aplicación deja muy clara la separación entre el dominio de la aplicación CORBA

y el del servicio de persistencia. Todas las operaciones del registro de personas no están ligadas a los

objetos utilizados por el servicio de persistencia, esto se debe a que dichos objetos son privados, o sea

locales al servant y no pueden ser exportados por la ORB a otra máquina. Los objetos que define el

servicio de persistencia pasan a ser detalles de implementación del servant y desde el punto de vista de

un cliente del servant no existen.

object EjemploCORBAPSS

«servidor»Serv idorPersonas

+ main(String[]) : void+ ServidorPersonas(String[])

«cliente»ConsultaPersonas

# atenderConsultas() : void+ ConsultaPersonas(String[])+ leerEntrada() : String+ main(String[]) : void+ presentarMenu() : int

IDLEntityObject

«interface»

RegistroPersona

RegistroPersonaImpl

+ cerrarRegistro() : void+ crearPersona(String, String, int) : void+ eliminarPersona(int) : void# getPersona(int) : Persona+ nombreCompleto(int) : String+ RegistroPersonaImpl(ORBAccessor, PersonaHome)

Servant

RegistroPersonaPOA

«interface»

RegistroPersonaOperations

+ cerrarRegistro() : void+ crearPersona(String, String, int) : void+ eliminarPersona(int) : void+ nombreCompleto(int) : String

IDLEntityPersonaHomeOperations

StorageHomeBaseLocalInterface

«interface»

PersonaHome

StorageObject

«interface»

Persona

+ apel lido() : String+ apel lido(String) : void+ nombre() : String+ nombre(String) : void+ nombreCompleto() : String+ nroDocumento() : long+ nroDocumento(long) : void

utils::ORBAccessor

+ activateRootPOAManager() : void+ getDynAnyFactory() : DynAnyFactory+ getNameService() : NamingContextExt+ getOrb() : ORB+ getPersistentStateService() : ConnectorRegistry+ getRootPOA() : POA+ isIni tialized() : boolean+ object_to_string(org.omg.CORBA.Object) : String+ ORBAccessor()+ ORBAccessor(String[])+ ORBAccessor(String[], Properties)+ readObject(String) : org.omg.CORBA.Object+ shoutDown(boolean) : void+ string_to_object(String) : org.omg.CORBA.Object+ writeObject(org.omg.CORBA.Object, String) : void

-registro

-home

«creación»«accede»

DISEÑO DEL SERVICIO 19 - APLICACIÓN CORBA CON ACCESSO A PSS

A continuación voy a mostrar el segmento de código que da inicio al proceso servidor.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

115

Este segmento de código corresponde a la clase ServidorPersonas y muestra la interacción entre

el servant y el servicio de persistencia. El objeto server representa una instancia de la clase ORBAccessor,

la primera línea de código obtiene una referencia al registro de conectores como una referencia inicial a

la ORB. Las siguientes líneas inicializan al conector persistente, que es el conector identificado por

Persistent, con las clases que representan el dominio del servicio de persistencia. Estas clases son: el

almacén de personas PersonaHome y el objeto almacenado Persona. Las siguientes líneas están

dedicadas a la construcción de la sesión de trabajo con el servicio. La clase SessionParameterBuilder

permite construir los parámetros necesarios para crear la sesión de trabajo basada en el archivo

“registroPersonas.db”, que será utilizado por DB4O, como almacén de la base de datos que soporte a la

sesión. Finalmente, las últimas líneas permiten la inicialización del servant RegistroPersonaImpl con una

referencia al almacén de personas, que es utilizado para delegar todas las operaciones con personas.

Si se quisiera compartir la base de datos que da soporte al repositorio con otras instancias del

servicio de persistencia, tan sólo sería necesario agregar más parámetros al método que crea la sesión de

trabajo.

En el caso de los parámetros mostrados en el cuadro anterior, se habilita al repositorio para su

acceso remoto por el puerto 12300, mediante un usuario con nombre pablo, y clave test.

Parameter[] parameters = new SessionParameterBuilder() .withFileName("c:\\registroPersonas.db") .withUserName("pablo") .withUserPassword("test") .withPort(12300) .build(server.getOrb());

// Se accede al registro de conectores ConnectorRegistry registry = server.getPersistentStateService(); // Se obtiene el conector persistente Connector connector = registry.find_connector("Persistent"); // Se registra en el conector la clase PersonaBaseImpl connector.register_storage_object_factory( PSDLUtils.getRepositoryID(PersonaBase.class), PersonaBaseImpl.class); // Se registra el almacén por defecto final String idAlmacenPersonas = PSDLUtils.getRepositoryID(PersonaHomeBase.class); connector.register_storage_home_factory(idAlmacenPersonas, PersonaHomeBase.class); // Se crean los parámetros para utilizar el archivo registroPersonas.db como // base de datos para la sesión a crear Parameter[] parameters = new SessionParameterBuilder() .withFileName("c:\\registroPersonas.db").build(server.getOrb()); // Se crea una sesión Session mySession = connector.create_basic_session(READ_WRITE.value, parameters); // Ahora se obtiene el almacén de personas de la sesión PersonaHome almacenPersonas = (PersonaHome) mySession.find_storage_home(idAlmacenPersonas); // Ahora se crea el registro de personas CORBA RegistroPersonaImpl registro = new RegistroPersonaImpl(server, almacenPersonas); // Se activa el servant, para que se pueda acceder por otros procesos server.getRootPOA().activate_object(registro);

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

116

Otra instancia del servicio de persistencia que quiera acceder al mismo repositorio creado

anteriormente, deberá utilizar parámetros como los mostrados en el cuadro anterior. Se asume que la

dirección de Internet, donde se creó el repositorio inicialmente es 192.168.133.100.

Parameter[] parameters = new SessionParameterBuilder() .withHostName("192.168.133.100") .withUserName("pablo") .withUserPassword("test") .withPort(12300) .build(server.getOrb());

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

117

REFERENCIAS

PSS01 – Persistence State Service V2.0 OMG 2002. http://www.omg.org/cgi-bin/doc?formal/02-

09-06

TS01 - Transaction Service Specification, OMG

2003.http://www.omg.org/technology/documents/corbaservices_spec_catalog.htm

CORBA01 – Common Object Request Broker Architecture, Core Specification OMG.

http://www.omg.org/technology/documents/corba_spec_catalog.htm

JVMTI01 – Java Virtual Machine Tool interface – SUN Java 5 2004.

http://java.sun.com/j2se/1.5.0/docs/guide/jvmti

JVMI01 – Java Virtual Machine Instrumentation – SUN Java 5 2004.

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/instrument/Instrumentation.html

IDL2Java – IDL to Java Language Mapping Specification. http://www.omg.org/cgi-

bin/doc?formal/02-08-05

CorbaJava00 – CORBA Technology and the Java (TM) 2 Platform Standard Edition.

http://java.sun.com/j2se/1.5.0/docs/guide/idl/corba.html

Garshol00 – BNF and EBNF: What are they and how do they work?, Lars Marius Garshol.

http://www.garshol.priv.no/download/text/bnf.html

CooperRice00 – Engineering A Compiler, Keith Cooper - Rice University, Houston, Texas; Linda

Torczon - Rice University, Houston, Texas http://www.cs.rice.edu/~keith/

Javacc00 – Java Compiler Compiler [tm] (JavaCC [tm]) - The Java Parser Generator.

https://javacc.dev.java.net/

JavaccTu00 – The JavaCC Tutorial, Theodore S. Norvell http://www.engr.mun.ca/~theo/JavaCC-

Tutorial/

Crenshaw00 – Let's Build a Compiler, by Jack Crenshaw http://compilers.iecc.com/crenshaw/

Dasgupta00 – Algorithms, S. Dasgupta, C. H. Papadimitriou, and U. V. Vazirani. July 18, 2006.

Gamma01 – Design Patterns: Elements of Reusable Object-Oriented Software - Addison-Wesley

Professional Computing Series - by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,

January 15, 1995.

BM0 – Meyer, Bertrand. “Object-Oriented Software Construction”. Prentice Hall, 1997. ISBN 0-

13-629155-4.

GrandM01 – Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd

Edition, Volume 1, by Mark Grand. Wiley; September 17, 2002. ISBN-10: 0471227293.

KingG01 – Hibernate in Action, Christian Bauer, Gavin King; Manning, 2005. ISBN: 1932394-15-X.

http://www.hibernate.org

RoodsR01 – Java Data Objects, Robin M. Roods; Addison-Wesley, 2003. ISBN 0-321-12380-8.

http://java.sun.com/javaee/technologies/jdo

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

118

ReverbelF01 – Persistence in Distributed Object Systems: ORB/ODBMS Integration, Francisco

Reverbel. Ph.D. Dissertation Presented to the Computer Science Department of the University of

New Mexico. April 1996.

NewardT01 – Avoiding the Quagmire Offering solutions to the problems of Object/Relational-

Mapping by Ted Neward. May 21th, 2007. http://www.odbms.org/experts.html#article12

CookWR01 – Native Queries for Persistent Objects. A Design White Paper. William R. Cook.

Department of Computer Sciences, The University of Texas. Febraury 15th,

2006.http://www.cs.utexas.edu/users/wcook/papers/NativeQueries/NativeQueries8-23-05.pdf

Castor01 – Castor Open Source data binding framework for Java. http://www.castor.org

Toplink01 – Oracle TopLink, Oracle Fusion Middleware family of products.

http://www.oracle.com/technology/products/ias/toplink/index.html

CKA01 - Surviving Object-Oriented Projects, By Alistair Cockburn, Dec 22, 1997, Addison Wesley

Professional. Part of The Agile Software Development Series.

DBO401 – Database for Objects. http://www.db4o.com/about/productinformation

SODA01 – Simple Object Database Access. http://sodaquery.sourceforge.net

Cache01 – InterSystems Caché® high-performance object database.

http://www.intersystems.com/cache/index.html

Annotations01 – Java 5.0 Annotations.

http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html

Conc01 – The origin of concurrent programming: from semaphores to remote procedure calls

book contents. ISBN:0-387-95401-5, Center for Science and Technology, Syracuse University,

Syracuse, NY. Edsger W. Dijkstra, Per Brinch Hansen, C. A. R. Hoare

RoySeif01 – Concepts, Techniques, and Models of Computer Programming, by Peter Van Roy and

Seif Haridi. The MIT Press Cambridge, Massachusetts London, England. ISBN 0-262-22069-5

GrayReuter01 - Transaction Processing: Concepts and Techniques by Jim Gray, Andreas Reuter ,

1993 Morgan Kaufmann. ISBN 1558601902

DEVQTC01 – Developing Quality Technical Information. A handbook for Writers and Editors.

Second Edition. G. Hargis, M. Carey, A. Hernandez, P. Hughes, D. Longo, S. Rouiller, E. Wide. ISBN

0-13-1477490.

PSS-PMI – Mi servicio de estado persistente Pablo M. Ilardi

119

REFERENCIAS A BIBLIOTECAS

Jakarta Commons – http://commons.apache.org/

Jakarta Commons Logging – http://commons.apache.org/logging/

Jakarta Commons Collections – http://commons.apache.org/collections/

Jakarta Commons Jexl – http://commons.apache.org/jexl/

Hibernate – http://www.hibernate.org/

JDO – http://java.sun.com/jdo/

Db4o – Database For Objects , http://www.db4o.com/

Javacc – Java Compiler Compiler, https://javacc.dev.java.net/

JJTree – https://javacc.dev.java.net/doc/JJTree.html

Junit – http://www.junit.org/

Janino – An Embedded Java Compiler, http://www.janino.net/

Eclipse – http://www.eclipse.org/

120

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

121

COMPARACIÓN CON OTRAS IMPLEMENTACIONES DEL SERVICIO DE ESTADO

PERSISTENTE DE CORBA

Servicios a Comparar ..............................................................................................123

OpenORB ............................................................................................................123

OpenCCM ............................................................................................................124

Puntos de Comparación ..........................................................................................124

Comparación entre OpenORB, OpenCCM y mi servicio ..........................................125

Compatibilidad con el estándar ...........................................................................125

Nivel de dependencia con la ORB ........................................................................127

Conectores disponibles y características de los mismos .......................................127

Requerimientos y facilidad de uso .......................................................................127

Extensibilidad para la creación de nuevos conectores..........................................128

Conclusiones ...........................................................................................................128

Referencias .............................................................................................................130

122

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

123

ado que PSS no es uno de los servicios CORBA más difundidos, existen pocas implementaciones,

y especialmente en Java. Cuando se habla de una implementación de CORBA, generalmente se

está hablando de implementaciones de la ORB. Todas las implementaciones proveen además de

la ORB, implementaciones de los servicios básicos, como puede ser el NameService y algunas en

particular ofrecen implementaciones del PSS.

Mi proyecto no contempla la creación de una ORB, ya que hay muchas implementaciones de

ella, sino se propone integrar el servicio con, idealmente, cualquier implementación de la ORB en Java.

He probado mi servicio, con dos implementaciones de ORB: la de SUN para Java que trae la máquina

virtual y la ORB de JacORB [JACORB01]. JacORB es una implementación en código abierto del

Departamento de Ciencias de la Computación de la Universidad de Freie en Berlín, Alemania.

Ninguna de las dos ORBs con las que he probado mi servicio, tienen implementaciones de PSS, y

por ende, constituyeron un buen escenario para integrar mi servicio con ellas.

SERVICIOS A COMPARAR

De las pocas implementaciones existentes, para realizar la comparación he elegido las dos más

representativas: OpenORB [OPENORB01] y OpenCCM [OPENCCM01].

OPENORB OpenORB (u ORB abierta) es una implementación de la ORB de CORBA, basada en una ORB

anterior, escrita totalmente en Java, y llamada JavaORB (que actualmente se encuentra discontinuada).

Esta ORB fue construida por el grupo de personas llamado Distributed Object Group (Grupo de Objetos

Distribuidos) [DOG01], que se dedica a proveer tecnología CORBA de código abierto. Uno de los servicios

que provee OpenORB es el servicio de estado persistente que utilizaré para la comparación. Según la

documentación de este servicio, provee una implementación totalmente compatible con la especificación

PSS de CORBA.

OpenORB provee tres conectores para el servicio: conector de memoria sin soporte

transaccional, otro conector con soporte de archivos donde se almacena el estado persistente con

soporte transaccional y un tercer conector que utiliza una base de datos relacional para almacenar el

estado persistente del repositorio. Este último conector requiere un proveedor de base de datos

relacional, que se encuentra fuera del control del servicio. Al tratarse de una dependencia no controlada

por el servicio, la aplicación CORBA requiere tener configurada una base de datos relacional para poder

utilizar esta implementación del servicio.

Según la documentación, este servicio requiere utilizar su propia implementación de ORB y su

servicio de Transacciones OTS, también provisto por este grupo.

El servicio provee un compilador PSDL, que se invoca mediante un script por línea de comandos.

El servicio no soporta persistencia transparente, sólo se puede utilizar el servicio mediante una definición

PSDL compilada por su propio compilador.

La configuración se realiza a través del mecanismo de configuración de la ORB provista por

OpenORB, lo que lo hace completamente dependiente de ella. Esta configuración también incluye los

parámetros de uso de los conectores de base de datos relacional y de archivos, dejando sin uso a los

parámetros de construcción de la sesión.

En cuando a las posibilidades de extensión, la documentación sólo trata la interacción con el

servicio por un usuario del mismo. De todas formas, el código fuente es libre y se lo puede analizar. El

compilador de PSDL que utiliza, está basado en el compilador de OpenORB para el que está definido el

D

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

124

servicio. Para agregar un nuevo conector es necesario modificar el código del servicio, ya que éste no

provee configuración alguna que permita un comportamiento dinámico del compilador. El compilador

sólo genera código para sus tres conectores. De manera análoga, el servicio tampoco soporta registro

dinámico de conectores, lo que obliga a modificar el código para agregar un nuevo conector.

OPENCCM OpenCCM también surge de JavaORB, pero no se trata de una implementación de ORB, sino de

una implementación del modelo de componentes de CORBA [CCM01]. Una parte de CCM es la ORB que

se utiliza internamente y en este caso se trata de JacORB. Al tratarse de un contenedor de componentes,

de acuerdo a la especificación de CORBA, es posible también combinarlo con el servicio de estado

persistente PSS. OpenCCM provee su propia implementación de PSS.

Este servicio provee dos conectores de funcionalidades similares. El primero es un conector que

utiliza al framework Hibernate [KingG01] para almacenar el estado en una base de datos relacional.

Hibernate es una herramienta de mapeo de objetos a bases de datos relacionales. El otro conector utiliza

a la API de SUN, para persistencia de objetos llamada JDO (Java Database Object) [RoodsR01]. Si bien el

servicio provee dos conectores, se debe definir en tiempo de compilación del servicio cual conector se

utilizará, lo que deja en la práctica un único conector posible en tiempo de ejecución. El servicio está muy

acoplado a la implementación de la ORB y al contenedor de componentes. No me fue posible configurar

el conector de Hibernate en mis pruebas, sólo logré hacerlo funcionar con JDO.

Tanto JDO como Hibernate requieren del uso de una base de datos relacional. De todas formas

Hibernate provee herramientas para utilizar una base de datos incrustada en el proceso donde se lo

utilice, como por ejemplo HypersonicSQL [HSQLDB01] que está realizada completamente en Java.

La posibilidad de extensión está dada en el momento de compilación del servicio, pero está muy

acoplada al modelo de construcción de componentes y servicios que provee OpenCCM.

PUNTOS DE COMPARACIÓN

Las características que he comparado con estas dos implementaciones del servicio son:

1. Compatibilidad con el estándar Se evalúa el grado en que se respetan los requerimientos impuestos por el estándar con respecto

a los requerimientos del mismo, tales como claves múltiples, construcciones de PSDL, generación de

código Java, funcionalidades del compilador, etc.

2. Nivel de dependencia con la ORB Se evalúa la dependencia de la implementación del servicio con respecto a la ORB para la que se

promueve su utilización.

3. Conectores disponibles y características de los mismos Este punto es quizás, el más importante desde la perspectiva de un usuario del servicio. Se

evalúan los conectores que provee el servicio, sus requerimientos y funcionalidades que proveen.

4. Requerimientos y facilidad de uso Otro punto importante para un usuario final son los requerimientos que impone el uso de un

determinado servicio o conector, y los conocimientos y recursos de los cuales debe disponer un nuevo

usuario.

5. Extensibilidad para la creación de nuevos conectores Finalmente este punto trata de evaluar las posibilidades y limitaciones para la extensión del

servicio, ya sea mediante un nuevo conector o modificando el servicio.

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

125

COMPARACIÓN ENTRE OPENORB, OPENCCM Y MI SERVICIO

COMPATIBILIDAD CON EL ESTÁNDAR Uno de los puntos destacados de la especificación de CORBA es la unicidad de interfases entre

distintas implementaciones del servicio. Esto implica que todos los servicios deberán generar el mismo

código para las mismas definiciones abstractas compiladas.

OpenORB, genera código de interfases incompatible con la especificación, dado que no respeta

el mapeo de las referencias. De acuerdo a la especificación, el mapeo de Java define que las referencias

se deben traducir a byte[] y para C++, a clases llamadas EntidadRef, donde Entidad es el tipo del objeto

almacenado. En OpenORB, las referencias se traducen en clases EntidadRef, es decir que utilizaron el

mapeo de C++ en Java. Al definir una clase particular para la representación de las referencias, da como

resultado que el código generado por su compilador sea muy distinto al esperado por la especificación.

OpenCCM, tampoco respeta las interfases. De acuerdo a la especificación, las interfases que

representen objetos almacenados deberán heredar de la interfase StorageObject de CORBA. Sin embargo

en OpenCCM, heredan de una interfase definida por ellos:

org.objectweb.openccm.pss.runtime.common.api.StorageObject, que a su vez hereda de

StorageObject. Dicha interfase agrega un método inicialize, que es utilizado para indicarle al objeto el

almacén al que pertenece.

En cuanto a la generación de almacenes abstractos, OpenORB y mi compilador generan código

muy similar, pero OpenCCM genera código totalmente incompatible con CORBA.

OpenCCM no genera interfases para las operaciones del almacén. Todas las operaciones están

declaradas dentro de la misma interfase del almacén. Tampoco genera las clases de utilidad como Holder

y Helper que requiere CORBA para una interfase. OpenCCM no genera interfases CORBA válidas.

Considero que es un problema importante que no se respete el estándar a nivel de interfase,

dado que expone la implementación de un servicio CORBA al usuario del mismo, sin permitir un

intercambio directo de implementaciones entre distintos proveedores del servicio. La independencia de

la implementación es una de las características más importantes de las especificaciones CORBA.

// En OpenCCM public interface PersonHome extends org.omg.CosPersistentState.StorageHomeBase // En mi compilador (similar en OpenORB) public interface PersonHome extends PersonHomeOperations, LocalInterface, IDLEntity, StorageHomeBase

// En OpenCCM public interface Person extends org.objectweb.openccm.pss.runtime.common.api.StorageObject // En mi compilador import org.omg.CosPersistentState.StorageObject; public interface Person extends StorageObject

// En OpenORB public void adress( org.objectweb.openccm.pss.demo1.AddressRef arg ); // En mi compilador public abstract void adress(byte[] br);

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

126

En cuanto al compilador, tanto OpenCCM como OpenORB, proveen soporte de preprocesador,

que permite darle directivas al procesador de código. Por ejemplo, se puede definir un módulo con un

nombre simple para traducirlo en Java a un paquete más complejo con varios niveles de nombre. En el

caso de mi compilador, sólo soporta la directiva al preprocesador para incluir archivos externos en el

proceso de compilación.

Una característica que sólo posee mi compilador, es la optimización de los nombres de clase

utilizados. En Java se puede referenciar a una clase por su nombre completo (incluyendo el paquete), o

por su nombre importando el nombre completo en el archivo, si es que la clase se encuentra definida en

otro paquete Java. Si la misma clase se utiliza en muchos lugares del código, es recomendable importarlo,

para poder referenciarla simplemente con su nombre en todos esos lugares. Mi compilador genera

código optimizado, importando las clases utilizadas cuando sea necesario y sólo utiliza el nombre

completo de la clase cuando exista otra clase utilizada en el mismo archivo pero definida en otro paquete.

En el caso de OpenORB y OpenCCM, se utiliza el nombre completo siempre, incluso cuando la clase

referenciada se encuentra dentro del mismo paquete Java.

Tanto en el caso de OpenORB como OpenCCM, sus respectivos compiladores generan código

para las construcciones IDL definidas en el archivo PSDL compilado, mi compilador no lo hace, tan solo las

ignora.

En cuanto a la generación de clases concretas, si bien queda fuera del alcance de la

especificación, existen algunas características importantes. Mi compilador y el de OpenORB generan

objetos almacenados que sólo implementan interfases, mientras que OpenCCM obliga a heredar de una

clase interna del conector. Además OpenCCM también obliga a implementar una interfase de JDO,

exponiendo totalmente los detalles de la implementación del conector, que en este caso logra la

persistencia por medio de JDO. En el caso de OpenORB, se implementan interfases propias del conector,

mientras que el caso de mi servicio solo se implementa una interfase definida por el servicio, ocultado los

detalles del conector que se esté utilizando.

Como resultado de esta comparación, se puede decir que tanto OpenORB como OpenCCM

generan interfases incompatibles con la especificación CORBA por los motivos expuestos. De todas

formas, OpenCCM tiene un mayor grado de incompatibilidad al exponer interfases propias de su

// En OpenCCM public abstract class ST_Person extends org.objectweb.openccm.pss.runtime.jdo.lib.StorageObjectBase implements org.objectweb.openccm.pss.demo1.Person, javax.jdo.InstanceCallbacks // En OpenORB public abstract class ST_Person implements org.objectweb.openccm.pss.demo1.Person, org.openorb.pss.connector.memory.PersistentObject // En mi compilador public abstract class ST_Person implements Person, StorageObject, ExtendedStorageObject

// En OpenORB o OpenCCM public org.objectweb.openccm.pss.demo1.Address adress(); public void adress( org.objectweb.openccm.pss.demo1.Address arg ); // En mi compilador public abstract Address adress(); public abstract void adress(Address b);

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

127

implementación e ignorar la definición de interfases para almacenes. Mi compilador está en desventaja al

no generar construcciones IDL, aunque genera código más optimizado para las construcciones PSDL y

compatible con la especificación CORBA.

NIVEL DE DEPENDENCIA CON LA ORB OpenORB requiere la configuración del servicio por los mismos mecanismos que usa para

configurar su implementación de ORB. Esto implica que sólo se puede utilizar el servicio con esta

configuración, y por ende, con una implementación particular de la ORB. A nivel de código fuente, es

también dependiente de la implementación de la ORB. Esto se debe principalmente a que se reutilizaron

muchas utilidades / funcionalidades entre los distintos servicios que esta ORB provee.

En cuanto a OpenCCM, la documentación menciona explícitamente que se requiere de

OpenCCM para que el servicio funcione correctamente.

En conclusión, mi servicio es el único que soporta distintas ORBs. La dependencia de una ORB en

el contexto de la implementación completa de una ORB, no es necesariamente una mala cualidad, dado

que la dependencia permite un grado más alto de reutilización de componentes entre servicios. Por

ejemplo, es una gran ventaja no tener que volver a implementar el compilador desde cero para cada

lenguaje que requiera cada servicio, si el compilador IDL de la ORB permite extensiones al lenguaje, tal

como PSDL.

CONECTORES DISPONIBLES Y CARACTERÍSTICAS DE LOS MISMOS El servicio de OpenORB es el que provee mayor número de conectores, tres conectores. El

conector de memoria es equivalente a mi conector de memoria con las mismas funcionalidades. Tanto el

conector de archivos, como el de base de datos, proveen integración con el servicio de transacciones.

Ninguno de los dos conectores hace referencia a acceso remoto al repositorio.

OpenCCM provee solo dos conectores con las mismas funcionalidades, la única diferencia entre

ellos, es la biblioteca que utilizan para persistir los objetos almacenados. De todas formas, el servicio

solamente funciona con uno de los conectores por vez, debido a la configuración que requiere la ORB

para habilitarlos. OpenCCM provee integración con su implementación del servicio de transacciones de

CORBA. Al igual que OpenCCM, tampoco hace referencia al acceso remoto del repositorio.

Analizando el código fuente de OpenORB, se deduce que no tiene soporte para compartir el

repositorio entre más de un servicio. No implementa ningún mecanismo para la generación de

identificadores, que contemple otro proceso además del propio. Para la generación de identificadores

utiliza la hora de la máquina en la cual se está ejecutando el proceso que genera el identificador único.

En el caso de OpenCCM, se utilizan los mecanismos que proveen tanto Hibernate como JDO para

la generación de identificadores, mediante alguna traducción a los identificadores que requiere el servicio

de estado persistente. Estos mecanismos se basan en la generación de identificadores mediante una

secuencia o algún valor auto incrementado que provea la base de datos relacional que se utilice en

conjunto con el servicio.

REQUERIMIENTOS Y FACILIDAD DE USO En el caso de OpenORB, tanto el conector de memoria como el de archivos, no tienen

requerimientos adicionales, sólo se debe configurar el servicio. En el caso del conector de base de datos

relacional, se requiere tener una base datos operativa para poder utilizar el servicio.

OpenCCM requiere de una configuración adicional para poder utilizar el servicio. Primero se

debe identificar el tipo de conector que será utilizado y luego, tanto para el caso del conector de JDO

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

128

como para el de Hibernate, es necesario crear y administrar la base datos con la cual se utilizará el

servicio, antes de poder utilizar el servicio mismo.

Tanto OpenORB como OpenCCM requieren de la intervención explícita del usuario para poder

utilizar el conector de base de datos relacional. El usuario debe agregar también el driver de conexión

para la base datos que decida utilizar.

En el caso de mi servicio, no se requiere ninguna configuración adicional para que funcione el

servicio o los conectores. Solamente requiere los parámetros necesarios para la creación de la sesión de

trabajo. A diferencia de los otros dos servicios, mis conectores no exponen los detalles de

implementacion de los mismos para su configuración. Cuando se utiliza el conector para DB4O, el usuario

no sabe realmente cómo y con qué se están persistiendo los objetos.

EXTENSIBILIDAD PARA LA CREACIÓN DE NUEVOS CONECTORES OpenORB no prevé la existencia de nuevos conectores. Su registro de conectores debe conocer

los tipos de conectores que provee el servicio. Para agregar nuevos conectores es necesario modificar el

código fuente del servicio y recompilarlo.

OpenCCM tiene muy bien desacoplado el concepto de interfase e implementación. Se puede

configurar mediante modificaciones al script que compila el servicio indicándole el tipo de conector que

se utilizará. El proceso de compilación buscará un directorio para la interfase y otro para la

implementación con el nombre del conector configurado. De todas formas, esta configuración está muy

ligada al proceso de compilación ya que no se realiza en tiempo de ejecución.

Mi servicio de persistencia es el único que provee una configuración en tiempo de ejecución de

los conectores que estarán disponibles. Para agregar un nuevo conector no se requiere recompilar el

servicio, sino solamente compilar el nuevo conector y agregar las líneas de configuración para el servicio.

El registro de conectores está completamente desacoplado de las implementaciones de conectores.

CONCLUSIONES

Un punto que está fuera de la comparación es la cantidad de personas involucradas en el

proyecto. Tanto en OpenORB como OpenCCM, los proyectos están constituidos por múltiples personas y

además, al contar con el soporte de una ORB, tienen muchas utilidades que se puede reutilizar en el

servicio. En mi caso se trató de un proyecto de una única persona.

Desde el punto de vista de la extensibilidad creo que mi servcio es el más apto de los tres, dado

que es el que permite mayor grado de configuración sin recompilar y también es el único que no expone

detalles de implementación de los conectores. Mi compilador de PSDL está en desventaja al no generar

código fuente para las construcciones IDL, esta debe ser la primer tarea a encarar en un trabajo futuro

sobre el servicio. Implementar la integración con algún servicio de transacciones de CORBA (TTS) también

es una característica que sería importante agregar. Desde el punto de vista funcional los tres servicios

proveen funcionalidades similares (salvo por la integracion con TTS). En relación a la correctitud de los

requerimientos de la especificación, creo que mi servicio es el más apto, ya que es el único que según mi

interpretación del estándar, lo respeta. La independencia de la ORB es un objetivo propio de mi trabajo y

no tiene porque estar soportado por otros servicios.

Ninguna de las implementaciones del servicio que he analizado, tanto en Java como en C++,

incluyendo la mía, proveen soporte para persistencia transparente. Esto creo que se debe principalmente

a que no está bien resuelto por la especificación, y en el modelo de trabajo de CORBA es común requerir

de un compilador de un lenguaje genérico a un lenguaje concreto.

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

129

Finalmente, el acceso remoto al repositorio es también una caracteristica que está fuera del

alcance de la especificación y que en las otras implementaciones del servicio está solo resuelta

parcialmente por el uso de una base de datos relacional.

La forma de accesso remoto propuesta tiene como desventaja que no permite independecia de

la ubicación, ya que depende directamente de la ubicación donde se creó el repositorio. De todas

formas, la ubicación del repositorio es sólo necesaria al momento de crear la sesión de trabajo y es éste el

único punto donde está expuesta. Este grado de desacoplamiento de la ubicación permite migrar el

repositorio en forma manual y relativamente sencilla, poniendo la ubicación física del repositorio en una

configuración externa a la aplicación.

A continuación expongo una tabla comparativa entre las distintas características analizadas y los

tres servicios de estado persistente comparados.

Característica \ Servicio OpenORB OpenCCM PMI-PSS

Respeta el estandar No No Si

Depende de una ORB Si Si No

Candidad de Conectores 3 2(uno x vez) 2

Complejidad de Uso Media Alta Baja

Requiere adicionales BDR + Driver BDR + Driver No

Extensibilidad Baja Media Alta

Soporte de transacciones Corba TSS Corba TSS Solo x DB4O

Compilador IDL + PSDL IDL + PSDL PSDL

Persistencia Transparente No No No

Acceso Remoto No x BD Relacional x Conector Persistente

Expone implementación Si, x BD Relacional Si, x BD Relacional y JDO No

Candidad de Personas +3 Consorcio de Software 1(Yo)

COMPARACIONES 1 - TABLA COMPARATIVA

Comparación con otras implementaciones del servicio de estado persistente Pablo M. Ilardi

130

REFERENCIAS

JACORB01 – JacORB: Software Engineering and Systems Software Group, at the CS department of

Freie Universität Berlin, Germany. www.jacorb.org

OPENORB01 – The Community OpenORB, www.openorb.org

DOG01 – Distributed Object Group. dog.team.free.fr

OPENCCM01 – OpenCCM - The Open CORBA Component Model Platform.

openccm.objectweb.org

CCM01 – CORBA Component Model (CCM) Specification, OMG.

http://www.omg.org/technology/documents/formal/components.htm

KingG01 – Hibernate in Action, Christian Bauer, Gavin King; Manning, 2005. ISBN: 1932394-15-X.

http://www.hibernate.org

RoodsR01 – Java Data Objects, Robin M. Roods; Addison-Wesley, 2003.ISBN 0-321-12380-8.

http://java.sun.com/javaee/technologies/jdo

HSQLDB01 – HSQLDB a relational database engine written in Java, with a JDBC driver, supporting a

large subset of ANSI-92 SQL. hsqldb.sourceforge.net

Trabajos Adicionales Pablo M. Ilardi

131

TRABAJOS ADICIONALES PSS tiene algunas limitaciones en su modelo que podrían ser incorporadas en un futuro. La

principal es la posibilidad de hacer consultas complejas. La única alternativa de consultas que se plantea

en PSS es mediante consultas por las claves de los objetos en los almacenes. Existen situaciones reales en

las que se quieren realizar consultas más avanzadas sobre los objetos, por atributos que no son parte de

la clave necesariamente o por condiciones complejas. Este tipo de consultas están bien resueltas en la API

de SODA en la que se basa uno de los modos de consulta de DB40.

Existe otra limitación importante de PSS, que es el manejo de colecciones. En PSS la única forma

de manejar una colección como atributo / estado de un objeto almacenado, es mediante un array de

objetos almacenados, lo que se logra mediante una definición IDL de un arreglo de objetos almacenados.

Si bien es suficiente para almacenar más de un objeto como estado, el lenguaje Java provee posibilidades

mucho más avanzadas para el manejo de colecciones, tales como colecciones con semántica de conjuntos

o colecciones indexadas. El tratar con un array plano de objetos hace que se requiera la construcción de

operaciones que ya están definidas en el lenguaje en forma nativa para poder utilizarlas con el modelo de

PSS. Esta limitación concretamente se debe a que PSS es una especificación multilenguaje,

particularmente para Java y C++. Esto requiere que las funcionalidades provistas deban ser soportadas

por todos los lenguajes de forma relativamente simple y en el caso de C++, los arrays son las únicas

construcciones nativas que se soportan para el manejo de colecciones.

Mi implementación en particular requiere agregar al compilador de PSS la posibilidad de generar

construcciones IDL para permitir interactuar a los objetos almacenados con ellas, por ejemplo, mediante

atributos / estados que sean tipos definidos en IDL directamente.

De acuerdo a la especificación PSS, existen dos características opcionales, que son persistencia

transparente y soporte transaccional. Si se provee persistencia transparente, se dice que el servicio es: "a

compliant Persistent State Service implementation with transparent persistence support", o una

implementación del servicio de estado persistente compatible con soporte de persistencia transparente.

Y si provee soporte transaccional es: “a compliant Persistent State Service implementation with

transaction support” o una implementación del servicio de estado persistente compatible con soporte de

transacciones. Mi servicio no provee ninguna de estas dos características adicionales, por lo que son

buenos candidatos de futuros trabajos.

132

Índice de Gráficos Pablo M. Ilardi

133

ÍNDICE DE GRÁFICOS

FUNCIONAMIENTO DEL SERVICIO DE ESTADO PERSISTENTE

Gráfico 1 - Interacción con la ORB _______________________________________________________ 33

Gráfico 2 - MODELO LÓGICO ___________________________________________________________ 34

Gráfico 3 - Sesión para acceder al DataStore _______________________________________________ 34

Gráfico 4 - Herencia Diamante de interfases en CORBA ______________________________________ 35

Gráfico 5 - Tipos y Modelo de Herencia en PSS _____________________________________________ 36

Gráfico 6 - Acceso al servicio ___________________________________________________________ 37

COMPILADOR

Compilador 1 - Proceso de Compilación ___________________________________________________ 53

Compilador 2 - Primer Paso ____________________________________________________________ 54

Compilador 3 - Javacc ________________________________________________________________ 56

Compilador 4 - Diagrama general del Compilador ___________________________________________ 58

Compilador 5 - Fases del compilador _____________________________________________________ 60

Compilador 6 - Nodos de árbol __________________________________________________________ 63

Compilador 7 - Primera Fase ___________________________________________________________ 64

Compilador 8 - Exepciones de Validación __________________________________________________ 68

Compilador 9 - Tres Partes _____________________________________________________________ 69

Compilador 10 - Qué generar ___________________________________________________________ 70

Compilador 11 - Generación de archivos __________________________________________________ 71

Compilador 12 - Cómo generarlo ________________________________________________________ 72

DISEÑO DEL SERVICIO DE ESTADO PERSISTENTE

Diseño del Servicio 1 - Configuración de la ORB _____________________________________________ 76

Diseño del Servicio 2 - Conectores _______________________________________________________ 78

Diseño del Servicio 3 - PSDLUtils_________________________________________________________ 79

Diseño del Servicio 4 - Parameters _______________________________________________________ 80

Diseño del Servicio 5 - Conector y Sesiones ________________________________________________ 82

Diseño del Servicio 6 - Modelo base de almacenes __________________________________________ 83

Diseño del Servicio 7 - DElegación _______________________________________________________ 84

Diseño del Servicio 8 - Objetos Alamacenados ______________________________________________ 85

Diseño del Servicio 9 - Restricciones de Claves ______________________________________________ 89

Diseño del Servicio 10 - Esquema General del Conector Temporal ______________________________ 90

Diseño del Servicio 11 - Generación de identificadores _______________________________________ 91

Diseño del Servicio 12 - Modelo de DB4O _________________________________________________ 96

Diseño del Servicio 13 - Modelo de consultas S.O.D.A ________________________________________ 98

Diseño del Servicio 14 - Configuración de DB4O ____________________________________________ 99

Diseño del Servicio 15 - Conexión para acceso a la base de datos ______________________________ 100

Diseño del Servicio 16 - Generación atomica de identificadores _______________________________ 102

Diseño del Servicio 17 - Esquema general del conector persistente _____________________________ 103

Diseño del Servicio 18 - Acceso remoto a los repositorios ____________________________________ 105

Diseño del Servicio 19 - Aplicación CORBA con accesso a PSS _________________________________ 114

COMPARACIÓN CON OTRAS IMPLEMENTACIONES DE PSS

Comparaciones 1 - Tabla comparativa___________________________________________________ 129

134

Glosario Pablo M. Ilardi

135

GLOSARIO POO – Programación Orientada a Objetos u OOP Object Oriented Programming.

API – Application programming interface o interfase de programación de aplicaciones

IDE – Integrated Development Environment o entorno de desarrollo integrado.

CORBA – Common Object Request Broker Arquitecture o Arquitectura Común para el Agente de Pedidos a

Objetos.

OMG – Object Management Group o Grupo de Administración de Objetos

OMA – Object Management Architecture o Arquitectura para la Administración de Objetos

ORB – Object Request Broker o Agente de Pedidos a Objetos

Servant – entidad programada en un lenguaje, que implementa uno o más objetos CORBA. Se dice que

los servants, encarnan los objetos, porque proveen los cuerpos o implementaciones de los mismos. Los

servants, existen dentro del contexto de una aplicación servidora. Dentro de un lenguaje de

programación orientado a objetos, se trata de una instancia de un tipo de objeto.

IDL – Interface Definition Language o Lenguaje de Definición de Interfases. Lenguaje genérico de

definiciones utilizado por CORBA para permitir interconectar objetos implementados en diferentes

interfases.

PSS – Persistent State Service o Servicio de Estado Persistente, servicio de CORBA para permitir almacenar

objetos en forma persistente para los servants.

RDBMS – Relational Dabase Management System o Sistema de Administración de Base de Datos

Relacional.

SQL – Structured Query Language o Lenguaje Estructurado de Consultas utilizado para realizar consultas

en bases de datos Relacionales.

SQL3 – Extensión al ANSI-SQL también llamado SQL 1999 que introdujo conceptos de los lenguajes

orientados a objetos.

OODBMS – Object Oriented Dabase Management System o Sistemas de Administración de Bases de

Datos Orientadas a Objetos

TS – Transaction Service o Servicio de Transacciones de CORBA para realizar operaciones en contextos

trasaccionales.

PSDL – Persistent State Definition Language o Lenguaje de Definiciones de Estado Persistente

Bytecode – Código de bytes utilizado internamente por la máquina virtual de Java.

Sistema Distribuido – conjunto de computadoras independientes que se presentan al usuario del sistema

cómo una única computadora. Desde la perspectiva de hardware, las máquinas o computadoras son

autónomas, y pero desde el punto de vista del software, el sistema se ve por el usuario como un todo.

BOA – Basic Object Adapter o Adaptador Básico de Objetos, especificación descontinuada de CORBA que

permitía invocar operaciones remotas definidas en lenguaje C ubicadas en diferentes ORBs.

Glosario Pablo M. Ilardi

136

POA – Portable Object Adapter o Adaptador Portable de Objetos, especificación de CORBA que sucedió a

BOA y permitió ejecutar operaciones remotas ubicadas en diferentes ORBs e implementadas en cualquier

lenguaje.

OR - Object Reference o Referencia a Objeto, identifica un objeto CORBA, en forma unívoca. Le permite a

CORBA, identificar, ubicar y direccionar al objeto. Para los clientes, son entidades opacas que no pueden

ser modificadas ni creadas por ellos, las utilizan para dirigir los pedidos a los objetos. Identifican un único

objeto CORBA.

GIOP – General Inter ORB Protocol o Protocolo General Inter ORB, definición abstracta de un protocolo

que permitió interconectar diferentes implementaciones de la ORB.

TCP/IP – Transmission Control Protocol / Internet Protocolo, conjunto de protocolos, el primero sobre la

capa de transporte y el segundo sobre la capa de red, utilizados para comunicarse en Internet y la

mayoría de las redes comerciales.

IIOP – Internet Inter ORB Protocol o Protocolo Inter ORB sobre Internet, implementación de GIOP

mapeada sobre TCP.

IOR – Interoperable Object Reference o Referencia Interoperable a Objeto, referencia a objeto que es

entendible por todas las implementaciones de GIOP.

CCM – CORBA Component Model o Modelo de Componentes de CORBA, nueva especificación de CORBA

que promueve la construcción de sistemas mediante un modelo de componentes que extiende a las

interfases de CORBA.

RFP – Request For Proposal o Pedido de Propuesta, consiste en el llamado a entidades a participar en la

creación de una nueva especificación.

POS – Persistent Object Service o Servicio de Objetos Persistentes, especificación descontinuada de

CORBA que fue reemplazada por PSS, utilizada para permitir persistir objetos en el entorno de una ORB.

StorageObject – (PSS) Objeto almacenado, todo objeto que esté definido en PSDL se define como "objeto

almacenado".

StorageHome – (PSS) Almacén de PSDL donde se "almacenan" objetos de una familia definida por un tipo

base.

Singleton – Patrón de diseño que define a un objeto del que sólo puede existir una única instancia en

forma simultánea.

Connector – (PSS) Interfase que representa la implementación del servicio de persistencia, mediante ella

se realizan las operaciones iníciales sobre el servicio. Se obtiene una instancia invocando a un método del

registro que devuelva la implementación según el tipo de conector solicitado.

ConnectorRegistry – (PSS) "interfase/clase" que representa el registro de conectores de PSS, permite

obtener referencias a las implementaciones del servicio de persistencia (Conectores). El registro se

obtiene llamando al método resolve_initial_references(PSS) de la ORB.

CatalogBase – (PSS) Es un repositorio donde se encuentran los almacenes y objetos persistidos,

técnicamente representa lo mismo que una sesión. Esta clase es abstracta, existen dos extensiones a la

misma, que son una sesión y un pool de sesiones. Toda instancia de home, sabe a qué catalogo

pertenece.

Glosario Pablo M. Ilardi

137

TransactionalSession – (PSS) Implementación transaccional de una sesión.

SessionPool – (PSS) Implementación alternativa de CatalogBase, cuando se requiere trabajar con Pools

de conexiones.

CosPersistentState – (PSS) es el nombre del módulo (en C++ namespace) de servicio de estado

persistente.

NotFound – (PSS) es una excepción que es lanzada cuando se realiza una búsqueda para la que no se

obtiene el resultado esperado.

StorageType – (PSS) es un string que define el tipo persistido, es la puerta de entrada al conector, para

obtener referencias a almacenes.

Pid – (PSS) el pid de un objeto almacenado identifica unívocamente a un objeto dentro de un repositorio.

ShortPid – (PSS) el short pid de cualquier objeto almacenado, identifica unívocamente a cualquier objeto

dentro de un mismo almacén.

Factory Method – Patrón de diseño que permite centralizar la creación de objetos en un método en

función de los parámetros y estado del objeto que implemente el método.

JVMTI – JVM Tool Interface o Interfase de Herramientas para la Máquina Virtual, que permite configurar

agentes que podrán interceptar puntos de control utilizados para la creación de objetos y carga de las

clases.

BNF – Backus-Naur form, notación para la definición gramatical de lenguajes.

EBNF – Extended Backus-Naur form, extensión a la notación BNF que simplifica las definiciones recursivas.

BSD – Berkely Software Distribution licencia de uso de software de tipo libre.

Backtracking – Algoritmo que permite encontrar una solución a un problema evaluando todas las posibles

soluciones.

Javacc – Java Compile Compiler.

JJTree – Pre procesador de Javacc utilizado para la generación de árboles.

Builder – Patrón de diseño que permite delegar en un objeto la construcción de otro objeto encapsulando

los detalles de la construcción en el builder.

Template Method – Patrón de diseño que permite generalizar un algoritmo o comportamiento en un

método de un objeto base, para luego ser adaptado o personalizado por subclases.

Marker Interface – Patrón de diseño que permite marcar clases para definir que la misma cumple o no

con una característica.

ORM – Object Relation Mapping o Mapeo de Objeto - Relación, definición que se adapta a toda técnica

de mapeo de objetos en un modelo relacional.

JDO – Java Database Object, API de SUN para mapear un modelo de objetos en lenguaje Java a bases de

datos relacionales.

J2EE – Java2 Enterprise Edition o Edición empresarial de Java2.

Glosario Pablo M. Ilardi

138

DDL – Data Definition Language o Lenguaje de Definición de Datos utilizado en las bases de datos

Relacionales.

DB4O – Dabase For Objects o Base de Datos para Objetos

SODA – Simple Object Data Base Access o Acceso Simple a Bases de Datos de Objetos.

JacORB – Implementación en Java de la ORB del Departamento de Ciencias de la Computación de la

Universidad de Freie en Berlín, Alemania.