Ejemplo de aplicación web con Hibernate que funciona usando Netbeans IDE y JSTL (JSP(X))

4.12.09. Por ooscarr (ooscarr)

Al final de este tutorial conocerás la dinámica del desarrollo de una aplicación web manejando la persistencia de la base de datos con Hibernate y cómo NetBeans IDE te ayuda a escribir el código y consultar la base de datos. También escribirás una aplicación web JSP que haga consultas a esta base de datos a través usando XML.

Este ejemplo de Hibernate es famoso, está documentado en varias páginas, pero todas tienen el código malo y es frustrante aprender así. Así que lo corregí y lo traduje al español sacando cosas de uno y de otro mismo tutorial que encontré en ¡chino, portugués, inglés y ruso!

Soporte para Hibernate en aplicaciones web está disponible desde NetBeans IDE 6.1. Desde NetBeans IDE 6.5, también puedes hacer ingeniería inversa a las tablas de una BD a archivos de mapeo y clases Java correspondientes usando un asistente intuitivo.

Pasos previos

Antes de empezar necesitarás:

  • Instalar NetBeans IDE. Al momento de escribir esto, anda dando vueltas la versión 6.8 RC2.
  • Instalar MySQL o Java DB, o tener acceso a conectarte a una de estas bases de datos con permiso para acceder o crear la base de datos Sakila. La base de datos Sakila está disponible en estos dos tipos de bases de datos, pero Hibernate funciona con cualquier JDBC, por ejemplo PostgreSQL y Oracle.
  • Importar la base de datos de ejemplo Sakila (a la que le haremos consultas en este ejemplo).

Una vez obtenido esto, asegúrate de configurar NetBeans IDE para poder acceder a la base de datos Sakila desde el panel "Prestaciones" (Menú Ventana > Prestaciones.

Instalar el complemento de Hibernate

El plug-in, si no lo tienes ya instalado, lo obtienes

  1. yendo, en el menú, a Herramientas > Complementos.
  2. En la ficha Plugins disponibles, selecciona la casilla del módulo Hibernate y presiona Instalar.

  3. Siguiente
  4. Acepta los términos de todos los contratos de licencia y presiona el botón de Instalar.
  5. Después que se descargue y se instale, presiona Terminar.
  6. Y listo, el módulo para Hibernate (o Hibernar) se activará solo; o sino, reinicia NetBeans IDE.

Crear Proyecto nuevo

Primero: lo típico. Creamos un Proyecto nuevo de tipo Aplicación web.

Asistente de Nuevo proyecto de NetBeans, bajo la categoría Web está la opción Aplicación web.

Para nuestro ejemplo, le damos el nombre DVDStore.

Elegimos un servidor como Tomcat o Glassfish, Siguiente y marcamos la opción Hibernate y seleccionamos la base de datos jdbc:mysql://localhost:3306/sakila, en nuestro caso, de la lista que, supongo, ya habías configurado anteriormente como indiqué en los pasos previos de este tutorial.

Asistente de Nuevo proyecto de NetBeans, después de marcar la casilla que habilita Hibernate 3.2.5 abajo selecciona la base de datos Sakila instalada en localhost.
Terminar

Fíjate que en:

  • DVDStore
    • Libraries

se agregaron automáticamente las librerías necesarias para utilizar Hibernate y conectar a la base de datos.

Configurar proyecto

hibernate.cfg.xml

Abre el archivo hibernate.cfg.xml que está en la ruta:

  • DVDStore
    • Source Packages
      • <paquete predeterminado>
        • hibernate.cfg.xml

Una vez abierto en NetBeans IDE, en el modo Diseño, en la parte Propiedades opcionales, Propiedades de configuración, pon Agregar... y añade la propiedad:

Nombre de la propiedad:hibernate.show_sql
Valor de la propiedad:true

Com el asistente de diseño de XML de configuración en NetBeans IDE se puede seleccionar los valores en una lista desplegable sin tocar el código XML.

Dentro de las mismas Propiedades opcionales del modo Diseño de NetBeans IDE, más abajo, en Propiedades varias, Agrega la propiedad:

Nombre de la propiedad:hibernate.current_session_context_class
Valor de la propiedad:thread

Después de esto, si ves el archivo hibernate.cfg.xml en el modo Operador XML, quedaría un código más o menos así, pero con otras contraseñas, por supuesto:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/sakila</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.connection.password">######</property>
    <property name="hibernate.current_session_context_class">thread</property>
    <property name="hibernate.show_sql">true</property>
  </session-factory>
</hibernate-configuration>

Guarda los cambios del archivo.

Asistente de ingeniería inversa de Hibernate

Agreguemos un nuevo archivo, y seleccionemos Hibernar > Asistente de ingeniería inversa de Hibernate

Asistente para la creación de nuevo archivo en NetBeans IDE. El primer paso es seleccionar la categoría Hibernar y luego, en la columna derecha, Asistente de ingeniería inversa de Hibernate

En el siguiente paso dejamos los valores por defecto (asegurándonos que pertenezca al proyecto DVDStore). Siguiente.

Se cargarán automáticamente los nombres de las tablas de la base de datos Sakila (elegida cuando creamos el proyecto), y seleccionaremos las tablas:

  • actor
  • category
  • film
  • film_actor
  • film_category
  • language

Asistente de ingeniería inversa de Hibernate tiene 2 columnas: Tablas disponibles (con las tablas en la base de datos) y Tablas seleccionadas (con las tablas que se utilizarán en el proyecto)
Para pasar tablas de una columnas a la otra, existen los botones Agregar > y < Eliminar.

Si marcas la casilla Incluir tablas relacionadas algunas otras tablas se incluirán automáticamente. Eso está bien.

Al presionar Terminar se va a crear un archivo hibernate.reveng.xml parecido a este:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd">
<hibernate-reverse-engineering>
  <schema-selection match-catalog="sakila"/>
  <table-filter match-name="film"/>
  <table-filter match-name="language"/>
  <table-filter match-name="category"/>
  <table-filter match-name="actor"/>
  <table-filter match-name="film_category"/>
  <table-filter match-name="film_actor"/>
</hibernate-reverse-engineering>

No lo edites, cíerralo.

Archivos de mapas de Hibernate y POJOs de la base de datos

Crea un Archivo nuevo.... Categoría Hibernar, tipo Archivos de mapas de Hibernate y POJOs de la base de datos.

Asistente para la creación de nuevo archivo en NetBeans IDE. El primer paso es seleccionar la categoría Hibernar y luego, en la columna derecha, Archivos de mapas de Hibernate y POJOs de la base de datos.

Debes ponerle nombre a la clase donde estarán los mapas de Hibernate. Para que este tutorial funcione, ponle dvdrental

En el asistente de nuevo archivo de mapas de Hibernate y POJOs de la base de datos, en el paso de la Generación de código, debes seleccionar hibernate.cfg.xml como archivo de configuración de Hibernate e hibernate.reveng.xml como archivo de ingeniería inversa.

...y presiona el botón Terminar. Se deberían generar los siguientes archivos:

  • DVDStore
    • Source Packages
      • dvdrental
        • Actor.hbm.xml
        • Actor.java
        • Category.hbm.xml
        • Category.java
        • Film.hbm.xml
        • Film.java
        • FilmActor.hbm.xml
        • FilmActor.java
        • FilmActorId.java
        • FilmCategory.hbm.xml
        • FilmCategory.java
        • FilmCategoryId.java
        • Language.hbm.xml
        • Language.java

HibernateUtil.java

Lo último sería crear un Archivo Nuevo..., Categoría Hibernar, Tipo HibernateUtil.java.

Al crear el nuevo archivo HibernateUtil.java, en el asistente de nuevo archivo de NetBeans IDE, por favor fijarse en cambiar el nombre del archivo propuesto NewHibernateUtil por HibernateUtil sin el New y agregarlo al paquete dvdrental
Terminar

Empecemos

FilmHelper.java

Crea una nueva Clase Java (Archivo Nuevo..., Categoría Java, tipo Clase Java) y llámale FilmHelper y ponlo dentro del paquete dvdrental.

Creación de una nueva clase Java llamada FilmHelper en el paquete dvdrental utilizando NetBeans IDE 6.8 para Mac OS X.
Terminar

Agrega el siguiente código (en negrita) para crear una sesión de Hibernate.

package dvdrental;

public class FilmHelper {

 Session session = null;

 public FilmHelper() {
  this.session = HibernateUtil.getSessionFactory().getCurrentSession();
 }

}

Van a aparecer unos errores que se arreglan usando el menú contextual (botón secundario del ratón) y eligiendo la opción Reparar importaciones.

Menú contextual aparece cuando uno hace click sobre el código fuente.
La opción Reparar importaciones en inglés se llama Fix import.

Si dejamos el valor por defecto, quedaría el código de FilmHelper.java de la siguiente manera:

package dvdrental;

import org.hibernate.Session;

public class FilmHelper {

 Session session = null;

 public FilmHelper() {
  this.session = HibernateUtil.getSessionFactory().getCurrentSession();
 }
 
}

Consultas HQL

Probemos algo. Haz click con el botón secundario sobre el archivo Source Packages/<paquete predeterminado>/hibernate.cfg.xml y selecciona la opción Ejecutar la consulta HQL del menú contextual.

Opción Ejecutar la consulta HQL aparece en el menú contextual sólo cuando se hace click sobre el archivo hibernate.cfg.xml.

En la barra de herramientas del editor de consultas HQL, selecciona la Sesión: hibernate.cfg y escribe lo siguiente en la primera caja de texto grande:

from Film

Y presiona el botón que está al lado de la lista desplegable que debería estar mostrando hibernate.cfg como opción seleccionada para hacer la consulta y obtener un resultado como el siguiente.

El Editor de consultas HQL de NetBeans IDE tiene dos partes: arriba para escribir la consultas y abajo para mostrar los resultados o equivalente SQL.

Ahora prueba con una consulta un poquito más complicada:

from Film as film where film.filmId between 100 and 200

Con las consultas probadas, obteniendo los resultados esperados, podemos utilizarlas en la clase helper.

Hibernate

Edita el archivo FilmHelper.java y agrégale el siguiente método (marcado con negrita):

package dvdrental;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;

public class FilmHelper {

 Session session = null;

 public FilmHelper() {
  this.session = HibernateUtil.getSessionFactory().getCurrentSession();
 }
 
  // Obtiene los films donde el id de film está entre un cierto rango especificado por las variables startID y endID

 public List getFilmTitles(int startID, int endID) {
  List<Film> filmList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Film as film where film.filmId between '" + startID + "' and '" + endID + "'");
   filmList = (List<Film>) q.list();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return filmList;
 }
 
}

Repara las importaciones (Fix Import) si es necesario, utilizando las clases de hibernate.

Asistente para reparar importaciones en el código Java de NetBeans IDE. Instrucciones Import: Query org.hibernate.Query y List java.util.List.

para que quede completamente igual como está en el código de ejemplo anterior.

Vuelve a abrir el Editor de Consultas HQL y haz la siguiente query:

from Actor as actor where actor.actorId in (select filmActor.actor.actorId from FilmActor as filmActor where filmActor.film.filmId='10')

Esta consulta obtiene los actores de la película del filmid = 10.

Ahora súmale el siguiente método (marcado con negrita) al archivo FilmHelper.java:

package dvdrental;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;

public class FilmHelper {

 Session session = null;

 public FilmHelper() {
  this.session = HibernateUtil.getSessionFactory().getCurrentSession();
 }

  // Obtiene los films donde el id de film está entre un cierto rango especificado por las variables startID y endID

 public List getFilmTitles(int startID, int endID) {
  List<Film> filmList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Film as film where film.filmId between '" + startID + "' and '" + endID + "'");
   filmList = (List<Film>) q.list();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return filmList;
 }

 // Obtiene los actores en un film particular
 public List getActorsByID(int filmId) {
  List<Actor> actorList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Actor as actor where actor.actorId in (select filmActor.actor.actorId from FilmActor as filmActor where filmActor.film.filmId='" + filmId + "')");
   actorList = (List<Actor>) q.list();

  } catch (Exception e) {
   e.printStackTrace();
  }

  return actorList;
 }

}

