viernes, 12 de febrero de 2016

Transformaciones ATL de Modelos con Perfiles UML

Los Perfiles UML nos permiten extender la sintaxis y semántica de UML, permitiéndonos crear modelos extendidos que se aplican a un entorno particular, pero en muchas ocasiones es necesario elaborar transformaciones del tipo Model-To-Model o Model-To-Text para manipularlos.

En el presente artículo estudiaremos la forma de programar transformaciones ATL (Atlas Transformation Language) con modelos que usan Perfiles UML.

Para una aplicación práctica de lo estudiado, extenderemos el metamodelo Java estudiado en el artículo Atlas Transformation Language ATL incluyendo elementos conocidos como Anotaciones Java. Con esta extensión, explicaremos la forma de manejar los elementos del Perfil EJB incluidos en un modelo UML mediante la transformación denominada UML2Java.

Posteriormente modificaremos la transformación Java2Code para serializar a código fuente el modelo de clases Java que incluyen anotaciones EJB.

Perfil UML para EJB’s

Como se estudió en el artículo Creación de Perfiles UML con Papyrus el perfil EJB 3.0 de manera simplificada define estereotipos para Clases EJB, que se generalizan en clases Entity EJB, Session EJB y Message EJB, adicionalmente se definen estereotipos para interfaces Remotas, Locales y de Mensajería para representar interfaces EJB. Finalmente existe un estereotipo para empaquetar las clases e interfaces en un archivo Jar.



Perfil UML simplificado para EJB 3.0

Al estereotipo “Entity EJB”, se le añadió una propiedad o “Tag Value” que la denominamos “table”, la misma permitirá registrar el nombre de la tabla en la Base de Datos asociada al Entity Bean.

La siguiente figura ilustra el perfil EJB con el cambio indicado.


Perfil EJB, modificado.

Para descargar el Perfil EJB modificado puede hacerlo de aquí.

Metamodelo Java

Para poder representar elementos del framework EJB 3.0, es necesario añadir a las clases Java comunes elementos conocidos como “Anotaciones”, estos elementos se representan con el símbolo “@”, por ejemplo para representar un Entity Bean en el framework se utiliza la anotación @EJB, para representar un Stateless Session Bean se usa la anotación @Stateless, etc. Para tener una referencia rápida de las anotaciones EJB 3.0 puede verla acá.

Por otro lado, para elaborar una transformación Model-To-Model de un modelo conformado por el metamodelo UML más el Perfil EJB a un modelo conformado por el metamodelo Java, será necesario adicionar al metamodelo Java elementos que permitan transformar los estereotipos del Perfil EJB en una serie de anotaciones Java. Es decir un elemento del Perfil EJB será transformado a un elemento tipo anotación del metamodelo Java, además, los “Tag Values” de los elementos del Perfil EJB, serán transformados como atributos de los elementos tipo anotación.

La siguiente figura ilustra el metamodelo Java con anotaciones.


Metamodelo Java con elementos para representar Anotaciones.

Como puede observarse en el diagrama la clase “Annotation” hereda de la clase “JavaElement” y una instancia de dicha clase puede contener cero o más anotaciones (agregación “annotations”), esto significa que un elemento de la clase Java que puede ser la misma clase, un método de la clase o un atributo de la clase puede contener anotaciones.

Por otro lado la clase “AnnotationAttribute”, representa a un atributo de la anotación que tiene un determinado valor (propiedad “value”), la anotación puede contener cero o más atributos representados por la agregación “attributes”.

Para descargar el Metamodelo Java que soporta anotaciones puede hacerlo de aquí.

Transformación UML2Java

La transformación original presentada en el artículo Atlas Transformation Language ATL, será modificada de tal manera que soporte las modificaciones realizadas al Perfil EJB y al metamodelo Java, de la siguiente forma:

El Perfil EJB será incluido en la transformación como un modelo de entrada con el nombre “INPROFILE” que será conformado por el metamodelo “profile”.



module UML2Java;
create OUT : Java from IN : UML, INPROFILE : profile;

Se crearon métodos helpers que permiten realizar consultas al Perfil EJB, como por ejemplo:
  • Verificar si un elemento UML contiene un determinado estereotipo “hasStereotype”, como parámetro se ingresara el nombre calificado del estereotipo (Qualified Name).
  • Verificar si un elemento UML no contiene ningún estereotipo “hasNoStereotype”.
  • Recuperar el valor de un Tag Value de un determinado estereotipo “getTagValue”, como parámetros al mismo se ingresan el nombre del estereotipo y el nombre del “tag value”.

helper context UML!Element def: hasStereotype(name : String) : Boolean =
	not self.getAppliedStereotype(name).oclIsUndefined();

helper context UML!Element def: hasNoStereotype() : Boolean =
    if(self.getAppliedStereotypes()->isEmpty())
    then true
    else false
    endif;

