Arranque de un proyecto con Rest, JPA y Maven
De Energía y Red
Objetivos:
- Configurar un entorno de desarrollo Java con todo lo necesario para implementar servicios REST que utilicen JPA como mecanismo de persistencia.
- Indicar todos los pasos necesarios para hacer una aplicación REST partiendo desde cero.
La ventaja de desarrollar servicios web siguiendo los principios de las arquitecturas REST es que se puede acceder a la funcionalidad utilizando el lenguaje que se desee siempre que se puedan hacer peticiones HTTP y parsear respuestas XML.
El código de la aplicación se puede descargar desde aquí.
Asumiendo que se dispone de conexión a Internet, al distribuir la aplicación como un proyecto maven, bastará con ejecutar mvn install desde el directorio en el que se encuentra el archivo pom.xml para que se descarguen las librerías Java necesarias. La versión 6.7 de Netbeans permite abrir y trabajar con proyectos Maven de manera casi transparente, conviene ejecutar el mvn install antes de abrir el proyecto con el IDE.
Para arrancar un servidor con la aplicación basta descomprimir el archivo y ejecutar desde el directorio raíz de la aplicación mvn jetty:run, puede que la primera vez que se ejecute tarde un tiempo en descargarse todas las dependencias. Una vez que el comando arranque el servidor se podrá acceder a una breve descripción del servicio en la dirección http://localhost:9090/RestletJPA/.
Contenido |
Herramientas necesarias
- Netbeans 6.7. Herramienta de desarrollo.
- SQL Power*Architect. Herramienta gráfica para el diseño de modelos de datos y de generación de scripts de creación de tablas para varios tipos de bases de datos.
- Maven. Herramienta de gestión de proyectos.
- Rest-client. Aplicación que sirve para hacer pruebas sobre servicios REST.
Modelo de datos
Creación de un modelo de datos normalizado
Lo primero que hay que hacer a la hora de crear una aplicación es generar un modelo de datos normalizado con los datos de la aplicación.
Para la creación del modelo de datos se ha utilizado Power Architect. Por claridad se ha generado un modelo muy sencillo:
Power Architect permite crear scripts de generación de tablas a partir de un modelo.
Se utilizará la base de datos multiplataforma H2, para instalarla lo único que hace falta es descomprimirla en un directorio y ejecutar <ruta_instalacion_base_de_datos>/h2/bin/h2.sh
Una vez arrancado el servidor de la base de datos se puede acceder con el navegador a una consola de administración en http://localhost:8082. A través de esa consola se puede crear una nueva base de datos rellenando el formulario de acceso con los siguientes datos:
- URL de conexión 'jdbc:h2:tcp://localhost/~/javahispano/RestletJpa'
- Usuario: 'scott'
- Contraseña: 'tiger'
Si existe la base de datos se accede a la misma, si no existe, H2 la crea de forma automática. Los archivos de respaldo se encuentran en el directorio ~/javahispano/RestletJpa'.
Para crear el script de generación de tablas desde la herramienta Power Architect:
- Seleccionar desde la herramienta 'tools > Forward Engineer' y rellenar el formulario introduciendo como nombre del schema 'public' y seleccionando la base de datos para la que se desea generar el script (H2 es compatible con PostgreSQL).
El script generado se puede ejecutar desde la consola de administración de H2 abierta en el navegador.
Generación de las entidades de persistencia y los controladores de acceso a las mismas
- Para generar las entidades de persistencia se ha utilizado Netbeans.
- Como motor de persistencia se ha utilizado Eclipselink. Maven se encarga de descargar las librerías necesarias.
Pasos necesarios para generar de forma automática las entidades de un modelo de datos:
- Crear desde la pestaña de servicios la conexión a la base de datos con la que se desea trabajar.
- Crear el paquete en el que se guardarán las entidades generadas, en este caso, 'org.javahispano.aula.model'
- Pulsar con el botón derecho sobre el directorio fuente del proyecto y seleccionar 'new > Other > Persistence > Entity classes from database' y completar el asistente:
Pulsar sobre el botón Create Persistence Unit
Una vez que se dispone de las entidades se pueden generar las clases controladoras de las mismas que servirán para guardar/extraer/modificar entidades de la base de datos. Para ello se deben seguir los siguientes pasos:
- Crear el paquete en el que irán las clases, en este caso, 'org.javahispano.aula.model.controller'
- Pulsar con el botón derecho sobre el directorio fuente del proyecto, seleccionando 'new > Other > Persistence > JPA Controller Classes from Entity Classes' y completando el asistente:
Retoques JPA
Añadiendo las siguientes anotaciones el motor de persistencia genera de forma automática las claves primarias de las entidades:
@Id
@Basic(optional = false)
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
Dependiendo de la base de datos y del motor de persistencia puede que sea necesario que en la primera inserción que se haga se tenga que intoducir en el archivo persistence.xml:
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
Esa línea hace que cada vez que se ejecute la aplicación se borren y se vuelvan a crear las tablas. Esta operación es necesario hacerla al menos una vez porque se necesita crear una tabla auxiliar para la generación automática de claves. Una vez que se haya creado la tabla auxiliar se puede borrar esa línea, si no se borra se perderán los datos almacenados cada vez que se ejecute la aplicación. En el caso de la aplicación ejemplo no es necesario ya que lleva incorporada una base de datos preparada con información.
Para añadir el proveedor de persistencia para la base ded datos H2 es recomendable añadir la siguiente línea:
<property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform"/>
La clase H2Platform se puede encontrar en el código del ejemplo (ha sido sacada y retocada a partir de la que viene en la distribución de H2)
Retoques JPA para integración con JAXB
La clase Grupo.java tiene, entre otras, las siguientes anotaciones:
@Transient
@XmlAttribute(name="cursoID")
private String cursoID;
@Transient
@XmlElement(name="link")
private String link;
...
@PostLoad
public void postProcess() {
this.cursoID = cursoid.getId().toString();
link = LinkConstants.getGrupoLink(String.valueOf(id.intValue()));
}
Esas anotaciones están diciendo:
- Al motor de persistencia:
- La anotación @Transient indica que las variables cursoID y link no deben almacenarse en la base de datos.
- La anotación @PostLoad indica que una vez se haya extraido la información de la base de datos se debe ejecutar el método postProcess().
- Al compilador JAXB:
- Mapear el valor de la variable cursoID al atributo de elemento cursoID.
- Mapear el valor de la variable link al elemento link.
Creación de la capa de servicio
Para abstraer el resto de la aplicación del mecanismo de persistencia se ha creado una capa de servicio que consiste en una interfaz AulaDataService.java y una implementación de la misma AulaDataServiceImpl.java. Cualquier operación que tenga que ver con el almacenamiento de datos se hace a través de esa interfaz.
JAXB
Las representaciones están en formato XML, para pasar de Java a XML y viceversa se utiliza JAXB.
El siguiente fragmento de código muestra como pasar de objetos Java a XML y viceversa:
/**
* Método que maneja peticiones Http PUT.
* Actualiza un curso en la base de datos a partir de la representación recibida
* siguiendo los siguientes pasos:
* 1) Comprueba que el Content-Type recibido es el requerido
* 2) Extrae la representación de la petición
* 3) Realiza validación contra el schema 'curso_input.xsd'
* 4) Si todo es correcto actualiza la base de datos
* 5) Envía respuesta de éxito y una copia de la representación creada.
*
* @param entity
* @throws ResourceException
*/
@Override
public void storeRepresentation(Representation entity)
throws ResourceException {
Representation resultRepresentation = null;
try {
Curso cursoOri = dataService.findCurso(new Integer(cursoID));
if (cursoOri == null) {
getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
} else {
MediaType mediaType = entity.getMediaType();
if (CURSO_INPUT_MEDIA_TYPE.equals(mediaType.getName())) {
JaxbRepresentation<Curso> jaxbRep =
new JaxbRepresentation<Curso>(entity, Curso.class);
jaxbRep.validate(getCursoSchema(CURSO_INPUT_SCHEMA_PATH));
Curso curso = (Curso)jaxbRep.getObject();
curso.setId(cursoOri.getId());
dataService.editCurso(curso);
getResponse().setEntity(jaxbRep);
getResponse().setStatus(Status.SUCCESS_OK);
} else {
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
}
}
} catch (NumberFormatException ex) {
ErrorMessage em = new ErrorMessage(ex.toString());
getResponse().setEntity(
em.getErrorRepresentation(MediaType.APPLICATION_XML));
getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
logger.log(Level.SEVERE, ex.toString(), ex);
} catch (Exception ex) {
ErrorMessage em = new ErrorMessage(ex.toString());
getResponse().setEntity(
em.getErrorRepresentation(MediaType.APPLICATION_XML));
getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
logger.log(Level.SEVERE, ex.toString(), ex);
}
}
- Las clases utilizadas se han anotado con:
@XmlRootElement(name = "") @XmlType
- Si para el mapeo XML se necesitan crear variables adicionales en una entidad y no se desea que el motor de persistencia las guarde en la base de datos se deben marcar con la anotación @Transient.
- Se ha creado la clase ObjectFactory.java en el paquete donde se encuentran las entidades. Esta clase la utiliza JAXB para realizar la asociación/desasociación de XML a Java. Para añadir una clase al se tienen que añadir las siguientes líneas:
// Course representa el nombre de la clase que se quiere asociar/desasociar
private final static QName _course_QNAME = new QName("", "Course");
...
public Course createCourse() {
return new Course();
}
...
// Course representa el nombre de la clase que se quiere asociar/desasociar
// (tener cuidado con mayúsculas)
@XmlElementDecl(namespace = "", name = "Course")
public JAXBElement<Course> createCourse(Course value) {
return new JAXBElement<Course>(_course_QNAME, Course.class, null, value);
}
- Para obtener la representación del recurso 'cursos' se ha creado la clase Aula.java en el paquete en el que se encuentran las entidades.
- Para resolver el problema de las dependencias cíclicas se ha utilizado la anotación @XmlTransient (explicada en este artículo)
Además para que el código:
- Para mantener la claridad del ejemplo las operaciones de pasar de XML a Java se realizan sin validar las peticiones, lo apropiado es validar las representaciones recibidas con un schema.
API REST
Para la implementación de los servicios REST se ha utilizado el framework Restlet. Las librerías necesarias se encuentran especificadas en el archivo pom.xml, al ejecutar mvn install desde el directorio en el que se encuentra el archivo pom.xml Maven se encarga de descargar todas las librerías necesarias.
La clase AulaApplication.java es la encargada de asociar las rutas a los recursos, es el sitio en el que se deben configurar filtros, seguridad...
Siguiendo los principios REST se crearán 4 recursos: 'cursos', 'curso', 'grupos' y 'grupo', las clases necesarias se encuentran en el paquete org.gruposp2p.restletjpa.rest.server.resource.
Recurso 'cursos'
- URL: /cursos
- Métodos soportados:
- GET: Devuelve una representación de todos los cursos [Aula].
- POST: Permite la creación de nuevos cursos a partir de la representación recibida:
- Métodos soportados:
- El recurso recibe sus mensajes (peticiones POST) con el formato application/vnd.gruposp2p.org.cursos_input+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="aula">
<xs:complexType>
<xs:sequence>
<xs:element ref="cursos" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="curso">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="cursos">
<xs:complexType>
<xs:sequence>
<xs:element ref="curso" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
- El recurso envía sus mensajes (respuestas GET) con el formato application/vnd.gruposp2p.org.cursos_output+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="aula">
<xs:complexType>
<xs:sequence>
<xs:element ref="cursos" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="curso">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element ref="grupos" />
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="nombre" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="cursos">
<xs:complexType>
<xs:sequence>
<xs:element ref="curso" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="grupoID" type="xs:string" use="required" />
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="grupos">
<xs:complexType>
<xs:sequence>
<xs:element ref="grupo" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Recurso 'curso'
- URL: /curso/{cursoID}
- Métodos soportados:
- GET: Devuelve la representación del curso con id {cursoID}.
- PUT: Permite la modificación de los datos del curso con id {cursoID} a partir de la representación recibida.
- DELETE: Permite el borrado del recurso con id {cursoID}
- Métodos soportados:
- Para la recepción de mensajes (método PUT) soporta el formato application/vnd.gruposp2p.org.curso_input+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="curso" type="curso"/>
<xs:complexType name="curso">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="nombre" type="xs:string"/>
</xs:complexType>
</xs:schema>
- El recurso envía sus mensajes (respuestas GET) con el formato application/vnd.gruposp2p.org.curso_output+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="curso">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element ref="grupos" />
</xs:choice>
<xs:attribute name="nombre" type="xs:string" use="required" />
<xs:attribute name="id" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="grupoID" type="xs:string" use="required" />
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="grupos">
<xs:complexType>
<xs:sequence>
<xs:element ref="grupo" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Recurso 'grupos'
- URL: /grupos
- Métodos soportados:
- GET: Devuelve una representación de todos los grupos.
- POST: Permite la creación de nuevos grupos a partir de la representación recibida.
- Métodos soportados:
- El recurso recibe sus mensajes (peticiones POST) con el formato application/vnd.gruposp2p.org.grupos_input+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="grupos">
<xs:complexType>
<xs:sequence>
<xs:element ref="grupo" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
- El recurso envía sus mensajes (respuestas GET) con el formato application/vnd.gruposp2p.org.grupos_output+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="grupoID" type="xs:string" use="required" />
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="grupos">
<xs:complexType>
<xs:sequence>
<xs:element ref="grupo" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Recurso 'grupo'
- URL: /grupo/{grupoID}
- Métodos soportados:
- GET: Devuelve la representación del grupo con id {grupoID} a partir de la representación recibida.
- PUT: Permite la modificación de los datos del grupo con id {grupoID}.
- DELETE: Permite el borrado del recurso con id {cursoID}
- Métodos soportados:
- Para la recepción (método PUT) y envío (método GET) de mensajes soporta el formato application/vnd.gruposp2p.org.grupo_input+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
- Para el envío (método GET) de mensajes soporta el formato application/vnd.gruposp2p.org.grupo_output+xml, un XML que debe cumplir con el schema:
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="grupo">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="link" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="descripcion" type="xs:string" minOccurs="1" maxOccurs="1"/>
</xs:choice>
<xs:attribute name="grupoID" type="xs:string" use="required" />
<xs:attribute name="cursoID" type="xs:string" use="required" />
<xs:attribute name="nombre" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
</xs:schema>
Ejecución de la aplicación
Como aplicación Restlet
La aplicación se puede arrancar ejecutando la clase AulaServer.java, Restlet lleva incorporado su propio servidor, que se puede arrancar de la siguiente forma:
public class AulaServer extends Component {
public static void main(String[] args) throws Exception {
AulaServer aulawebServer = new AulaServer();
aulawebServer.getServers().add(Protocol.HTTP, 8888);
aulawebServer.start();
}
public AulaServer() {
getDefaultHost().attach(new AulaApplication());
}
}
Como aplicación web
Para integrar la aplicación en un servidor web hay que editar el archivo web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>RestletJPA</display-name>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<context-param>
<param-name>org.restlet.application</param-name>
<param-value>
org.gruposp2p.restletjpa.rest.server.AulaApplication
</param-value>
</context-param>
<!-- Restlet adapter -->
<servlet>
<servlet-name>RestletServlet</servlet-name>
<servlet-class>
org.restlet.ext.servlet.ServerServlet
</servlet-class>
</servlet>
<!-- Catch all requests -->
<servlet-mapping>
<servlet-name>RestletServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Apuntes
- Aunque sólo sea por la gestión de dependencias hay que saber usar Maven, facilita mucho la distribución de proyectos.
- Netbeans 6.7 permite trabajar con proyectos Maven.
- El ejemplo lleva incorporada una base de datos ya inicializada con datos. Para poder usarlos la aplicación sigue los siguientes pasos (el proceso es transparente al usuario):
- Descomprime el archivo 'cursos_db.zip' (una copia de los archivos de respaldo de la base de datos) en un directorio local.
- Arranca una instancia de base de datos en memoria con los datos extraidos de los archivos de respaldo.
- Las mismas clases empleadas como entidades JPA, que han sido generadas de forma automática a partir de una base de datos, se retocan con anotaciones JAXB para facilitar el paso de XML a Java y viceversa.
- Las clases auxiliares creadas para trabajar con el mapeo XML<->Java (Aula.java y Grupos.java) se han creado en el mismo paquete que las entidades.
- Para la creación de servicios REST se pueden hacer pruebas con el navegador y con la herramienta RestClient (preferiblemente con RestClient), una vez que los servicios alcancen un grado de estabilidad ya se puede empezar a pensar en la implementación de las interfaces de usuario.
- Las aplicaciones basadas en servicios REST facilitan la integración de sistemas distribuidos. Si los organismos públicos implementasen los servicios prestados con arquitecturas REST se facilitaría mucho la integración entre administraciones.
- Se dice que una herramienta o solución es buena cuando con ella se pueden hacer cosas que el creador de la misma no ha sido capaz de imaginar, con REST se ponen los servicios para que cualquiera que cumpla los requisitos de uso pueda usarlos como mejor le convenga.
Cosas pendientes
- Mecanismo de paginación a la hora de obtener la representación de todos los recursos, por ejemplo /grupos?comienzo=1&numero=n
- Añadir mecanismos de autorización de acceso a recursos. Restlet proporciona Guards.
- Crear mecanismo de filtros para grabar todos los accesos. Restlet proporciona Filters.
- Validar los XMLs de las representaciones enviadas y recibidas con los schemas correspondientes.
- Artículo en el que se explique como crear una interfaz gráfica de usuario acceso con GWT* .
...