Repara las importaciones si es necesario. Guarda.

Ahora agrega 3 métodos más y algunos métodos que inventé para hacer uso de beans en el código JSPX.


package dvdrental;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;

public class FilmHelper {

 Session session = null;

 public FilmHelper() {
  this.session = HibernateUtil.getSessionFactory().getCurrentSession();
 }

 // Obtiene los films donde el id de film está entre un cierto rango especificado por las variables startID y endID
 public List getFilmTitles(int startID, int endID) {
  List<Film> filmList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Film as film where film.filmId between '" + startID + "' and '" + endID + "'");
   filmList = (List<Film>) q.list();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return filmList;
 }

 // Obtiene los actores en un film particular
 public List getActorsByID(int filmId) {
  List<Actor> actorList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Actor as actor where actor.actorId in (select filmActor.actor.actorId from FilmActor as filmActor where filmActor.film.filmId='" + filmId + "')");
   actorList = (List<Actor>) q.list();

  } catch (Exception e) {
   e.printStackTrace();
  }

  return actorList;
 }

 // Obtiene una lista de categorías de acuerdo al filmId
 public Category getCategoryByID(int filmId) {
  List<Category> categoryList = null;
  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Category as category where category.categoryId in (select filmCat.category.categoryId from FilmCategory as filmCat where filmCat.film.filmId='" + filmId + "')");
   categoryList = (List<Category>) q.list();

  } catch (Exception e) {
   e.printStackTrace();
  }

  return categoryList.get(0);
 }

 // Obtiene un solo film de acuerdo al filmId
 public Film getFilmByID(int filmId) {

  Film film = null;

  try {
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Film as film where film.filmId=" + filmId);
   film = (Film) q.uniqueResult();
  } catch (Exception e) {
   e.printStackTrace();
  }

  return film;
 }


 // Obtiene el idioma del film de acuerdo a un langId
 public String getLangByID(int langId) {

  Language language = null;

  try{
   org.hibernate.Transaction tx = session.beginTransaction();
   Query q = session.createQuery("from Language as lang where lang.languageId=" + langId);
   language = (Language) q.uniqueResult();
  } catch (Exception e) {
   e.printStackTrace();
  }

  return language.getName();
 }



 //Métodos para no poner código java en el JSPX
 int startId;
 int endId;

 int filmId;
 int langId;

 public void setAttributeStartID(int startId){
  this.startId = startId;
 }
 public void setAttributeEndID(int endId){
  this.endId = endId;
 }
 public List getFilmTitlesList(){
  return getFilmTitles(startId,endId);
 }
 public void setAttributeFilmByID(int filmId){
  this.filmId = filmId;
 }
 public List getActorsID() {
  return getActorsByID(filmId);
 }
 public Category getCategoryID() {
  return getCategoryByID(filmId);
 }
 public Film getFilmID() {
  return getFilmByID(filmId);
 }
 public void setAttributeLangByID(int langId){
  this.langId = langId;
 }
 public String getLangID() {
  return getLangByID(langId);
 }

}

JSP(X)

Hurra. Por fin llegamos a las páginas web. A mi me gusta escribir las JSP con JSTL así que antes de seguir deberás...