helper context UML!Element def: getTagValue(stereotype: String,tagName : String) : String =
	if (self.hasStereotype(stereotype))
    then
       self.getValue(self.getAppliedStereotype(stereotype),tagName)
    else
       ''
    endif;

Se creó una regla de emparejamiento que  verifica si una clase del modelo UML contiene el estereotipo “EntityEJB”, si es así se recupera la propiedad “table”, con esa información la clase UML se transforma a una clase Java con las anotaciones “EJB” y “Table”, estas anotaciones se crean con reglas imperativas denominadas “makeAnnotation” y “makeAnnotationWithAttr”, la primera crea una clase de tipo “Annotation” solo con un nombre y la segunda crea la anotación con un atributo y su respectivo valor. Cabe señalar que a la anotación “Table” se le asocia el atributo con nombre “name” y con el valor “tableName” o nombre de la tabla.


rule C2C_EntityEJB {
	from e : UML!Class (e.hasStereotype('Profile::EJB::EntityEJB'))
	using {
		tableName : String = e.getTagValue('Profile::EJB::EntityEJB','table');
	}
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('EJB'),thisModule.makeAnnotationWithAttr('Table','name',tableName)}
	)
}

De la misma forma, se crearon reglas de emparejamiento para clases que contienen los estereotipos “StatelessEJB”, “StatefulEJB” y “MessageEJB”, de manera similar las reglas crean clases Java con las anotaciones necesarias.



rule C2C_StatelessEJB {
	from e : UML!Class (e.hasStereotype('Profile::EJB::StatelessEJB'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('Stateless')}
	)
}

rule C2C_StatefulEJB {
	from e : UML!Class (e.hasStereotype('Profile::EJB::StatefulEJB'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('Stateful')}
	)
}

rule C2C_MessageEJB {
	from e : UML!Class (e.hasStereotype('Profile::EJB::MessageEJB'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('MessageDriven')}
	)
}

Finalmente para aquellas clases sin ningún estereotipo asociado se modificó la regla de emparejamiento C2C, adicionándole la condición “hasNoSterotype”, esta regla simplemente creara una clase Java común.


rule C2C {
	from e : UML!Class(e.hasNoStereotype())
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general )
	)
}

Para las Interfaces Locales y Remotas EJB, se crearon reglas de emparejamiento que verifican si el elemento contiene o no el estereotipo indicado creando la interface Java con la anotación respectiva, de la siguiente forma:


rule I2C_Remote {
	from e : UML!Interface(e.hasStereotype('Profile::EJB::Remote Bussines Interface') and not e.hasStereotype('Profile::EJB::Local Bussines Interface'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- false,
		isInterface <- true,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('Remote')}
	)
}

rule I2C_Local {
	from e : UML!Interface(e.hasStereotype('Profile::EJB::Local Bussines Interface') and not e.hasStereotype('Profile::EJB::Remote Bussines Interface'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- false,
		isInterface <- true,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('Local')}
	)
}

rule I2C_Local_Remote {
	from e : UML!Interface(e.hasStereotype('Profile::EJB::Local Bussines Interface') and e.hasStereotype('Profile::EJB::Remote Bussines Interface'))
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- false,
		isInterface <- true,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general ),
		annotations <- Sequence{thisModule.makeAnnotation('Local'),thisModule.makeAnnotation('Remote')}
	)
}

Para aquellas interfaces sin estereotipos asociados se modificó la regla I2C, adicionándole la condición “hasNoSterotype”.


rule I2C {
	from e : UML!Interface(e.hasNoStereotype())
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- false,
		isInterface <- true,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general )
	)
}

Para descargar la transformación UML2Java.atl de manera completa puede hacerlo de aquí.

Transformación Java2Code

La transformación original presentada en el artículo Transformaciones Model-To-Text con ATL Query, se modificó de tal manera que permita serializar las clases “Annotation” y “AnnotationAttribute” del modelo Java a su código fuente respectivo, también se refino la serializacion a código de superclases e implementación de interfaces de una clase Java del modelo. Para esto se crearon los siguientes metodos Helpers:

El método Helper “annotations()”, permite serializar a código las clases “Annotation”, asociadas a un elemento Java:

helper context Java!JavaElement def : annotations() : String =
self.annotations->
iterate(ann; acc : String = '' |
    acc + '@' + ann.name +
	if ann.attributes.size()>0 then
	    '('+ann.attributes->iterate(attr;acc : String = '' | 
	           acc + 
			if acc = '' then
			''
			else
			', '
			endif +
			attr.name + '="'+attr.value+'"')+')'
	else
		''
    endif+
	'\n'
);

El método Helper “superClassesDef()”, serializa a código la extensión y/o implementación de interfaces de una clase Java:


