6. Versión 2: Arquitectura OpenEJB / JPA
6.1. Introducción a los principios de la migración
A continuación presentamos los principios que regirán la migración de una aplicación JPA / Spring / Hibernate a una aplicación JPA / OpenEJB / EclipseLink. Esperaremos hasta el apartado 6.2 para crear los proyectos Maven.
6.1.1. Las dos arquitecturas
La implementación actual con Spring / Hibernate
![]() |
La implementación que se va a crear con OpenEJB / EclipseLink
![]() |
6.1.2. Las bibliotecas de los proyectos
- Las capas [DAO] y [metier] ya no son instanciadas por Spring, sino por el contenedor OpenEJB.
- Las bibliotecas del contenedor Spring y su configuración desaparecen y se sustituyen por las bibliotecas del contenedor OpenEJB y su configuración.
- Las bibliotecas de la capa JPA / Hibernate se sustituyen por las de la capa JPA / EclipseLink
6.1.3. Configuración de la capa JPA / EclipseLink / OpenEJB
- El archivo [META-INF/persistence.xml], que configura la capa JPA, queda de la siguiente manera:
- línea 3: las transacciones en un contenedor EJB son de tipo JTA (transacción Java API). Con Spring eran de tipo RESOURCE_LOCAL.
- línea 9: la implementación JPA utilizada es EclipseLink
- líneas 5-7: las entidades gestionadas por la capa JPA
- líneas 11-13: propiedades del proveedor EclipseLink
- línea 12: en cada ejecución, se crearán las tablas
Las características JDBC de la fuente de datos JTA utilizada por el contenedor OpenEJB se especificarán mediante el siguiente archivo de configuración [conf/openejb.conf]:
- línea 3: se utiliza el identificador «Default JDBC Database» cuando se trabaja con un contenedor OpenEJB integrado (embedded) en la propia aplicación.
- línea 5: utilizamos una base de datos MySQL [dbpam_eclipselink]
6.1.4. Implementación de la capa [DAO] mediante EJB
- Las clases que implementan la capa [DAO] pasan a ser EJB. Tomemos como ejemplo la clase [CotisationDao]:
La interfaz [ICotisationDao] en la versión Spring era la siguiente:
La clase EJB implementará esta misma interfaz de dos formas diferentes: una local y otra remota. La interfaz local puede ser utilizada por un cliente que se ejecute en el mismo JVM, mientras que la interfaz remota puede ser utilizada por un cliente que se ejecute en otro JVM.
La interfaz local:
- línea 6: la interfaz [ICotisationDaoLocal] hereda de la interfaz [ICotisationDao] para adoptar todos sus métodos. No añade ninguno nuevo.
- línea 5: la anotación @Local la convierte en una interfaz local para la interfaz EJB, que la implementará.
La interfaz remota:
- línea 6: la interfaz [ICotisationDaoRemote] hereda de la interfaz [ICotisationDao] para adoptar todos sus métodos. No añade ninguno nuevo.
- línea 5: la anotación @Remote la convierte en una interfaz remota para EJB, que la implementará.
La capa [DAO] se implementa mediante un EJB que implementa ambas interfaces (aunque no es obligatorio):
- línea 1: la anotación @Stateless, que convierte la clase en un EJB
- línea 2: la anotación @TransactionAttribute, que hace que cada método de la clase se ejecute dentro de una transacción.
- línea 5: la anotación @PersistenceContext, que inyecta en la clase [CotisationDao] el EntityManager de la capa JPA. Es idéntica a la que teníamos en la versión de Spring.
Cuando se utiliza la interfaz local de la capa [DAO], el cliente de dicha interfaz se ejecuta en la misma JVM.
![]() |
En el ejemplo anterior, las capas [metier] y [DAO] intercambian objetos por referencia. Cuando una capa modifica el objeto compartido, la otra capa detecta dicho cambio.
Cuando se utiliza la interfaz remota de la capa [DAO], el cliente de dicha interfaz suele ejecutarse en otra JVM.
![]() |
En el ejemplo anterior, las capas [metier] y [DAO] intercambian objetos por valor (serialización del objeto intercambiado). Cuando una capa modifica un objeto compartido, la otra capa solo detecta este cambio si se le devuelve el objeto modificado.
6.1.5. Implementación de la capa [metier] mediante una EJB
- La clase que implementa la capa [metier] se convierte también en un EJB que implementa una interfaz local y remota. La interfaz inicial [IMetier] era la siguiente:
Se crean una interfaz local y una interfaz remota a partir de la interfaz anterior:
La clase EJB de la capa [metier] implementa estas dos interfaces:
- líneas 1-2: definen un EJB cuyos métodos se ejecutan en una transacción.
- línea 7: una referencia a la interfaz local de EJB [CotisationDao].
- línea 6: la anotación @EJB indica que el contenedor EJB debe inyectar una referencia a la interfaz local de EJB [CotisationDao].
- Líneas 8-11: se repite el mismo proceso para las interfaces locales de EJB, [EmployeDao] y [IndemniteDao].
Al final, cuando se instancie el EJB [Metier], los campos de las líneas 7, 9 y 11 se inicializarán con referencias a las interfaces locales de los tres EJB de la capa [DAO]. Por lo tanto, aquí se parte de la hipótesis de que las capas [metier] y [DAO] se ejecutarán en la misma JVM.
![]() |
6.1.6. Los clientes de EJB
![]() |
En el esquema anterior, para comunicarse con la capa [metier], la capa [ui] debe obtener una referencia a la interfaz remota de la capa EJB desde la capa [metier].
![]() |
En el esquema anterior, para comunicarse con la capa [metier], la capa [ui] debe obtener una referencia a la interfaz local de la capa EJB de la capa [metier]. El método para obtener estas referencias varía de un contenedor a otro. En el caso del contenedor OpenEJB, se puede proceder de la siguiente manera:
Referencia en la interfaz local:
- líneas 2-5: se inicializa el contenedor OpenEJB.
- línea 5: se dispone de un contexto JNDI (Java Naming and Directory Interface) que permite obtener referencias sobre los EJB. Cada EJB se designa con un nombre JNDI:
- (continuación)
- para la interfaz local, se añade «Local» al nombre del EJB (líneas 7-9)
- para la interfaz remota, se añade «Remote» al nombre del EJB
Con Java EE 5, estas reglas varían en función del contenedor EJB. Esto supone una dificultad. Java EE 6 introdujo una notación JNDI compatible con todos los servidores de aplicaciones.
El código anterior obtiene referencias a las interfaces locales de EJB a través de sus nombres JNDI. Anteriormente hemos mencionado que estas también se pueden obtener mediante la anotación @EJB. Por lo tanto, podríamos querer escribir:
La anotación @EJB solo se respeta si pertenece a una clase cargada por el contenedor EJB. Este será el caso, por ejemplo, de la clase [Metier]. El código anterior, por su parte, pertenecerá a una clase de consola que no será cargada por el contenedor EJB. Por lo tanto, nos vemos obligados a utilizar los nombres JNDI o EJB.
A continuación, el código para obtener una referencia a la interfaz remota de EJB y [Metier]:
6.2. Trabajo práctico
Nos proponemos migrar la aplicación NetBeans Spring/Hibernate a una arquitectura OpenEJB / EclipseLink.
La implementación actual con Spring / Hibernate
![]() |
La implementación que se va a desarrollar con OpenEJB / EclipseLink
![]() |
6.2.1. Configuración de la base de datos [dbpam_eclipselink]
Si no existe, crea la base de datos MySQL [dbpam_eclipselink]. Si existe, elimina todas sus tablas. Crea una conexión de NetBeans a esta base de datos tal y como se describe en el apartado 6.2.1.
6.2.2. Configuración inicial del proyecto de NetBeans
- Cargar el proyecto Maven [mv-pam-spring-hibernate]
- Crea un nuevo proyecto Maven Java [mv-pam-openejb-eclipselink] [1]
![]() |
- En la pestaña [Files] [2], crear una carpeta [conf] [3] en la raíz del proyecto
- colocar en esta carpeta el siguiente archivo [openejb.conf] [4]:
![]() |
- Crea la carpeta [src / main/ resources/ META-INF] [5]
- colocar en ella el archivo [persistence.xml] [6] siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- el proveedor JPA es EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propiedades del proveedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- línea 12: se solicitan registros detallados a EclipseLink,
- línea 13: las tablas se crearán al instanciar la capa JPA,
- Añadir las bibliotecas OpenEJB, EclipseLink y el controlador JDBC de MySQL al archivo [pom.xml] del proyecto:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-openejb-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-openejb-eclipselink</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
<repositories>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
</project>
- líneas 18-22: la dependencia OpenEJB,
- líneas 30-39: las dependencias EclipseLink,
- líneas 41-44: la dependencia del controlador JDBC de MySQL
6.2.3. Portabilidad de la capa [DAO]
Vamos a realizar la migración de la capa [DAO] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
- Copiar los paquetes [dao, exception, jpa]
![]() |
Los errores señalados anteriormente se deben a que la capa [DAO] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto.
6.2.3.1. El EJB [CotisationDao]
Creamos las interfaces local y remota del futuro EJB [CotisationDao]:
La interfaz local ICotisationDaoLocal:
Para obtener los paquetes correctos import, ejecute [clic droit sur le code / Fix Imports].
La interfaz remota ICotisationDaoRemote:
A continuación, modificamos la clase [CotisationDao] para convertirla en una EJB:
El import que generaba esta clase en el framework Spring desaparece. Generar un [Clean and Build] del proyecto:
![]() |
En el [1], ya no hay errores en la clase [CotisationDao].
6.2.3.2. Los EJB, [EmployeDao] y [IndemniteDao]
Se repite el mismo procedimiento para los demás elementos de la capa [DAO]:
- las interfaces IEmployeDaoLocal y IEmployeDaoRemote, derivadas de IEmployeDao
- EJB y EmployeDao, que implementan estas dos interfaces
- interfaces IIndemniteDaoLocal y IIndemniteDaoRemote, derivadas de IIndemniteDao
- EJB y IndemniteDao, que implementan estas dos interfaces
Una vez hecho esto, ya no hay errores en el proyecto [2].
6.2.3.3. La clase [PamException]
La clase [PamException] sigue siendo la misma, salvo por un pequeño detalle:
Se ha añadido la línea 5. Para obtener los import correctos, hay que crear [Fix imports].
Para entender la anotación de la línea 5, hay que recordar que cada método de EJB de nuestra capa [DAO]:
- se ejecuta en una transacción iniciada y finalizada por el contenedor EJB
- lanza una excepción de tipo [PamException] en cuanto surge algún problema
![]() |
Cuando la capa [metier] llama a un método M de la capa [DAO], esta llamada es interceptada por el contenedor EJB. Todo ocurre como si hubiera una clase intermedia entre la capa [metier] y la capa [DAO], denominada aquí [Proxy EJB], que intercepta todas las llamadas a la capa [DAO]. Cuando se intercepta la llamada al método M de la capa [DAO], el proxy EJB inicia una transacción y, a continuación, cede el control al método M de la capa [DAO], que se ejecuta entonces dentro de dicha transacción. El método M finaliza con o sin excepción.
- Si el método M finaliza sin excepción, la ejecución vuelve al proxy EJB, que finaliza la transacción validándola mediante un commit. A continuación, el flujo de ejecución se devuelve al método llamante de la capa [metier]
- Si el método M finaliza con una excepción, la ejecución vuelve al proxy EJB, que finaliza la transacción invalidándola mediante un rollback. Además, encapsula esta excepción en un tipo EJBException. A continuación, el flujo de ejecución se devuelve al método llamante de la capa [metier], que recibe, por tanto, un EJBException. La anotación de la línea 5 anterior impide esta encapsulación. Por lo tanto, la capa [metier] recibirá un PamException. Además, el atributo rollback=true indica al proxy EJB que, cuando reciba un PamException, debe invalidar la transacción.
6.2.3.4. Prueba de la capa [DAO]
Nuestra capa [DAO], implementada mediante EJB, puede someterse a pruebas. Empezamos copiando el paquete [dao] de [Test Packages], del proyecto [mv-pam-springhibernate], al proyecto en desarrollo [1]:
![]() |
Solo conservamos la prueba [JUnitInitDB], que inicializa la base de datos con algunos datos de [2]. Renombramos la clase [ JUnitInitDbLocal] como [3]. La clase [JUnitInitDBLocal] utilizará la interfaz local de EJB de la capa [DAO].
En primer lugar, modificamos la clase [JUnitInitDBLocal] de la siguiente manera:
- líneas 3-5: referencias a las interfaces locales de EJB de la capa [DAO]
- línea 7: @BeforeClass anota el método ejecutado al iniciar la prueba JUnit
- líneas 10-13: inicialización del contenedor OpenEJB. Esta inicialización es específica y varía con cada contenedor EJB.
- línea 13: se dispone de un contexto JNDI (Java Naming and Directory Interface) que permite acceder a los EJB mediante nombres. Con OpenEJB, la interfaz local de un EJB E se designa como ELocal y la interfaz remota como ERemote.
- Líneas 15-17: se solicita al contexto JNDI una referencia a las interfaces locales de los EJB y [EmployeDao, CotisationDao, IndemniteDao].
![]() |
Se compila el proyecto (Build), se inicia el servidor MySQL si es necesario y se ejecuta la prueba JUnitInitDBLocal. Cabe recordar que el archivo [persistence.xml] se ha configurado para recrear las tablas en cada ejecución. Antes de ejecutar la prueba, es recomendable eliminar las posibles tablas de las bases de datos MySQL y [dbpam_eclipselink].
![]() |
- en [1], en la pestaña [Services], se eliminan las tablas de la conexión de NetBeans establecida en el apartado 6.2.1.
- En [2], la base de datos [dbpam_eclipselink] ya no tiene tablas
- En [3], se compila el proyecto
- en [4], se ejecuta la prueba JUnitInitDBLocal
![]() |
- en [5], la prueba se ha superado
- en [6], se actualiza la conexión de NetBeans
- en [7], se ven las 4 tablas creadas por la capa JPA. El objetivo de la prueba era rellenarlas. Se visualiza el contenido de una de ellas
![]() |
- en [8], el contenido de la tabla [EMPLOYES]
El contenedor OpenEJB ha mostrado registros en la consola:
- líneas 2-3: los dos nombres JNDI de EJB y [CotisationDaoLocal],
- líneas 4-5: los dos nombres JNDI de EJB y [CotisationDaoRemote],
- líneas 7-8: los dos nombres JNDI de EJB y [EmployeDaoLocal],
- líneas 9-10: los dos nombres JNDI de EJB y [EmployeDaoRemote],
- líneas 12-13: los dos nombres JNDI de EJB y [IndemniteDaoLocal],
- líneas 14-15: los dos nombres JNDI de EJB y [EmployeDaoRemote].
Repetimos la misma prueba, utilizando esta vez la interfaz remota de EJB.
![]() |
En [1], la clase [JUnitInitDBLocal] se ha duplicado (copiar/pegar) en [JUnitInitDBRemote]. En esta clase, sustituimos las interfaces locales por las remotas:
Una vez hecho esto, se puede ejecutar la nueva clase de prueba. Antes de ello, con la conexión de NetBeans [dbpam_eclipselink], elimina las tablas de la base de datos [dbpam_eclipselink].
![]() |
Con la conexión de NetBeans [dbpam_eclipselink], comprueba que la base de datos se ha rellenado.
6.2.4. Migración de la capa [metier]
Vamos a realizar la migración de la capa [metier] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
![]() |
Los errores señalados anteriormente ([1]) se deben a que la capa [metier] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto.
6.2.4.1. El EJB [Metier]
Seguimos el mismo procedimiento que el descrito para EJB y [CotisationDao]. En primer lugar, creamos en [2] las interfaces local y remota del futuro EJB [Metier]. Ambas derivan de la interfaz inicial [IMetier].
Una vez hecho esto, en [3] modificamos la clase [Metier] para que se convierta en una EJB:
- línea 1: la anotación @Stateless convierte la clase en una EJB
- línea 2: cada método de la clase se ejecutará en una transacción
- línea 3: el EJB [Metier] implementa las dos interfaces, la local y la remota, que acabamos de definir
- línea 7: EJB [Metier] utilizará EJB [CotisationDao] a través de la interfaz local de este último. Esto significa que las capas [metier] y [DAO] deben ejecutarse en el mismo JVM.
- línea 6: la anotación @EJB hace que el contenedor EJB inyecte él mismo la referencia en la interfaz local de EJB [CotisationDao]. La otra forma que hemos encontrado consiste en utilizar un contexto JNDI.
- Líneas 8-11: se utiliza el mismo mecanismo para los otros dos EJB de la capa [DAO].
6.2.4.2. Prueba de la capa [metier]
Ya podemos probar nuestra capa [metier], implementada mediante un EJB. Comenzamos copiando el paquete [metier] de [Test Packages] del proyecto [mv-pam-spring-hibernate] al proyecto en construcción [1]:
![]() |
- en [1], el resultado de la copia
- en [2], se elimina la primera prueba
- en [3], la prueba restante pasa a llamarse [JUnitMetierLocal]
La clase [JUnitMetierLocal] queda así:
- línea 4: una referencia a la interfaz local de EJB [Metier]
- líneas 8-12: configuración del contenedor OpenEJB idéntica a la realizada en la prueba de la capa [DAO]
- líneas 15-19: se solicitan al contexto JNDI de la línea 12, referencias a los tres EJB de la capa [DAO] y al EJB de la capa [metier]. Los EJB de la capa [DAO] servirán para inicializar la base de datos, y el EJB de la capa [metier] para realizar pruebas de cálculo de salarios.
La ejecución de la prueba [JUnitMetierLocal] da el siguiente resultado [1]:
![]() |
En [2], se duplica [JUnitMetierLocal] en [JUnitMetierRemote] para probar, en esta ocasión, la interfaz remota de EJB y [Metier]. Se modifica el código de [JUnitMetierRemote] para utilizar esta interfaz remota. El resto no cambia.
- líneas 4 y 19: se utiliza la interfaz remota de EJB [Metier].
- líneas 15-17: se utilizan las interfaces remotas de la capa [DAO]
- líneas 34-35: dado que, con las interfaces remotas, los objetos intercambiados entre el cliente y el servidor se pasan por valor, es necesario recuperar el resultado devuelto por el método create(Indemnite i). Esto no era obligatorio con las interfaces locales, en las que los objetos se pasan por referencia.
Una vez hecho esto, se puede compilar el proyecto y ejecutar la prueba [JUnitMetierRemote]:
![]() |
6.2.5. Portabilidad de la capa [console]
Vamos a realizar la migración de la capa [console] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
![]() |
Los errores señalados anteriormente ([1]) se deben a que la capa [metier] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto. En [2], la clase [Main] pasa a llamarse [MainLocal]. Utilizará la interfaz local de EJB [Metier].
El código de la clase [MainLocal] evoluciona de la siguiente manera:
Los cambios se encuentran en las líneas 13-25. Se trata de la forma de hacer referencia a la capa [metier], que cambia (líneas 17-22). No explicamos el nuevo código, ya que ya se ha visto en ejemplos anteriores. Una vez realizadas estas modificaciones, el proyecto ya no presenta errores (véase [3]).
Configuramos el proyecto para que se ejecute con los argumentos [1]:
![]() |
Para que la aplicación de consola se ejecute con normalidad, es necesario que haya datos en la base de datos. Para ello, hay que modificar el archivo [META-INF/persistence.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- el proveedor JPA es EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propiedades del proveedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<!--
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
</properties>
</persistence-unit>
</persistence>
La línea 14, que provocaba que las tablas de la base de datos se volvieran a crear en cada ejecución, se ha comentado. Es necesario recompilar el proyecto (Clean and Build) para que se aplique este cambio. Una vez hecho esto, se puede ejecutar el programa. Si todo va bien, aparecerá en la consola un mensaje similar al siguiente:
Aquí hemos utilizado la interfaz local de la capa [metier]. Ahora utilizamos su interfaz remota en una segunda clase de consola:
![]() |
En [1], la clase [MainLocal] se ha duplicado en [MainRemote]. Se modifica el código de [MainRemote] para utilizar la interfaz remota de la capa [metier]:
Se han realizado modificaciones en las líneas 2 y 8. Se ha configurado el proyecto [2] para ejecutar la clase [MainRemote]. Su ejecución ofrece los mismos resultados que anteriormente.
6.3. Conclusion
Hemos mostrado cómo migrar una arquitectura Spring/Hibernate a una arquitectura OpenEJB/EclipseLink.
La arquitectura Spring/Hibernate
![]() |
La arquitectura OpenEJB / EclipseLink
![]() |
La migración se pudo llevar a cabo sin demasiadas dificultades porque la aplicación inicial se había estructurado en capas. Es importante comprender este punto.




