Instalar la librería JSTL

  1. Entra a las propiedades del proyecto (botón secundario del ratón sobre DVDStore, opción Propiedades).

  2. En la sección Libraries hay un botón Add Library.... Presiónalo.
  3. Si no te aparece inmediatamente, Importa la librería (o biblioteca también le llaman) JSTL 1.1 o superior.
  4. Selecciona la librería JSTL y añádela al proyecto.
  5. Aceptar

Nuevo archivo JSPX

Primero elimina el archivo index.jsp del proyecto que está en la carpeta DVDStore/Web Pages/ del proyecto.

Agrega un nuevo archivo JSPX y cambia la página por defecto.

En el archivo index.jspx escribe lo siguiente.

index.jspx
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
    Document   : index
    Created on : 01-dic-2009, 21:43:16
    Author     : ooscarr
-->
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
     xmlns:c="http://java.sun.com/jsp/jstl/core"
     xmlns:fn="http://java.sun.com/jsp/jstl/functions">

 <jsp:output
  omit-xml-declaration="no"
  doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
  doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
  doctype-root-element="html"/>

 <jsp:directive.page import="dvdrental.*"/>
 <jsp:directive.page import="java.util.List"/>

 <jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/>

 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <title>DVDStore</title>
  </head>
  <body>


   <c:set var="startID" value="1"/>
   <c:set var="endID" value="10"/>
   <c:set var="prev_startID" value="1"/>
   <c:set var="prev_endID" value="10"/>
   
   <c:set var="FILM_RECORD_COUNT" value="1000"/>
   
   <c:set var="RECORD_START_PAGE" value="false"/>
   <c:set var="RECORD_END_PAGE" value="false"/>


   <c:if test="${not empty param.startid}">
    <c:set var="startID" value="${param.startid}"/>
   </c:if>
   <c:if test="${not empty param.endid}">
    <c:set var="endID" value="${param.endid}"/>
   </c:if>

   <jsp:useBean id="helper" class="dvdrental.FilmHelper"/>
   <!-- Invento para pasar filmID a FilmHelper.java -->
   <c:set target="${helper}" property="attributeStartID" value="${startID}"/>
   <c:set target="${helper}" property="attributeEndID" value="${endID}"/>
   <c:set var="filmTitles" value="${helper.filmTitlesList}"/>

   <c:if test="${startID == 1}">
    <c:set var="RECORD_START_PAGE" value="true"/>
   </c:if>
   <c:if test="${endID == FILM_RECORD_COUNT}">
    <c:set var="RECORD_END_PAGE" value="true"/>
   </c:if>

   <c:set var="prev_startID" value="${startID-10}"/>
   <c:set var="prev_endID" value="${endID-10}"/>

   <c:set var="startID" value="${endID+1}"/>
   <c:set var="endID" value="${endID+10}"/>

   <c:set var="filmTitlesSize" value="${fn:length(filmTitles)}"/>


   <table>
    <thead>

     <c:choose>
      <c:when test="${RECORD_START_PAGE}">
       <tr>
        <td class="NEXT"> </td>
        <td class="NEXT"> </td>
        <td class="NEXT"> </td>
        <td class="NEXT">
         <a class="NEXT" href="index.jspx?startid=${startID}&amp;endid=${endID}">Next</a></td>
       </tr>
      </c:when>
      <c:when test="${RECORD_END_PAGE}">
       <tr>
        <td class="NEXT"> </td>
        <td class="NEXT"> </td>
        <td class="NEXT">
         <a class="NEXT" href="index.jspx?startid=${prev_startID}&amp;endid=${prev_endID}">Prev</a></td>
        <td class="NEXT"> </td>
       </tr>
      </c:when>
      <c:otherwise>
       <tr>
        <td class="NEXT"> </td>
        <td class="NEXT"> </td>
        <td class="NEXT">
         <a class="NEXT" href="index.jspx?startid=${prev_startID}&amp;endid=${prev_endID}">Prev</a></td>
        <td class="NEXT">
         <a class="NEXT" href="index.jspx?startid=${startID}&amp;endid=${endID}">Next</a>
        </td>
       </tr>
      </c:otherwise>
     </c:choose>


     <tr><th>Title</th><th>Description</th><th> </th><th> </th></tr>
    </thead>
    <tbody>

     <c:forEach items="${filmTitles}" var="i">
      <c:set var="film" value="${i}"/>
      <c:set var="filmID" value="${film.filmId}"/>
      <tr>
       <td class="COL1">
        <a href="browse.jspx?id=${filmID}">${film.title}</a>
       </td>
       <td class="COL2">${film.description}</td>
       <td class="COL2">
        <a href="browse.jspx?id=${filmID}">More</a>
       </td>
       <td class="COL2">
        <a href="rent.jsp?id=${filmID}">Rent</a>
       </td>
      </tr>
     </c:forEach>

    </tbody>
   </table>

  </body>
 </html>
</jsp:root>

Ya lo puedes hacer correr si quieres probarlo.

Si no te gusta escribir las JSP con JSTL, puedes revisar el código oficial donde está lo mismo pero en clave Java clásico.

browse.jspx

Crea otro archivo JSP con sintaxis XML que se llame browse con el siguiente código:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
    Document   : browse
    Created on : 29-nov-2009, 19:47:16
    Author     : ooscarr
-->
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
     xmlns:c="http://java.sun.com/jsp/jstl/core">

 <jsp:output
  omit-xml-declaration="no"
  doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
  doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
  doctype-root-element="html"/>

 <jsp:directive.page import="dvdrental.*"/>
 <jsp:directive.page import="java.util.List"/>

 <jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/>

 <html xmlns="http://www.w3.org/1999/xhtml">
  <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <title>DVDStore browse</title>
  </head>
  <body>
   
   <!-- Get title ID -->
   <c:set var="filmID" value="1"/>
   <c:if test="${not empty param.id}">
    <c:set var="filmID" value="${param.id}"/>
   </c:if>

   <c:set var="startPage">false</c:set>
   <c:set var="endPage">false</c:set>

   <jsp:useBean id="helper" class="dvdrental.FilmHelper"/>

   <!-- Invento para pasar filmID a FilmHelper.java -->
   <c:set target="${helper}" property="attributeFilmByID" value="${filmID}"/>

   <jsp:useBean id="film" class="dvdrental.Film"/>
   <c:set var="film" value="${helper.filmID}"/>

   <c:set var="filmTitle" value="${film.title}"/>
   <c:set var="filmDescription" value="${film.description}"/>

   <!-- Get Actors -->
   <c:set var="actors" value="${helper.actorsID}"/>

   <!-- Get Category -->
   <c:set var="category" value="${helper.categoryID}"/>

   <c:set var="totalCast">
    <c:forEach items="${actors}" var="i">${i.firstName} ${i.lastName}, </c:forEach>
   </c:set>

   <!-- Invento para pasar langId a FilmHelper.java -->
   <c:set target="${helper}" property="attributeLangByID" value="${film.languageByLanguageId.languageId}"/>
   <!-- Obtiene el idioma usando el langId entregado anteriormente -->
   <c:set var="language" value="${helper.langID}"/>
   
   <c:set var="filmLength" value="${film.length}"/>
   <c:set var="filmRating" value="${film.rating}"/>
   <c:set var="filmYear" value="${film.releaseYear}"/>
   
   <c:set var="rentalDuration" value="${film.rentalDuration}"/>
   <c:set var="rentalRate" value="${film.rentalRate}"/>

   <c:set var="specialFeatures" value="${film.specialFeatures}"/>


   <table>
    <tr>
     <td class="RENT"> </td>
     <td class="RENT">
      <a class="RENT" href="browse.jspx?id=${filmID}">Arrendar</a>
     </td>
    </tr>
    <tr>
     <th class="TITLE">Título</th>
     <td class="TITLE">${filmTitle}</td>
    </tr>
    <tr>
     <th class="COL1">Descripción</th>
     <td class="COL2">${filmDescription}</td>
    </tr>
    <tr>
     <td class="COL1"> </td>
     <td class="COL2"> </td>
    </tr>
    <tr>
     <th class="COL1">Género</th>
     <td class="COL2">${catName}</td>
    </tr>
    <tr>
     <td class="COL1"> </td>
     <td class="COL2"> </td>
    </tr>
    <tr>
     <th class="COL1">Reparto</th>
     <td class="COL2">${totalCast}</td>
    </tr>
    <tr>
     <td class="COL1"> </td>
     <td class="COL2"> </td>
    </tr>
    <tr>
     <th class="COL1">Duración de la película</th>
     <td class="COL2">${filmLength} <abbr title="minutos">mins.</abbr></td>
    </tr>
    <tr>
     <th class="COL1">Idioma</th>
     <td class="COL2">${language}</td>
    </tr>
    <tr>
     <th class="COL1">Calificación cinematográfica</th>
     <td class="COL2">${filmRating}</td>
    </tr>
    <tr>
     <th class="COL1">Año</th>
     <td class="COL2">${filmYear}</td>
    </tr>
    <tr>
     <td class="COL1"> </td>
     <td class="COL2"> </td>
    </tr>
    <tr>
     <th class="COL1">Características especiales</th>
     <td class="SPECIAL">${specialFeatures}</td>
    </tr>
    <tr>
     <td class="COL1"> </td>
     <td class="COL2"> </td>
    </tr>
    <tr>
     <th class="COL1">Precio Arriendo</th>
     <td class="COL2">US$ ${rentalRate}</td>
    </tr>
    <tr>
     <th class="COL1">Duración de Arriendo</th>
     <td class="COL2">${rentalDuration} días</td>
    </tr>
   </table>

   </body>
 </html>