helper context Java!JavaClass def : superClassesDef() : String =
self.superClasses->select(e|not e.isInterface)->
iterate( sc; acc : String = '' |
    acc +
    if acc = '' then
       ' extends '
    else
       ', '
    endif +
    sc.fullName()
)+
self.superClasses->select(e|e.isInterface)->
iterate( sc; acc : String = '' |
    acc +
    if acc = '' then
       ' implements '
    else
       ', '
    endif +
    sc.fullName()
);

Se modificó la implementación del método Helper “toString()” de la siguiente manera:


helper context Java!JavaClass def: toString() : String =
if self.package.oclIsUndefined() then '\n\n' else 'package ' + self.package.name + ';\n\n' endif+
self.annotations()+
self.visibility() +
self.modifierAbstract() +
if self.isInterface then ' interface ' else ' class ' endif + self.name +
self.superClassesDef()+
' {\n' +
--Fields Definition
self.fields->iterate(i; acc : String = '' |
acc + i.annotations() + i.toString()
) +
--Methods Definition
'\n'+
self.methods->iterate(i; acc : String = '' |
acc + i.annotations() + i.toString()
) +
'\n}\n\n';

Ejemplo de Modelo UML.


Como ejemplo para aplicar las transformaciones anteriormente descritas, se elaboró el siguiente diagrama UML, en el mismo se aplican los estereotipos del Profile EJB como sigue:


Nótese que a la clase “Persona”, se le aplico el estereotipo “entityEJB”, se crearon dos interfaces “PersonaServiceLocal” y “PersonaServiceRemote” a cada una se le aplico los estereotipos “local Bussines Interface” y “remote Bussines Interface” respectivamente, finalmente se creó la clase “PersonaServiceBean” a la cual se aplicó el estereotipo “stalessEJB” y se generalizo de las dos interfaces anteriores, se creó una clase “Dummy” que no tiene ningún estereotipo aplicado.

Puede descargar el modelo “PesonaEJB.uml” completo de aquí.

Configuración de las transformaciones ATL

Antes de ejecutar las transformaciones es necesario copiar el modelo UML “PersonaEJB.uml” y Perfil EJB “model.profile.uml” al proyecto de transformación ATL, tome en cuenta que el perfil debe ser copiado a una carpeta llamada “ProfileEJB”, para que el editor UML de Eclipse y  las transformaciones ATL lo reconozcan. La estructura de directorios deberá quedar como sigue:


A continuación se deberá configurar la transformación “UML2Java” como sigue:


  • URI de metamodelo UML: uri:http://www.eclipse.org/uml2/5.0.0/UML
  • URI de profile: /UML2Java/ProfileEJB/model.profile.uml
  • URI de metamodelo Java: /JavaMetamodel/model/Java.ecore
  • URI de modelo origen que contiene el metamodelo UML  IN: /UML2Java/models/PersonasEJB.uml
  • URI de modelo origen que contiene el profile INPROFILE:  /UML2Java/models/PersonasEJB.uml  (nótese que es el mismo modelo origen)
  • URI de modelo destino OUT: /UML2Java/run/PersonasEJB.xmi

Al ejecutar la transformación el resultado es el archivo “PersonaEJB.xmi” que deberá tener la siguiente estructura:


La configuración de la transformación “Java2Code”, deberá ser como sigue:


Finalmente, al ejecutar la transformación, esta deberá generar las clases Java en la carpeta “src” de la siguiente forma:



Clase Persona.java
@EJB
@Table(name="PERSONAS")
private class Persona {
       private Integer id;
       private String nombres;
       private String apellidos;
       private String direccion;
       private Date fechaNacimiento;

       public void setId(Integer id)
       {
                //TODO METHOD
       }
       public Integer getId()
       {
                //TODO METHOD
       }

}

Clase PersonaServiceBean.java :
@Stateless
private class PersonaServiceBean implements PersonaServiceLocal, PersonaServiceRemote {

       public void create(Persona persona)
       {
                //TODO METHOD
       }
       public void update(Persona persona)
       {
                //TODO METHOD
       }
       public void delete(Persona persona)
       {
                //TODO METHOD
       }

}

Interface PersonaServiceLocal.java:
@Local
private interface PersonaServiceLocal {

       public void create(Persona persona)
       {
                //TODO METHOD
       }
       public void update(Persona persona)
       {
                //TODO METHOD
       }
       public void delete(Persona persona)
       {
                //TODO METHOD
       }

}

Conclusiones


Las transformaciones entre modelos que involucran el uso de Perfiles UML es una práctica factible, solo es  necesario conocer la estructura con la que se conforman sus elementos y su uso dentro de programas de transformación ATL, se pudo constatar que con la herramienta de software libre Papyrus, se logra aplicar satisfactoriamente dichas transformaciones.