</jsp:root>

Ejecuta la aplicación en el servidor, visita la página (en mi máquina se está ejecutando en http://localhost:8084/DVDStore/) y Voilà!. Funciona o te equivocaste.

Conclusiones

A pesar de que este ejemplo sólo hace lecturas a la base de datos, me puedo imaginar que escribir debe ser igual de sencillo ya que autmáticamente se generan la clases bean para insertar valores (set).

Con la persistencia ya no me tengo que preocupar de hacer la conexión y desconexión en cada página, la contraseña está guardada de manera más segura y estandarizada, y el acceso a las tablas se hace sencillo manejándolas como clases (un editor de Java ayuda a completar el código) y no andan las consultas SQL dando vueltas por todo el código, lo que ofrece mayor abstracción entre los distintos niveles del código otorgando flexibilidad a cambios.

Si se me ocurre cambiar la base de datos (agregar una columna, modificar el nombre de una tabla o las relaciones y tipos de datos), habría que regenerar los mapas de Hibernate y volver a compilar todo.

También, si nos fijamos en todo lo que hay que saber sobre programación Java, se hace más difícil la programación, aunque supongo y reconozco que con el tiempo se debe volver más sencillo con la experiencia. Obvio.

Mejoras en el rendimiento... No sé. Supongo que al tener todas las consultas guardadas... Y con Hibernate preocupado de las conexiones y desconexiones... Me imagino que sí hay alguna mejora, lo desconozco.

En el próximo capítulo...

Esto fue cómo leer desde una base de datos con Hibernate, prometo escribir sobre cómo escribir en la base de datos usando Hibernate y hacer este mismo ejemplo usando JPA, que ya lo tengo hecho, pero hay que desarrollarlo y darle formato. Hasta el próximo mes.

Referencias

Artículos relacionados

Etiquetas: , , , ,

Exportar a formato Excel con JSP de forma bien básica (TSV)

19.8.09. Por ooscarr (ooscarr)

En este tutorial voy a mostrar la manera de generar un archivo compatible con Microsoft Excel que consiste en valores separados por espacios tabuladores muy elemental y sin gráficos o estilos.

Primero hay que partir diciendo qué opciones de formatos compatibles con Excel hay:

CSV
Es el más conocido, y consiste en un archivo de texto plano, generalmente con la extención .csv donde los valores de cada celda van separados por una coma (,) en el caso de las columnas, y un salto de línea para las filas.
TSV
TSV es una variación al anterior CSV, donde las columnas van separadas por un espacio tabulador y las filas por un salto de línea. Esto permite que se puedan introducir celdas con valores que incluyan una coma y no sean confundidas por un salto de columna, por ejemplo.
XLS
El formato Microsoft Excel tradicional que es un archivo binario para Windows donde se guardan en hojas, gráficos y macros. Mucho tiempo su especificación fue cerrada, pero desde que se comenzó a documentar el formato con ingeniería inversa y debido a las presiones de las cortes para aceptar su formato como un estándar, Microsoft se vio obligada a publicar su estructura de funcionamiento bien particular.
XLSX
Esta es la última especificación Office Open XML que Microsoft no sé cómo logró estandarizar, y que consiste en una serie de archivos XML y otros, organizados en una carpeta todo comprimido en un archivo ZIP (al igual que el estándar OpenDocument que también es soportado por Microsoft Office 2007 pero que no es de propiedad de Microsoft).

Y otros más menos importantes.

El formato

El formato que voy a exportar desde el JSP en este tutorial es el TSV, pero con extensión .xls. El archivo siguiente:

Uno Due Tre Quattro
Uno Dos Tres Cuatro
One Two
Ichi Ni San Shi
Odin Dva Tri

Se vería así en la planilla de cálculo:

ABCD
1UnoDueTreQuattro
2UnoDosTresCuatro
3OneTwo
4IchiNiSanShi
5OdinDvaTri
Más información »

Etiquetas: , , , , ,

Consumir servicio web SOAP (ShoppingAPI de eBay) desde un WSDL con JSP(X) en NetBeans

6.1.09. Por ooscarr (ooscarr)

Netbeans

Ahora haré lo mismo que hice por consola pero con una interfaz web. El consumo de un servicio web SOAP (el de ebay) a partir de un archivo WSDL

El código es casi el mismo, sólo cambia el envoltorio.

Necesitamos

En resumen,

  • Registrarnos en el sitio de desarrolladores de ebay.
  • Obtener una clave.
  • Anotar la dirección del archivo WSDL.

Más detalle de esto en el artículo anterior.

Nuevo proyecto en Netbeans

Creamos un proyecto nuevo de tipo Aplicación Web

Nueva aplicación web

Le puse de nombre Tebay.

Nombrando Tebay al proyecto

El servidor, como siempre, Glassfish o Tomcat.

Seleccioné Tomcat

Netbeans creando nuevo proyecto
Terminar

Nuevo Cliente de Servicio Web

Después agregamos un Archivo nuevo de tipo Web Service Client

Pegamos la dirección del WSDL http://developer.ebay.com/webservices/latest/ShoppingService.wsdl.

Specify the WSDL file of the Web Service
Terminar.

Protocolo

ebay ofrece sus servicios web en diferentes formatos de pedido y respuesta como se muestra en la siguiente figura.

Diferentes formatos de pedido y respuesta de la Shopping API de ebay: URL, XML, JSON, SOAP

En esta ocasión no utilizaremos SOAP para hacer los pedidos, en vez de eso, haremos las requests por medio de URL (a lo REST), pero por lo menos lo que recibiremos será SOAP.

JavaBean

Creamos una nueva clase java.

NetBeans: Archivo nuevo > Java > Clase Java

La nombro, por ejemplo, BuscaItems y la agrego a un paquete, eeh.. consumidor.

Nombrando a una nueva clase Java en NetBeans y agregándola a un nuevo paquete consumidor

BuscaItems.java

En el archivo .java escribo el código correspondiente a un bean, muy parecido al código de la clase java anterior

package consumidor;

import javax.xml.ws.BindingProvider;

public class BuscaItems {

 public String que="No definido";
 public String consulta="No definida";
 public String estado="No procesado";
 public Integer nitems=0;//Número de items
 public String url="";

 private static final String APPID = "UNAP3d995-2kj53-67u8-abcc-po988l9u035";
 private static final String CALLNAME = "FindItems";
 private static final String VERSION = "557";
 private static final String BASEURL="http://open.api.ebay.com/shopping?";

 private static BindingProvider bp;
 
 //Constructor
 public BuscaItems(){
 }
 
 public void setQue(String que){
  
  String endpointURL = BASEURL+"callname=" + CALLNAME+"&version="+VERSION+"&appid=" + APPID;

  try { // Llama Operación del Servicio Web
   ebay.apis.eblbasecomponents.Shopping service = new ebay.
    apis.eblbasecomponents.Shopping();
   ebay.apis.eblbasecomponents.ShoppingInterface port = service.
    getShopping();
   bp = (BindingProvider) port;
   bp.getRequestContext().
    put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
     endpointURL);
   // Inicializa los argumentos de la operación del SW
   ebay.apis.eblbasecomponents.
    FindItemsRequestType findItemsRequest = new ebay.
     apis.eblbasecomponents.FindItemsRequestType();
   findItemsRequest.setQueryKeywords(que);
   // Procesa los resultados
   ebay.apis.eblbasecomponents.FindItemsResponseType result = port.findItems(findItemsRequest);
   consulta=endpointURL;
   estado=result.getAck().toString();
   nitems=result.getTotalItems();
   url=result.getItemSearchURL();

  } catch (Exception ex) {
   estado=ex.getMessage();
  }
 }

 public String getConsulta(){
  return consulta;
 }
 public String getEstado(){
  return estado;
 }
 public Integer getNitems(){
  return nitems;
 }
 public String getUrl(){
  return url;
 }

}

Nuevo JSPX

Biblioteca JSTL

Porque voy a usar <c:choose>, <c:when>, <c:otherwise>, <c:if> y <c:out>, y porque lo hice en Tomcat, es necesario agregar la biblioteca JSTL.

Para eso, en las propiedades del proyecto, en la sección Librerías presionamos el botón Add Library... (Añadir biblioteca...)

Panel de propiedades del proyecto en Netbeans

Y añadimos la biblioteca JSTL.

Captura de pantalla del aistente para añadir bibliotecas de Netbeans con la biblioteca JSTL 1.1 seleccionada

index.jspx

Para hacerlo más entretenido, la JSP la escribiré en el nuevo formato de etiquetas, así que crea un nuevo archivo JSP 2.0, e ingresa lo siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
    Document   : index
    Created on : 06-ene-2009, 23:47:10
    Author     : ooscarr
-->
<jsp:root
 xmlns:jsp="http://java.sun.com/JSP/Page"
 version="2.0"
 xmlns:c="http://java.sun.com/jsp/jstl/core">
 <jsp:output
  omit-xml-declaration="no"
  doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
  doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
  doctype-root-element="html"/>
 <jsp:directive.page contentType="text/html" pageEncoding="UTF-8"/>
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <title>JSP Page</title>
 </head>
 <body>
  <div class="encabezado">
   <h1>¿Tebay?</h1>
   <p>Nombra un producto y te diré si se encuentra en el sitio de ebay.</p>
  </div>

  <!-- Formulario de consulta -->
  <h2>Consulta</h2>
  <form action="index.jspx" method="get"><p>
   <c:choose>
    <c:when test="${empty param.que}">
     <input type="text" name="que"/>
    </c:when>
    <c:otherwise>
     <input type="text" name="que" value="${param.que}"/>
    </c:otherwise>
   </c:choose>
   <button type="submit">Buscar</button>
  </p></form>
  <p>(Se obtienen mejores resultados con consultas en inglés).</p>


  <!-- Si se consultó algo -->
  <c:if test="${not empty param.que}">


   <h2>Resultados de la búsqueda "<c:out value="${param.que}" />"</h2>

   <jsp:useBean id="b" scope="page" class="consumidor.BuscaItems">
    <jsp:setProperty name="b" property="que" value="${param.que}"/>
   </jsp:useBean>
   
   <c:set var="cuantos">
    <jsp:getProperty name="b" property="nitems"/>
   </c:set>

   <c:choose>
    <c:when test="${cuantos > 0}">

     <!-- La intención de guardar esta variable es para
       procesarla con c:out -->
     <c:set var="url">
      <jsp:getProperty name="b" property="url"/>
     </c:set>

     <p>Se encontraron
      <jsp:element name="a">
       <jsp:attribute name="href">
        <!-- Al procesar con c:out los "&" se convierten en "%amp;" -->
        <c:out value="${url}"/>
       </jsp:attribute>
       <jsp:body>
        <jsp:getProperty name="b" property="nitems"/>
        resultados en ebay
       </jsp:body>
      </jsp:element>
     </p>

    </c:when>
    <c:otherwise>
     <p><jsp:element name="a">
       <jsp:attribute name="href">
        <jsp:getProperty name="b" property="consulta"/>
       </jsp:attribute>
       <jsp:body>No hubieron resultados</jsp:body>
     </jsp:element>.</p>
    </c:otherwise>
   </c:choose>


  </c:if>

  <hr /><p>Oscar Fernández</p>
 </body>
</html>
</jsp:root>

Resultado

Al presionar el botón Ejecutar (Run Main Project), después de compilar y hacer Deploy automáticamente, se debería abrir la página http://localhost:8084/Tebay/index.jspx parecida a la siguiente:

Ver página de referencia de resultado.
* La página es sólo de referencia. No funciona.

Conclusiones

Este método claramente se demora debido a que debe comunicarse con los servidores de ebay antes de generar la página de resultado, por lo que el uso de estos servicios web en servidores externos son más recomendables procesarlos en el computador del cliente por medio de técnicas como, en el caso de la web, Javascript (bueno, AJAX).

Artículos relacionados

Etiquetas: , , , ,

Consumir servicio web SOAP (ShoppingAPI de eBay) desde un WSDL con Java en NetBeans

5.1.09. Por ooscarr (ooscarr)

Netbeans

Consumir un servicio web en Java resultó más difícil de lo que pensaba. Por eso algunos servicios ofrecen SDKs para facilitar las cosas, pero aquí lo haremos a mano, consumiendo el Servicio Web SOAP que ofrece eBay desde el puro archivo WSDL (y la ayuda de NetBeans).

¿Por qué el SW de eBay? Bueno, uno, porque es uno de los pocos servicios web más famosos que ofrecen una API en SOAP (a todos les gustó REST).

De los grandes

Y dos, porque ni con Microsoft ni con Amazon pude lograr un resultado. Mi culpa. Así que sigamos con ebay.

De los más famosos que ofrecen sus servicios web por SOAP gratis, tenemos:

Primero se necesita

4 steps

Registro

Para obtener una clave para utilizar los servicios web de ebay, hay que registrarse en la página para desarolladores de ebay http://developer.ebay.com/.

Clave de desarrollador en eBay

Después de registrarnos, pedimos unas claves Production keys que son como estas:

* Estas claves son inventadas. Consiga sus propias claves.
DEVID03abb543-5t3d-87k-na95f-o234bo854
AppID:UNAP3d995-2kj53-67u8-abcc-po988l9u035
CertID:4hd73h-9385-383d-as3j-ffie34u5eee35

El archivo WSDL

En esta ocasión vamos a utilizar la Shopping API. El archivo WSDL para el SOAP de eBay, se encuentra en la parte de Documentación y es éste:

http://developer.ebay.com/webservices/latest/ShoppingService.wsdl

Nuevo proyecto en Netbeans

Ahora, creamos un proyecto nuevo de tipo Aplicación Java

Nueva aplicación Java

Le puse de nombre SOAPebay.

Nombrando al proyecto SOAPebay en Netbeans 6.5
Terminar.

Después agregamos un Archivo nuevo de tipo Web Service Client

Nuevo archivo Web Services > Web Service Client.

Pegamos la dirección del WSDL http://developer.ebay.com/webservices/latest/ShoppingService.wsdl.

Specify the WSDL file of the Web Service
Terminar.

Y después que se genera el servicio web, arrastramos y soltamos el método FindItems desde Web Services References/ShoppingService/Shopping/Shopping al main del archivo Main.java del proyecto.

Arrastrar el método y soltar en el código Java en Netbeans.

Código

Lo ideal hubiera sido que se generara todo automáticamente para llegar y reemplazar strings, pero por lo menos tenemos el esqueleto del código hecho.

ebay ofrece sus servicios web en diferentes formatos de pedido y respuesta como se muestra en la siguiente figura.

Diferentes formatos de pedido y respuesta de la Shopping API de ebay: URL, XML, JSON, SOAP

En esta ocasión no utilizaremos SOAP para hacer los pedidos, en vez de eso, haremos las requests por medio de URL (a lo REST), pero por lo menos lo que recibiremos será SOAP.

Main.java

Estructura de la URL pedida http://open.api.ebay.com/shopping?

Reemplace todo el contenido de Main.java por esto:

package soapebay;

import javax.xml.ws.BindingProvider;

public class Main {

 //Cambiar APPID
 private static final String APPID = "UNAP3d995-2kj53-67u8-abcc-po988l9u035";
 private static final String CALLNAME = "FindItems";
 private static final String VERSION = "557";
 private static final String BASEURL="http://open.api.ebay.com/shopping?";

 private static BindingProvider bp;

 public static void main(String[] args) {
  
  String endpointURL = BASEURL+"callname=" + CALLNAME+"&version="+VERSION+"&appid=" + APPID;

  try {

   ebay.apis.eblbasecomponents.Shopping service = new ebay.apis.eblbasecomponents.Shopping();
   ebay.apis.eblbasecomponents.ShoppingInterface port = service.getShopping();bp = (BindingProvider) port;
   bp = (BindingProvider) port;
   bp.getRequestContext().
    put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
     endpointURL);
   // Inicializa los argumentos
   ebay.apis.eblbasecomponents.FindItemsRequestType findItemsRequest = new ebay.apis.eblbasecomponents.FindItemsRequestType();
   findItemsRequest.setQueryKeywords("iphone");
   // procesa los resultados
   ebay.apis.eblbasecomponents.FindItemsResponseType result = port.findItems(findItemsRequest);
   System.out.println ( " Consulta : " + endpointURL);
   System.out.println("Estado = "+result.getAck());
   System.out.println("Número total de Items= "+result.getTotalItems());
   System.out.println("URL = "+result.getItemSearchURL());

  } catch (Exception ex) {
   // TODO handle custom exceptions here
  }
 }
}

Y al presionar el botón Ejecutar (Run Main Project), en la consola o Panel de Salida debería aparecer lo siguiente:

compile:
run:
 Consulta : http://open.api.ebay.com/shopping?callname=FindItems&version=557&appid=UNAP3d995-2kj53-67u8-abcc-po988l9u035
Estado = SUCCESS
Número total de Items= 64525
URL = http://search.ebay.com/ws/search/SaleSearch?fsoo=1&fsop=1&satitle=iphone
GENERACIÓN CORRECTA (tiempo total: 59 segundos)

No olvidar cambiar la clave de la API en el código fuente. Y si se va a compilar por segunda vez, se debe limpiar (Clean) antes.

Por web

También intenté hacerlo por web, donde tiene más sentido el consumo de este servicio, pero no pude terminarlo debido a mi ignorancia en JavaBeans. Help.

Referencias

Etiquetas: , , , ,

Consumir servicio web con Microsoft ASP.NET C# desde un WSDL

5.1.09. Por ooscarr (ooscarr)

Visual C#

Me pidieron que consumiera el servicio web que creé anteriormente en java pero con ASP.NET. Así que en este post mostraré cómo consumí el servicio web a partir del WSDL utilizando las herramientas oficiales de Microsoft® para Windows®.

Preparativos

Primero, obtenemos la ruta web al WSDL (en este caso, http://localhost:8080/Holaste/HolasteService?WSDL) y nos aseguramos que esté funcionando.

Nuevo sitio web

Después, abrir el Visual (en este caso, Microsoft® Visual Web Developer 2008 Express Edition, es gratis) y crear un Nuevo sitio web ASP.NET en Visual C#.

Nuevo sitio web C# en Microsoft Visual Web Developer 2008 Express Edition
Se creará un archivo Default.aspx.

Ahora vamos al menú Sitio web > Agregar referencia web..., y pegamos la dirección del WSDL.

Agregando WSDL en Microsoft Visual Web Developer 2008 Express Edition

Cuando aparezca el servicio, le ponemos un Nombre de referencia web, por ejemplo, localhost.

Poniendo nombre de referencia web al servicio en Microsoft Visual Web Developer 2008 Express Edition

De modo que quede en el Explorador de soluciones, más o menos así:

Captura de pantalla del Explorador de Soluciones del proyecto con el servicio web en App_WebREferences/localhost/HolasteService.discomap/

Código

El código, que pone un simple formulario y consume el servicio quedaría como sigue:

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<script runat="Server">
void Page_Load (object sender, EventArgs e) {
 
 // Crea el servicio
 localhost.HolasteService Servicio1 = new localhost.HolasteService();

    //Obtiene el nombre del formulario
    String nombre = Servicio1.diHola(CampoNombre.Text); 

 // Muestra el nombre, si obtiene alguno
    if (nombre == null || nombre == "Hola, ")
  Label1.Text = "[No se ingresó nombre]";
 else 
  Label1.Text = nombre;
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es-cl" lang="es-cl" dir="ltr">
<head>
 <title>Holaste</title>
 <meta http-equiv="content-type" content="application/xhtml+xml;charset=UTF-8" />
</head>
<body>
    <h1>Holaste</h1>
    <p>Este programa consume el servicio <a href="http://www.ooscarr.com/nerd/elblog/2008/12/crear-un-simple-servicio-web-en.php">Holaste</a>.</p>
    <h2>HolasteService</h2>
    <form id="form1" runat="server" method="post">
        <p><label for="CampoNombre">Nombre:</label>
            <asp:TextBox id="CampoNombre" runat="server"/></p>
        <p><button id="Submit" type="submit" runat="server">Enviar</button></p>
        
        <h3>Respuesta</h3>
        <p><strong style="text-decoration:blink;"><asp:Label id="Label1" runat="server"></asp:Label></strong></p>
    </form>
</body>
</html>

Resultado

Finalmente lo ejecutamos, y debería aparecer algo parecido a esto:

ver referencia de resultado.
* La página es sólo de referencia. No sirve.

Artículos relacionados

Etiquetas: , , ,

Crear un simple servicio web SOAP en Java con NetBeans es re-fácil

21.12.08. Por ooscarr (ooscarr)

Netbeans

A través de un video tutorial (mencionado al final de este documento), aprendí a crear un simple servicio web con NetBeans.

Según leí, Sun removió el soporte para el WSDK (aunque igual se sigue distribuyendo) para reemplazarlo por GlassFish. Así que ni modo. Pero estas instrucciones también funcionan con Tomcat.

Crear nuevo proyecto

Lo primero es crear un nuevo proyecto, así que...

  1. Creamos un Nuevo proyecto, en mi caso de tipo Aplicación Web

    Captura de pantalla del asistente para crear un Nuevo proyecto en Netbeans

  2. Elijo un nombre para la aplicación web. Yo le llamé Holaste.

    Captura de pantalla del asistente para crear una Nueva Aplicación Web

  3. En los ajustes del servidor, elijo GlassFish o Tomcat, como siempre.

    Opción GlasshFish en los ajustes de servidor del asistente para crear nuevo Proyecto de Aplicación Web en NetBeans

  4. Y Terminar

    Asistente de Nuevo Proyecto de NettBeans creando el nuevo proyecto

¿Código?

Como toda apliación web, me aparece el típico index.jsp predeterminado en el que puedo aprovechar de describir el Servicio Web (puedo poner cualquier cosa, en realidad, porque esto no es parte del servicio web).

index.jsp

<%-- 
    Document   : index
    Created on : 21-dic-2008, 12:06:37
    Author     : ooscarr
--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es-cl">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>Servicio web</title>
</head>
<body>
 <h1>Este es el Servicio web</h1>
 <p>Este servicio lo que hace es decir "Hola, [nombre]" a cambio
   de un parámetro [nombre] de tipo <code>String</code>.</p>
</body>
</html>

Creación del Servicio Web

Mmm... Bueno, en NetBeans es muy sencillo crear un Servicio Web, sólo basta seleccionar el nombre del proyecto (Holaste en mi caso) y poner Archivo Nuevo. Ahi también hay una opción llamada Web Service.

agregando un Nuevo archivo al proyecto con el asistente en NetBeans

Y le damos un nombre, por ejemplo, ServicioWeb.

Nombrando servicio web en NetBeans
Aproveché de agregarlo a un paquete llamado hola.

Añadir operaciones

Para añadir operaciones hay que sólo posicionarse sobre el Servicio Web y poner Add Operation...

Menú contextual para Servicio web en NetBeans

De otra forma, en el modo Design existe el botón Add Operation....

Botón Add Operation... en el modo Design del editor de servicios web de NetBeans.

Ahí le pongo un nombre, puedo añadir parámetros y elegir si lo quiero de tipo String, boolean, int, etc.

Añadiendo Operaciones a un Servicio web en NetBeans

ServicioWeb.java

Con eso se generará automáticamente el código esqueleto que puedo modificar para que me retorne "Hola, " + nombre;. Así:

package hola;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

/**
 *
 * @author ooscarr
 */
@WebService()
public class ServicioWeb {

 /**
  * Web service operation
  */
 @WebMethod(operationName = "diHola")
 public String diHola(@WebParam(name = "nombre")
 String nombre) {
  //TODO write your implementation code here:
  return "Hola, " + nombre;
 }

}

Probémoslo

Ya. Probémoslo. Para eso primero hay que armar el proyecto con el menú contextual del proyecto, opción Deploy.

GENERACIÓN CORRECTA (tiempo total: 7 segundos)

Y probar el servicio web, seleccionando el Servicio web, opción Test Web Service.

INFO: Dynamically creating request wrapper Class hola.jaxws.DiHola
INFO: Dynamically creating response wrapper bean Class hola.jaxws.DiHolaResponse
INFO: parsing WSDL...
INFO: generating code...
INFO: 
compiling code...
INFO: Invoking wsimport with http://localhost:8080/Holaste/ServicioWebService?WSDL
INFO: wsimport successful
INFO: parsing WSDL...
INFO: generating code...
INFO: 
compiling code...
INFO: Invoking wsimport with http://localhost:8080/Holaste/ServicioWebService?WSDL
INFO: wsimport successful

Si todo está bien instalado, se debería abrir una ventana del navegador más o menos así.

Página web, con formulario para probar las operaciones del Servicio Web, generada por NetBeans

Aquí, por ejemplo, si pongo Oscar en el formulario, se puede ver el código que entrega el servicio Web al cliente.

ver referencia.

WSDL

También es bueno hacer notar que NetBeans genera automáticamente el archivo WSDL (y su XML Schema también) para ser usado en las implementaciones de los Clientes.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.5-hudson-$BUILD_NUMBER-. -->
<definitions targetNamespace="http://hola/" name="ServicioWebService" xmlns:tns="http://hola/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<xsd:schema>
<xsd:import namespace="http://hola/" schemaLocation="ServicioWebService_schema1.xsd"/>
</xsd:schema>
</types>
<message name="diHola">
<part name="parameters" element="tns:diHola"/>
</message>
<message name="diHolaResponse">
<part name="parameters" element="tns:diHolaResponse"/>
</message>
<portType name="ServicioWeb">
<operation name="diHola">
<input message="tns:diHola"/>
<output message="tns:diHolaResponse"/>
</operation>
</portType>
<binding name="ServicioWebPortBinding" type="tns:ServicioWeb">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="diHola">
<soap:operation soapAction=""/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>
<service name="ServicioWebService">
<port name="ServicioWebPort" binding="tns:ServicioWebPortBinding">
<soap:address location="REPLACE_WITH_ACTUAL_URL"/>
</port>
</service>
</definitions>

Fuente

Todo lo que dije lo saqué de un video hecho por un desarrollador de Sun. Lo puedes ver (en inglés) desde el siguiente link, por si te quedó alguna duda.

[ACTUALIZACIÓN] Después él hizo otro video consumiendo datos desde una base de datos usando JPA (pero con menos calidad):

Ver video en youtube.com

Etiquetas: , ,

Agregar patrones a un elemento con atributos en un W3C XML Schema

26.11.08. Por ooscarr (ooscarr)

Llevo 3 días sufriendo pensando y probando todas las combinaciones posibles para agregar una restricción de patrón a un elemento con atributos. Finalmente di con la respuesta. Era mi primer XML Schema.

Explicar qué es y cómo se escribe un XML Schema da para largo y todavía no estoy muy seguro de algunos aspectos más avanzados, así que no lo voy a hacer (a menos que me sobre el tiempo).

Así que por lo que pude probar usando el excelente editor de XML Schema de netbeans (seguramente basado en algún validador de XML Schemas del W3C), sólo podía agregar restricciones a:

  • Elementos simples sin atributos
  • Elementos complejos con elementos hijos

Pero yo necesitaba agregar una restricción al contenido de un elemento con atributos a mi W3C XML Schema.

Problema

Digamos que tengo la siguiente etiqueta en mi XML:

<title type="text">A1</title>

...y necesito agregarle las siguientes restricciones de XML Schema:

<xsd:restriction base="xsd:string">
 <xsd:pattern value="[A-Z]+[0-9]+"/>
 <xsd:minLength value="2"/>
</xsd:restriction>

(Sí, se tratan de celdas de una planilla de cálculo)

Solución

Y la manera en que lo logré fue... ¡Ah! ¡Primero deja explicar que tenia un namespace llamado tns en la cabecera! (Poco relevante)

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 targetNamespace="http://www.ooscarr.com"
 xmlns:tns="http://www.ooscarr.com"
 elementFormDefault="qualified">

W3C XML Schema

Primero creé un tipo simple llamado ncelda

<xsd:simpleType name="ncelda">
 <xsd:restriction base="xsd:string">
  <xsd:pattern value="[A-Z]+[0-9]+"/>
 </xsd:restriction>
</xsd:simpleType>

Luego creé un tipo complejo llamado titulo basado en el ncelda

<xsd:complexType name="titulo">
 <xsd:simpleContent>
  <xsd:extension base="tns:ncelda">
   <xsd:attribute name="type" type="xsd:string"/>
  </xsd:extension>
 </xsd:simpleContent>
</xsd:complexType>

Y finalmente, agregué el elemento title de tipo titulo

<xsd:element name="title" type="tns:titulo"/>

Fin.

Con Netbeans

Netbeans

Para hacer lo mismo con la interfaz gráfica de netbeans; (de los 3: Source, Schema y Design) en el modo Schema, es cosa de

  1. agregar un Simple Type...
    Menú contextual Agregar un Simple Type...
  2. llamarlo ncelda
  3. con el método de derivación restriction
  4. usando la definición existente Built-in Types > string

Menú contextual Propiedades

Después ir a las propiedades del tipo creado y agregar al patrón (Pattern) la expresión regular

[A-Z]+[0-9]+

Y de ahí,

  1. Se crea el elemento complejo llamado titulo
  2. Lo personalizamos
    menú contextual, opción Personalizar
    y usamos la extensión existente ncelda
    Global Complex Type customizer
  3. Después creamos el elemento title usando el tipo existente titulo y le agregamos el atributo type
    menú contextual de agregar atributo

árbol del esquema en el modo Schema del editor de XML Schema de netbeans

0 Error(s),  0 Warning(s).

Etiquetas: , ,

Ejemplo oficial de RMI en Java

18.11.08. Por ooscarr (ooscarr)

Se me pidió implementar un ejemplo de comunicación RMI en Java para un curso de Sistemas distribuidos. Y como estaba en inglés, traduzco el ejemplo que encontré en tutorial de introducción oficial de Java™ RMI. El famoso Hello, world

Los códigos

Lamentablemente, por la licencia, debo poner todos los comentarios iniciales correspondientes a los disclaimer, así que por eso se ven largos los códigos.

Hola.java

/*
 * Copyright 2004 Sun Microsystems, Inc. All  Rights Reserved.
 *  
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met:
 * 
 * -Redistributions of source code must retain the above copyright  
 *  notice, this list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright 
 *  notice, this list of conditions and the following disclaimer in 
 *  the documentation and/or other materials provided with the 
 *  distribution.
 *  
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *  
 * You acknowledge that Software is not designed, licensed or 
 * intended for use in the design, construction, operation or 
 * maintenance of any nuclear facility.
 */
package ejemplo.hola;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hola extends Remote {
    String diHola() throws RemoteException;
}

Servidor.java

/*
 * Copyright 2004 Sun Microsystems, Inc. All  Rights Reserved.
 *  
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met:
 * 
 * -Redistributions of source code must retain the above copyright  
 *  notice, this list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright 
 *  notice, this list of conditions and the following disclaimer in 
 *  the documentation and/or other materials provided with the 
 *  distribution.
 *  
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *  
 * You acknowledge that Software is not designed, licensed or 
 * intended for use in the design, construction, operation or 
 * maintenance of any nuclear facility.
 */
package ejemplo.hola;

import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Servidor implements Hola{

 public Servidor() {}

 public String diHola(){
  return "Hola, mundo";
 }

 public static void main(String args[]){

  try {
   Servidor obj = new Servidor();
   Hola stub = (Hola) UnicastRemoteObject.exportObject(obj, 0);

   // Liga los datos (stub) del objeto remoto en el registro
   Registry registro = LocateRegistry.getRegistry();
   registro.bind("Hola",stub);

   System.err.println("Servidor listo");
  } catch (Exception e) {
   System.err.println("Excepción del servidor: " + e.toString());
   e.printStackTrace();
  }
 }
}

Cliente.java

/*
 * Copyright 2004 Sun Microsystems, Inc. All  Rights Reserved.
 *  
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met:
 * 
 * -Redistributions of source code must retain the above copyright  
 *  notice, this list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright 
 *  notice, this list of conditions and the following disclaimer in 
 *  the documentation and/or other materials provided with the 
 *  distribution.
 *  
 * Neither the name of Sun Microsystems, Inc. or the names of 
 * contributors may be used to endorse or promote products derived 
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *  
 * You acknowledge that Software is not designed, licensed or 
 * intended for use in the design, construction, operation or 
 * maintenance of any nuclear facility.
 */
package ejemplo.hola;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Cliente {
 
 private Cliente() {}
 
 public static void main(String[] args){
  
  String host = (args.length < 1) ? null : args[0];
  try {
   Registry registro = LocateRegistry.getRegistry(host);
   Hola stub = (Hola) registro.lookup("Hola");
   String respuesta = stub.diHola();
   System.out.println("respuesta: " + respuesta);
  } catch (Exception e) {
   System.err.println("Exception del cliente: " + e.toString());
   e.printStackTrace();
  }
    }
}

Cómo compilarlo y ejecutarlo

Para compilarlo, sólo hay que ejecutar el comando usual:

javac -d clases Hola.java Servidor.java Cliente.java

El directorio donde se guardarán las clases (en este ejemplo llamado clases debe existir previamente.

El árbol con los archivos debería quedar como en la siguiente imagen:

Captura de pantalla del contenido de la carpeta del proyecto: 3 archivos .java más un subdirectorio ejemplo/hola/ con 3 archivos .class en él
Repito. La carpeta clases debe existir previamente.

Para ejecutar este ejemplo, se necesita hacer lo siguiente:

  • Iniciat el registro Java RMI
  • Iniciar el servidor
  • Ejecutar el cliente

Iniciar el registro Java RMI

Para iniciar el registro, ejecuta el comando rmiregistry en el host del servidor. Este comando no produce ningún mensaje de salida (cuando es satisfactorio) y típicamente corre en background. Para más informción, vea la documentación de las herramientas de rmiregistry [paraSolaris, Windows].

Por ejemplo, en Solaris™:

rmiregistry &

En OS X con Java 5 por lo menos es igual que en Solaris pero sin el &

rmiregistry

O, en la plataforma Windows:

start rmiregistry 

Por defecto, el registro corre en el puerto TCP 1099. Para iniciar un registro en un puerto diferente, especifica el número de puerto desde la línea de comandos. Por ejemplo, para iniciar el registro en el puerto 2001 sobre la plataforma Windows:

start rmiregistry 2001

Si el registro estará ejecutándose en un puerto distinto del 1099, necesitarás especificar el número de puerto en las llamadas a LocateRegistry.getRegistry en las clases Server y Client. Por ejemplo, si el registro está ejecutándose sobre el puerto 2001 en este ejemplo, la llamada a getRegistry en el servidor sería:

Registry registry = LocateRegistry.getRegistry(2001);

Iniciar el servidor

Para iniciar el servidor, ejecuta la clase Servidor usando el comando java siguiente:

En Solaris:

java -classpath clases -Djava.rmi.server.codebase=file:clases/ ejemplo.hola.Servidor &

En OS X:

java -classpath clases -Djava.rmi.server.codebase=file:clases/ ejemplo.hola.Servidor

Bajo la plataforma Windows:

start java -classpath clases -Djava.rmi.server.codebase=file:clases/ ejemplo.hola.Servidor

donde clases es el directorio raíz del árbol de archivos de las clases (el mismo clases de cuando fueron compilados los archivos fuentes). Definiendo la propiedad de sistema java.rmi.server.codebase asegura que el registro pueda cargar la definición de interfaz remota (ojo que el slash final es importante).

La salida del servidor debería verse como esto:

Servidor listo

El servidor mantiene ejecutándose hasta que el proceso es terminado por el usuario (típicamente matando al proceso).

Ejecutar el cliente

Una vez que el servidor está listo, el cliente puede ser ejecutado de la siguiente forma:

java -classpath clases ejemplo.hola.Cliente

donde clases es el directorio raíz del árbol de archivos de las clases (el mismo clases de cuando fueron compilados los archivos fuentes).

La salida del cliente es el siguiente mensaje:

respuesta: Hola, mundo

Referencias

Etiquetas: ,

Publicidad