Manual de Introducción a desarrollo con PrimeFaces
Manual de Introducción a desarrollo con PrimeFaces magm 23 Marzo, 2013 - 13:13PrimeFaces es una suite open source de componentes que extiende a JSF
- Posee un gran set de componentes UI (HtmlEditor, Dialog, AutoComplete, Charts, DatePicker, DataTables, TreeComponent, etc).
- Tecnología Ajax basada an la API Ajax JSF 2.0
- Muy liviana
- Solo un archivo jar
- No se requiere configuración
- No tiene dependencias (salvo por supuesto JSF runtime 2.0 o 2.1)
- Soporta Ajax Push via websockets
- Mobile UI kit que permite la creación de aplicaciones web para dispositivos mobiles
- Soporta Skinning y posee más de 35 temas preconstruídos. Soporta el editor visial de temas.
- Documentación mantenida por la comunidad muy actualizada.
- Muchos más
Se puede descargar la documentación en formato pdf desde aquí.
Se puede ver una demo llamada showcase que abarca todos los componentes de la UI y mobile desde aquí, además showcase permite ver el código fuente necesario para cada uno de las demostraciones.
Que haremos?
Vamos a crear un proyecto web dinámico utilizando Eclipse 4.3 Kepler con WTP en el cual habilitaremos JSF y PrimeFaces. El proyecto no es muy pretencioso, solo veremos como implementar algunos componentes para que quede la idea de la facilidad con la que se trabaja utilizando esta suite.
Primeros pasos con PrimeFaces, Eclipse y Tomcat (Paso a paso)
Primeros pasos con PrimeFaces, Eclipse y Tomcat (Paso a paso) magm 13 Febrero, 2013 - 03:22
Hola Gente,
en este post vamos a pegar u vistazo a PrimeFaces.
Que es PrimeFaces?
Se trata de una suite open source de componentes que extiende a JSF
- Posee un gran set de componentes UI (HtmlEditor, Dialog, AutoComplete, Charts, DatePicker, DataTables, TreeComponent, etc).
- Tecnología Ajax basada an la API Ajax JSF 2.0
- Muy liviana
- Solo un archivo jar
- No se requiere configuración
- No tiene dependencias (salvo por supuesto JSF runtime 2.0 o 2.1)
- Soporta Ajax Push via websockets
- Mobile UI kit que permite la creación de aplicaciones web para dispositivos mobiles
- Soporta Skinning y posee más de 35 temas preconstruídos. Soporta el editor visial de temas.
- Documentación mantenida por la comunidad muy actualizada.
- Muchos más
Se puede descargar la documentación en formato pdf desde aquí.
Se puede ver una demo llamada showcase que abarca todos los componentes de la UI y mobile desde aquí, además showcase permite ver el código fuente necesario para cada uno de las demostraciones.
Dependencias opcionales:
- itext 2.1.7 (DataExporter (PDF))
- apache poi 3.7 (DataExporter (Excel))
- rome 1.0 (FeedReader)
- commons-fileupload 1.2.1 (FileUpload)
- commons-io 1.4 (FileUpload)
Que haremos?
Vamos a crear un proyecto web dinámico utilizando Eclipse 4.3 Kepler con WTP en el cual habilitaremos JSF y PrimeFaces. El proyecto y el post no son muy pretenciosos, solo veremos como implementar algunos componentes para que quede la idea de la facilidad con la que se trabaja utilizando esta suite.
Prerequisitos
- Tener instalado JRE 1.6 o posterior.
- Eclipse 4.3 Kepler, el cual puede descargarse desde aquí. En la página que se mostrará hay que seleccionar el sistema operativo, la distribución, en nuestro caso "Eclipse IDE for Java EE Developers" y la plataforma (32 o 64 bits). Una vez descargado solo hay que descomprimir el archivo y se creará una carpeta llamada eclipse, solo debemos entrar en esa carpeta y ejecutar el comando eclipse
- Apache Tomcat 7.0.35, que puede descargarse desde esta página: https://tomcat.apache.org/download-70.cgi#7.0.35, una vez allí navegamos hasta la sección "Binary Distributions" se seleccionamos la distribución para nuestro SO. Recomiendo (zip para Window$ o tar.gz para Linux) a fin de no hacer más compleja la instalación. Una vez descargado el archivo lo descomprimimos en un carpeta, vemos que dentro se creará otra carpeta llamada "apache-tomcat-7.0.35" a la cual llamaremos [TOMCAT_HOME]
- JSF 2.1.17 que se puede descargar desde aquí. Una vez descargado el archivo lo copiamos en una carpeta cualquiera la que llamaremos [LIB_HOME]
- PrimeFaces 3.5 que se puede descargar desde aquí. Una vez descargado el archivo lo copiamos en [LIB_HOME].
Creando el proyecto.
Una vez que iniciamos eclipse y determinamos cual es nuestro workspace (recomiendo crear uno nuevo en el caso de tener uno ya), presionamos Ctrl+N para acceder al asistente de creación de nuevo proyecto y seleccionamos"Dynamic Web Proyect" y presionamos "Next >"
Como nombre del proyecto colocamos "pf" y luego presionamos "New Runtime..."
Ahora configuraremos un Server Runtime, esto implica que le diremos a eclipse donde se encuentran los archivos componentes del server.
Seleccionamos "Apache Tomcat v7.0" y presionamos "Next >"
Ahora debemos indicar el [TOMCAT_HOME] y presionamos "Finish".
Ahora nos encontramos en el asistente principal nuevamente, presionamos "Next >"
En la próxima pantalla solo presionamos "Next >"
En la última pantalla del asistente solo debemos marcar para que se genere el descriptor de despliegue web.xml y presionamos "Finish"
Para finalizar nos cambiamos a la perspectiva JEE
Habilitando JSF.
En la vista "Project Explorer" presionamos botón derecho sobre el proyecto "pf" y seleccionamos "Properties" y seleccionamos "Project Facets", luego chequeamos "JavaServer Faces" y presionamos el link "Further configuration required..."
En la siguiente pantalla presionar el icono del administrador de librerías.
Luego crear una nueva librería presionando el botón "New...", el nombre de la librería será "JSF" y presionamos "Ok"
Una ve definida la librería, la seleccionamos y presionamos el botón "Add External JARs...", debemos seleccionar ahora el path [LIB_HOME]/javax.faces-2.1.17.jar. Presionamos "OK"
Una vez en la pantalla principal del asistente, seleccionamos la librería y agregamos un patrón de mapeo que debe ser "*.xhtml"
Los mapeos deben quedar como se ve en la siguiente figura, para finalizar presionamos "OK" en esta pantalla y en la anterior.
Habilitar PrimeFaces
Esto es por demás sencillo, solo debemos arrastrar el archivo "javax.faces-2.1.17.jar" sobre la capeta WEB-INF/lib.
Una vez que lo soltamos seleccionamos la opción "Link to files" como se ve en la siguiente imagen.
Creando una instancia del servidor
Nos resta solo un paso para tener un proyecto JSF/PrimeFaces funcionando en un servidor, esto es, la instancia del servidor. Para ello en la vista "Servers" presionamos el link "new server wizard"
En la primera pantalla seleccionamos "Tomcat v7.0 Server" y presionamos "Next >"
En la última pantalla del asistente debemos pasar nuestro proyecto "pf· de la lista "Available" a la lista "Configured"para que se publique sea publicado en esta instancia del servidor, presionamos "OK" y listo.
La vista de "Servers" debe verse más o menos así:
Primeros pasos con PrimeFaces (La vista)
Bien, ya tenemos todo listo para trabajar con PrimeFaces, esta es la parte más corta de este post!
Vamos a crear un archivo llamado gauge.xhtml, para ello debemos seleccionar la carpeta "WebContent" y luego presionar Ctrl+N, navegamos a la categoría "General" y seleccionamos "File", presionamos "Next >" y en la siguiente pantalla colocamos "gauge.xhtml", chequeemos que "parent folder" sea: "pf/WebContent" como nombre del archivo y presionamos "Finish".
Una vez creado el archivo lo abrimos en el editor haciendo doble click en el. El código que debe contener es:
<html xmlns="https://www.w3c.org/1999/xhtml" xmlns:h="https://java.sun.com/jsf/html" xmlns:p="https://primefaces.org/ui"> <h:head> </h:head> <h:body> <h:form id="formGauge"> <p:poll interval="2" update="gauge" /> <p:meterGaugeChart id="gauge" value="#{gaugeBean.meterGaugeModel}" showTickLabels="false" labelHeightAdjust="110" intervalOuterRadius="130" seriesColors="66cc66, 93b75f, E7E658, cc6666" style="width:400px;height:250px" title="Custom Options" label="km/h" /> </h:form> </h:body> </html>
Diremos que el tag:
<html xmlns="https://www.w3c.org/1999/xhtml" xmlns:h="https://java.sun.com/jsf/html" xmlns:p="https://primefaces.org/ui">
Son las que permiten utilizar los tags JSF y PrimeFaces
El tag:
<p:poll interval="2" update="gauge" />
Hará que se actualice el elemento id="gauge" cada 2 segundos.
El tag:
<p:meterGaugeChart id="gauge" value="#{gaugeBean.meterGaugeModel}" showTickLabels="false" labelHeightAdjust="110" intervalOuterRadius="130" seriesColors="66cc66, 93b75f, E7E658, cc6666" style="width:400px;height:250px" title="Gauge Personalizado" label="km/h" />
Crea un gauge customizado, se personalizan cosas como los colores para los rangos, el alto el ancho, etc. El atributo value="#{gaugeBean.meterGaugeModel}", es uno de los más importantes ya que define la fuente de datos para este gauge, en este caso un bean llamado gaugeBean tendrá un método llamado getMeterGaugeModel(), el cual se traduce en un atributo llamado meterGaugeModel.
Con lo anterior hemos creado la capa de presentación, ahora falta el modelo que aportará datos a la presentación, en otras palabras el gaugeBean.
Primeros pasos con PrimeFaces (El modelo)
Vamos a crear una nueva clase java, para ellos debemos seleccionar el proyecto "pf" y luego presionar Ctrl+N, navegamos a la categoría "Java" y seleccionamos "Class", presionamos "Next >" y en la siguiente pantalla colocamos los datos que se muestran en la siguiente figura y presionamos "Finish":
Package: ar.com.magm.web.primeface
Name: GaugeBean
Debe implementar la interface Serializable
Abrimos la clase recién creada y agregamos el código que se muestra en negrita.
package ar.com.magm.web.primefaces; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.primefaces.model.chart.MeterGaugeChartModel; public class GaugeBean implements Serializable { private MeterGaugeChartModel meterGaugeModel; public MeterGaugeChartModel getMeterGaugeModel() { meterGaugeModel.setValue(Math.random() * 220); return meterGaugeModel; } public GaugeBean() { createMeterGaugeModel(); } private void createMeterGaugeModel() { List<Number> intervals = new ArrayList<Number>() { { add(20); add(50); add(120); add(220); } }; meterGaugeModel = new MeterGaugeChartModel(0, intervals); } }
El método más importante es:
public MeterGaugeChartModel getMeterGaugeModel() { meterGaugeModel.setValue((Math.random() * 190) + 20); return meterGaugeModel; }
Simplemente define una valor al azar para el MeterGaugeChartModel y lo retorna, esta es la fuente de datos, el modelo ya fue creado cuando se crea instancia y se definen los rangos que son:
add(20); add(50); add(120); add(220);
A estas alturas, ya tenemos los dos componentes necesarios, solo resta "decirle" a faces que este bean existe y como lo debe manejar. Para ello debemos hacer doble click en "faces-config.xml" que se encuentra en la carpeta "WEB-INF", una vez abierto el editor seleccionamos la pestaña "ManagedBean" y luego presionamos el botón "Add".
En la siguiente pantalla rellenar "Qualified class name" con ar.com.magm.web.primefaces.GaugeBean y presionar "Next >"
Por último nos aseguramos que el nombre del bean es gaugeBean, que es el que usamos en el tag y que el scope esapplication, esto nos asegura una sola instancia para toda la aplicación, luego presionamos "Finish".
Para finalizar presionamos Ctrl+Shift+S para guardar todos los cambios.
Probando la aplicación
Para probar nuestra aplicación debemos iniciar la instancia que maneja nuestro sitio web, para ellos vamos a la vista"Servers", seleccionamos la instancia que configuramos al principio y presionamos el botón start.
Apache Tomcat por defecto escucha en el puerto 8080, nuestro sitio tiene por nombre "pf", por ello debemos abrir nuestro navegador e ingresar la URL https://localhost:8080/pf/gauge.xhtml, luego podremos ver algomo lo que muestra la siguiente imagen, pero si nos detenemos veremos que el valor del gauge se actualiza de forma automática cada 2 segundos.
Bien, no tengo mucha más para decir que: "QUE LO DISFRUTEN"
Saludos
Login y control de acceso básico con PrimeFaces (Paso a paso)
Login y control de acceso básico con PrimeFaces (Paso a paso) magm 15 Febrero, 2013 - 01:35Hola Gente,
vamos a ver como hacer un formulario de login y un control de acceso utilizando PrimeFaces.
No usaremos bases de batos ni lógicas complejas, solo se trata de que un usuario no pueda acceder a ningún recurso sino no está logueado en el sistema.
Nos basamos en el post anterior "Primeros pasos con PrimeFaces, Eclipse y Tomcat (Paso a paso)", ya no entraré en tanto detalle de como realizar ciertas tareas que se encuentran explicadas en el post anterior.
Creando la vista
Debemos crear un archivo en "WebContent", o sea en el root de nuestra aplicación, llamado "login.xhtml"
El contenido es el siguiente:
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:f="https://java.sun.com/jsf/core"
xmlns:p="https://primefaces.org/ui">
<h:head> </h:head> <h:body style="text-align:center">
<p:growl id="mensajes" showDetail="true" life="2000" />
<h:form>
<p:panel header="Login" style="width:300px">
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="Usuario:" />
<p:inputText value="#{loginBean.nombre}" id="username"
required="true" label="username" />
<h:outputLabel for="password" value="Clave:" />
<p:password value="#{loginBean.clave}" id="password" required="true"
label="password" />
<f:facet name="footer">
<p:commandButton id="loginButton" value="Login"
actionListener="#{loginBean.login}" update=":mensajes"
oncomplete="manejarLogin(xhr, status, args)" />
</f:facet>
</h:panelGrid>
</p:panel>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
function manejarLogin(xhr, status, args) {
if (!args.validationFailed && args.estaLogeado) {
setTimeout(function() {
window.location = args.view;
}, 500);
}
}
//]]>
</script>
</html>
En un navegador, este código produce la siguiente vista:
Analicemos ahora un poco el código:
<p:growl id="mensajes" showDetail="true" life="2000" />
Se utiliza para mostrar mensajes emergentes, los generaremos desde loginBean, por ejemplo:
<p:inputText value="#{loginBean.nombre}" id="username" required="true" label="username" />
Este tag representa el campo que contiene el nombre del usuario que una vez que sea transmitido (submit) establecerá el valor en un bean, loginBean, del lado del servidor mediante el llamado al método setNombre() por el atributo nombre. Es un campo obligatorio, esto es, no puede estar en blanco.
<p:password value="#{loginBean.clave}" id="password" required="true" label="password" />
Este tag anterior trabaja similar al de nombre pero con la clave, además el campo no mostrará lo que se tipea.
Por último:
<p:commandButton id="loginButton" value="Login"
actionListener="#{loginBean.login}" update=":mensajes"
oncomplete="manejarLogin(xhr, status, args)" />
Es el botón que envía los campos al server (nombre y clave) y ejecuta el método login() de loginBean, una vez que se ejecuta al método mencionado se actualiza el componente de mensajes emergentes para mostrar los mensajes que se hayan acumulado, además ejecuta el script cliente manejarLogin(xhr, status, args). Las líneas más importantes del método son:
if (!args.validationFailed && args.estaLogeado) {
Nos aseguramos que el proceso de validación no haya fallado y que el usuario esté logueado. Si el usuario está logueado correctamente se ejecuta:
window.location = args.view;
donde args, es una selección de parámetros que recibiremos del servidor, en este caso nos envía el parámetro viewcon el nombre de la vista que debe ser cargada. A esto lo hacemos con un retardo para que se puedan mostrar los mensajes emergentes de bienvenida.
Creando el componente del servidor
Ya hemos visto como se realiza esta operación, debemos crear una clase java llamadaar.com.magm.web.primefaces.LoginBean, que debe ser luego agregada al faces-config.xml como bean de sesión, el código que para hacerlo es:
<managed-bean>
<managed-bean-name>loginBean</managed-bean-name>
<managed-bean-class>ar.com.magm.web.primefaces.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope> </managed-bean>
Para poder copiar el código anterior, es necesario seleccionar la pestaña "Source" del editor del archivo de configuración de faces.
El código de la clase recién creada es el siguiente:
package ar.com.magm.web.primefaces; import java.io.Serializable; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.faces.event.ActionEvent; import javax.servlet.http.HttpSession; import org.primefaces.context.RequestContext; public class LoginBean implements Serializable { private static final long serialVersionUID = -2152389656664659476L; private String nombre; private String clave; private boolean logeado = false; public boolean estaLogeado() { return logeado; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getClave() { return clave; } public void setClave(String clave) { this.clave = clave; } public void login(ActionEvent actionEvent) { RequestContext context = RequestContext.getCurrentInstance(); FacesMessage msg = null;if (nombre != null && nombre.equals("admin") && clave != null&& clave.equals("admin")) {logeado = true;msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);} else {logeado = false;msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Login Error","Credenciales no válidas");}FacesContext.getCurrentInstance().addMessage(null, msg);context.addCallbackParam("estaLogeado", logeado);if (logeado)context.addCallbackParam("view", "gauge.xhtml");} public void logout() {HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);session.invalidate();logeado = false;} }
Además de los gettes y setters para nombre y clave y el getter para saber si el usuario está logueado, tenemos dos métodos más: login() y logout(), analicemos login().
if (nombre != null && nombre.equals("admin") && clave != null && clave.equals("admin")) { logeado = true; msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);
Si el nombre de usuario y contraseña son correctos y además poseen el valor "admin", consideramos que el usuario es válido y lo logueamos ejecutando:
logeado = true;
Luego establecemos el mansaje de bienvenida del que hablamos antes:
msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);
Aclaración: si alguno se está preguntando porque el único usuario es "admin" y la clave es "admin" y ambos valores están "hardcodeados" y no se pueden cambiar, la respuesta es que he simplificado esa tarea para no hacer más complejo el ejemplo, además no resulta muy complejo obtenerlo de una base de datos por ejemplo.
Si el usuario no está logueado:
} else { logeado = false; msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Login Error", "Credenciales no válidas"); }
Simplemente lo aseguramos estableciendo el valor de la variable logeado en falso y estableciendo un mensaje de advertencia al usuario.
Finalmente enviamos el mensaje que establecimos antes:
FacesContext.getCurrentInstance().addMessage(null, msg);
Agregamos un parámetro que será utilizado en el script del cliente:
context.addCallbackParam("estaLogeado", logeado);
Si el usuario ha podido loguearse sin inconvenientes, le enviamos como parámetro la próxima vista.
if (logeado) context.addCallbackParam("view", "gauge.xhtml");
El método logout() que aún no hemos utilizado realiza lo siguiente:
Obtiene la sesión del usuario:
HttpSession session = (HttpSession) FacesContext.getCurrentInstance() .getExternalContext().getSession(false);
La invalida:
session.invalidate();
y desloguea al usuario:
logeado = false;
Agregando la opción logout a "gauge.xhtml"
Editamos "gauge.xhtml" y agregamos el siguiente código inmediatamente después del body:
<h:body><p:commandLink id="logout" actionListener="#{loginBean.logout}" style="margin-right:20px;" oncomplete="logout(xhr, status, args)"><h:outputText value="logout" /></p:commandLink>
Se agregará un link como el que se ve en la siguiente figura:
El link ejecutará el método loginBean.logout y una vez completado ejecutará el script del cliente logout(....)
El script del cliente debe agregarse entre el cierre del tag body y el cierre del tag html como sigue:
</h:body> <script type="text/javascript"> //<![CDATA[ function logout(xhr, status, args) { setTimeout(function() { window.location = 'login.xhtml'; }, 500); } //]]> </script> </html>
Como se puede observar solo hacemos un redirect a la página de login.
Controlando el acceso a todos los recursos
Hasta aquí hemos creado los componentes necesarios para permitir al usuario presentar sus credenciales y que sean comprobadas y almacenadas en una sesión, también permitimos al usuario terminar su sesión, pero aún falta algo muy importante ya que hasta aquí debemos confiar en que todos los usuarios son buenos y pasan primero por el proceso de login antes de peticionar cualquier otro recurso. En fin, definitivamente no podemos confiar en esto, debemos crear un mecanismo tal que si un usuario o proceso requiere un recurso sin que se hayan validado sus credenciales no pueda acceder al mismo. A esto lo hacemos con el uso de un filtro que es un componente que se ejecuta antes de que se maneje la petición final en el server y puede "torcer" el flujo de ejecución normal si es necesario.
Creando el filtro
Para crear el filtro presionamos Ctrl+N en la vista "Project Explorer" y seleccionamos "Filter" de la categoría "Web" y presionamos el botón "Next >"
Completamos la primer pantalla con:
Java package: ar.com.magm.web.filters Class name: LoginFilter
Presionamos "Next >"
Luego cambiamos el URL Pattern por "/*" para que este filtro se ejecute ante cualquier petición.
y presionamos "Finish"
El código que debe tener el método doFilter(..) se encuentra a continuación y está explicado con comentarios en el mismo código:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // Obtengo el bean que representa el usuario desde el scope sesión LoginBean loginBean = (LoginBean) req.getSession().getAttribute("loginBean"); //Proceso la URL que está requiriendo el cliente String urlStr = req.getRequestURL().toString().toLowerCase(); boolean noProteger = noProteger(urlStr); System.out.println(urlStr + " - desprotegido=[" + noProteger + "]"); //Si no requiere protección continúo normalmente. if (noProteger(urlStr)) { chain.doFilter(request, response); return; } //El usuario no está logueado if (loginBean == null || !loginBean.estaLogeado()) { res.sendRedirect(req.getContextPath() + "/login.xhtml"); return; } //El recurso requiere protección, pero el usuario ya está logueado. chain.doFilter(request, response); }
He separado la lógica de excepción de control de recursos en un método muy sencillo que se encuentra a continuación y que también forma parte del filtro el cual no requiere mucha explicación.
private boolean noProteger(String urlStr) { /* * Este es un buen lugar para colocar y programar todos los patrones que * creamos convenientes para determinar cuales de los recursos no * requieren protección. Sin duda que habría que crear un mecanizmo tal * que se obtengan de un archivo de configuración o algo que no requiera * compilación. */ if (urlStr.endsWith("login.xhtml")) return true; if (urlStr.indexOf("/javax.faces.resource/") != -1) return true; return false; }
Este filtro completa este pequeño sistema de control de acceso y logueo, espero que les sea útil.
El workspace completo de este post, del anterior y de futuros posts que tengan que ver con el tema se encuentra en github en la siguiente dirección: https://github.com/magm3333/workspace-pftuto
Saludos
Mariano
Hola Mariano, ante todo te
- Inicie sesión para enviar comentarios
Hola, te recomiendo
Hola,
te recomiendo imprimir la salida del método getRequestURL(), en una de esas no sirve endWith() y tienes que usar indexOf().
Utiliza las herramientas de debug para seguir paso a paso el intento de acceso, esto no tiene ninguna ciencia, dberías darte cuenta del problema con un buen seguimiento del flujo de ejecución.
Saludos
Mariano
- Inicie sesión para enviar comentarios
Hola, seguí tu ejemplo, pero
- Inicie sesión para enviar comentarios
Para que no te salga la
- Inicie sesión para enviar comentarios
Estimado, con nada de
Estimado,
con nada de información es imposible recomendarte algo salvo que si eres creyente reces a ver si funciona.
Hablando en serio pasa un poco más de info como para poder sacar conclusiones, ya que si haz escrito el código tal cual está en el post y has seguido todas las instrucciones, debe funcionar perfecto.
Por otro lado, si eres desarrollador, deberías usar alguna herramienta de debug para seguir paso a paso la ejecición y ver dónde está el problema.
Saludos
Mariano
- Inicie sesión para enviar comentarios
Hola que tal, eh revisado mi
- Inicie sesión para enviar comentarios
Chud, a dónde se está
Chud, a dónde se está redirigiendo la página?
- Inicie sesión para enviar comentarios
Saludos, Primero que nada
Saludos,
Primero que nada muchas gracias por compartir este exelente post, lo he probado y funciona perfectamente solo quisiera ver si me pueden ayudar con una duda. La cuestion es que mi forma para login la tengo en un popup en mi pagina index.xhtml. En mi filtro tengo definido que proteja todo mi sitio excepto mi pagina index.xhtml. Mi pagina index tiene un template del cual hereda el header y footer, pero cuando cargo esta pagina index no me los muestra asumo que es por que esas paginas estan bloquedas por el filtro. Asi que no se si me ayudan con una pequenia idea de como deproteguer varias paginas al mismo tiempo en un filtro.
Gracias y sigan adelante.
- Inicie sesión para enviar comentarios
Como puedes ver, el filtro es
Como puedes ver, el filtro es muy precario, yo recomiendo utilizar otras técnicas, pero si quieres salir rápido andando puedes modificarlo para que soporte tu excepción, por ejemplo:
if (urlStr.endsWith("login.xhtml" || expresión)
donde expresión será lo que tu definas que haga que no se filtre tu template. Igualmente te recomiendo utilizar expresiones regulares, sería algo como:
if (Pattern.compile(tuExprReg).matcher(urlStr).find())
return true;
Donde deberás escribir tu expresión regular en tuExprReg, de esta forma es más potente y flexible.
Saludos
Mariano
- Inicie sesión para enviar comentarios
Hola, muy buenos tus posteos
Hola, muy buenos tus posteos sobre primefaces... es mas, lo poco que se lo aprendi de ti...
trabajo en una obra social, tengo que dieñar una aplicacion web para controlar los Planes Especiales (medicamentos) que se les da a los afiliados y sus consumos...
quiero tener una pagina principal y con un menu cambiar solo el contenido de un p:layoutUnit ubicado en el centro..
tienes algun ejemplo, antes solo lo hacia con jquery cambiando el contenido del div
div.load('pagina.html')
como lo hace con primefaces o facelets...
gracias
- Inicie sesión para enviar comentarios
Hola Juan, la verdad es que
Hola Juan,
la verdad es que lo poco que se de este tema es lo que expuse ya que no es mi trabajo diario, esto lo publiqué ya que tuve que investigarlo para dictar una capacitación dentro de la cual tocábamos este tema de forma secundaria. No debe ser nada complejo ya que no me llevó mucho investigar para producir el material. Decidí publicarlo para que rindan un poco más las horas invertidas.
Si llegas a alguna solución, te pido que enriquezcas el material con tu aporte para que nos sirva a todo@s.
Saludos
Mariano
- Inicie sesión para enviar comentarios
alguien me puede explicar por
alguien me puede explicar por que cuando regrese de una pagina me lleva a login en vez de quedarse en la pagina principal del proyecto
Gracias por su apoyo
Saludos
- Inicie sesión para enviar comentarios
Hola, probé el código y
Hola, probé el código y funciona bien pero de igual manera puedo ingresar a la pagina gauge.xhtml sin necesidad de loguearme mediante la barra de direcciones... pasa que cuando te logueas y desloqueas igual puedes entrar con la barra de direcciones y el filtro no actúa sino que permite ingresar o sea no redirecciona....
- Inicie sesión para enviar comentarios
Saludos Mariano El metodo de
Saludos Mariano El metodo de logout no esta funcionando. Pudieras darnos alguna idea de como hacerlo funcionar??
- Inicie sesión para enviar comentarios
Una tabla con datos de una base de datos MySQL con PrimeFaces (Paso a paso)
Una tabla con datos de una base de datos MySQL con PrimeFaces (Paso a paso) magm 20 Febrero, 2013 - 17:53Hola gente,
una vez más con PrimeFaces, esta vez iremos un poco más allá, crearemos una tabla que mostrará datos de una base de datos MySQL. Además está tabla permitirá ordenar y filtrar y paginar los datos. Un "chiche!".
Como en el tutorial anterior, nos basaremos en el proyecto inicial creado en el post: "Primeros pasos con PrimeFaces, Eclipse y Tomcat (Paso a paso)"
Vamos a comenzar con una modificando el Filtro (LoginFilter.java) y nos aseguramos que la nueva versión contenga las líneas que muestro en negrita a continuación:
private boolean noProteger(String urlStr) {
/*
* Este es un buen lugar para colocar y programar todos los patrones que
* creamos convenientes para determinar cuales de los recursos no
* requieren protección. Sin duda que habría que crear un mecanizmo tal
* que se obtengan de un archivo de configuración o algo que no requiera
* compilación.
*/
if (urlStr.indexOf("/login.xhtml")!= -1)
return true;
if (urlStr.indexOf("/javax.faces.resource/") != -1)
return true;
return false;
}
Luego en la clase LoginBean
FacesContext.getCurrentInstance().addMessage(null, msg);
context.addCallbackParam("estaLogeado", logeado);
if (logeado)
context.addCallbackParam("view", "ventas.xhtml");
}
A raíz de esta modificación, ya estoy arrepentido de haber hecho el post anterior ya que la intención fue dar una idea genérica de como se hace para proteger recursos mediante algún sistema de control de acceso con artefactos Web Java, pero por simplista he caído en lo burdo, a este sistema le falta mucho y las falencias son grandes y es debido a que no se trata de un tema trivial, veré si en el futuro me redimo y vemos algo un poco más adecuado a la realidad, por ejemplo Spring Security, en fin ya veremos las ganas y el tiempo que son dos cosas que no regalan ni se compran.
Bien hecha la mea culpa comencemos.
Creando un DataSource accesible mediante JNDI
Para definir un recurso de este tipo, solo debemos crear un archivo xml llamada "context.xml" en la carpetaWebContent/META-INF cuyo contenido debe ser:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource auth="Container" description="BD Practico"
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>META-INF/context.xml</WatchedResource> </Context>
Sin entrar en mucho detalle acerca de la definición del pool de conexiones, solo diremos que los datos principales que contiene este archivo son aquellos referidos a los que requiere el driver JDBC los cuales he resaltado en negrita, un atributo fundamental es el nombre de recurso que estamos creado que es "jdbc/practico" y colocar la password correcta.
Una vez que disponemos de este archivo debemos colocar en el classpath el driver JDBC de MySQL que pueden descargar desde aquí: https://cdn.mysql.com/Downloads/Connector-J/mysql-connector-java-5.1.23.zip, una vez que descarguen este archivo deben descomprimir solo mysql-connector-java-5.1.23-bin.jar que se encuentra dentro del zip y copiarlo o linkearlo (como ya hemos visto) dentro de la carpeta WebContent/WEB-INF/lib
Obteniendo una conexión del pool y alacenandola en el contexto global de la aplicación
Ya hemos realizado la configuración necesaria para que Tomcat cree un pool de conexiones al iniciar, es nuestro trabajo ahora obtener una referencia a ese pool y pedirle una conexión a la base de datos para ser utilizada en la aplicación. Es buena práctica hacer esto una sola vez y al inicio de la aplicación. Afortunadamente la especificación web de java nos provee de una serie de herramientas para poder hacerlo, en este caso haremos uso de los métodos callback de control de ciclo de vida, también se los llama listeners, usaremos particularmente el listener a nivel de contexto, el cual posee dos métodos que son llamados por el contenedor (tomcat) cuando se inicia y cuando finaliza.
Crear un listener es muy sencillo, se trata de una clase java que debe implementar para este caso la interfaceServletContextListener y estar marcado con la anotación @WebListener, el código se muestra a continuación:
package ar.com.magm.web.listeners;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;
@WebListener
public class InitListener implements ServletContextListener { public InitListener() { }
public void contextInitialized(ServletContextEvent sce) {
DataSource fuenteDatos = null;
Context ctx;
try {
ServletContext sc = sce.getServletContext();
ctx = new InitialContext();
fuenteDatos = (DataSource) ctx.lookup("java:comp/env/jdbc/practico");
Connection cn = fuenteDatos.getConnection();
sc.setAttribute("datasource", cn);
throw new RuntimeException(e.getMessage());
} catch (SQLException e) {
throw new RuntimeException(e.getMessage());
}
}
public void contextDestroyed(ServletContextEvent sce) { }
}
En las líneas: ServletContext sc = sce.getServletContext();
ctx = new InitialContext();
fuenteDatos = (DataSource) ctx.lookup("java:comp/env/jdbc/practico");
Obtenemos una referencia al recurso mediante JNDI y el nombre que establecimos en el archivo de configuración.
La línea:
Connection cn = fuenteDatos.getConnection();
Obtiene la conexión.La línea: sc.setAttribute("datasource", cn);
almacena en el contexto global de la aplicación la instancia de la conexión con el nombre datasource.
Una vez que contamos con este listener es muy sencillo acceder desde la aplicación a esta conexión. Bien hasta aquí no hemos tocado un tema importante, a que base de datos nos conectamos, pues bien es una base de datos que he utilizado desde hace años para diversos ejemplos y la pueden descargar desde aquí, el archivo esBDDump.sql y se encuentra dentro de archivos.zip. Una vez descargado el zip y descomprimido el archivo .sql, y desde una consola escribimos:
$ mysql --user=UnUsuarioVálido --password=LaClave < BDDump.sql
Esto creará la base de datos practico en la instancia de MySQL.
Creando la vista del usuario
Ahora crearemos la vista del usuario, se trata como ya hemos visto antes de un archivo .xhtml, en este caso"ventas.xhtml", en este archivo crearemos una tabla en la cual mostraremos información de ventas que saldrán de la siguiente consulta SQL: SELECT YEAR(fecha) AS anio, MONTH(fecha) AS mes, zona, cliente, SUM(importe*cantidad) AS ventas FROM dw_ventasfact v INNER JOIN clientes c ON v.idCliente=c.idCliente INNER JOIN zonas z ON z.idZona=c.idZona GROUP BY zona, cliente, anio, mes ORDER BY anio, mes, zona, cliente
Para nuestro caso una venta tiene los atributos que se muestran en la imagen, o sea: año, mes, zona, cliente e importe de venta. Para representar cada venta (cada hecho), o cada fila que retorne la consulta, crearemos una clase java.
Ahora veremos el código de la vista, o sea del archivo "ventas.xhtml":
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:f="https://java.sun.com/jsf/core"
xmlns:p="https://primefaces.org/ui">
<h:head></h:head>
<h:body style="text-align:center">
<h:form>
<p:dataTable id="tablaDeDatos" var="venta"
value="#{ventasBean.ventas}" widgetVar="tablaDeVentas"
emptyMessage="No hay ventas con este criterio de filtrado"
filteredValue="#{ventasBean.ventasFiltradas}" paginator="true"
rows="10" style="width:800px">
<f:facet name="header">
<p:outputPanel>
<h:outputText value="Buscar en todos:" />
<p:inputText id="globalFilter" onkeyup="tablaDeVentas.filter()" style="width:150px" />
</p:outputPanel>
</f:facet>
<p:column id="zonaCol" filterBy="#{venta.zona}" headerText="Zona"
filterMatchMode="exact" filterOptions="#{ventasBean.zonasOptions}">
<h:outputText value="#{venta.zona}" />
</p:column>
<p:column id="clienteCol" filterBy="#{venta.cliente}"
headerText="Cliente" filterMatchMode="startsWith">
<h:outputText value="#{venta.cliente}" />
</p:column>
<p:column id="anioCol" filterBy="#{venta.anio}" headerText="Año"
filterMatchMode="startsWith">
<h:outputText value="#{venta.anio}" />
</p:column>
<p:column id="mesCol" filterBy="#{venta.mesLetra}" headerText="Mes"
filterMatchMode="exact" filterOptions="#{ventasBean.mesesOptions}">
<h:outputText value="#{venta.mesLetra}" />
</p:column>
<p:column id="ventasCol" headerText="Importe Venta" style="text-align:right">
<h:outputText value="#{venta.ventaFormat}"/>
</p:column>
</p:dataTable>
</h:form>
</h:body>
</html> El resultado final se verá así:
Bien, ese es todo el código de la vista del cliente, la analizaremos por partes:
Este tag: <p:dataTable id="tablaDeDatos" var="venta"value="#{ventasBean.ventas}" widgetVar="tablaDeVentas"
emptyMessage="No hay ventas con este criterio de filtrado"
filteredValue="#{ventasBean.ventasFiltradas}" paginator="true"
rows="10" style="width:800px"> define la tabla de datos que se llama "tablaDeDatos" (id="tablaDeDatos"), los valores que se mostrarán en la tabla se obtendrán de: ventasBean.ventas (value="#{ventasBean.ventas}") que es una lista de instancias de Venta que es la clase java que representa solo una venta y que aún no hemos creado, por cada instancia que se recorra de la lista se crea una variable llamada venta (var="venta"). La lista que se encuentra en ventasBean.ventas contiene todas las ventas que obtenemos de la consulta SQL, este componente permite que se apliuen filtros sobre esa lista y lo implementa utilizando una segunda lista, esta es: ventasBean.ventasFiltradas (filteredValue="#{ventasBean.ventasFiltradas}") lo bueno de esto es que no tenemos que preocuparnos mas que por definir esta lista en el server, PrimeFaces la mantiene por nosotros. La lista se paginará automáticamente y mostrará 10 filas por página, el ancho de la lista será de 800 píxeles (paginator="true" rows="10" style="width:800px"). También se podrá realizar una búsqueda global (en cualquier columna), está búsqueda y filtrado se produce en el cliente, por ello debemos definir el nombre que tendrá el elemento html (el widget) en el cliente y este será: tablaDeVentas (value="#{ventasBean.ventas}"). Por último diremos que si no existen items ante algún criterio de búsqueda se mostrará el mensaje: "No hay ventas con este criterio de filtrado" (emptyMessage="No hay ventas con este criterio de filtrado").
El filtro que mencioné en el último párrafo se fine con el tag:
Ahora solo resta definir las columnas que mostraremos en esta tabla de datos.
<p:column id="zonaCol" filterBy="#{venta.zona}" headerText="Zona"
filterMatchMode="exact" filterOptions="#{ventasBean.zonasOptions}">
<h:outputText value="#{venta.zona}" />
</p:column>
Para todas las columnas definiremos un id, en este caso id="zonaCol", también el texto que se mostrará en la cabecera, en este caso: headerText="Zona", también definiemos cual es el componente que renderizará el valor en cada celda, en este caso lo hacemos con el tag: <h:outputText value="#{venta.zona}" /> donde podemos ver que se utiliza el atributo zona del bean venta, recordemos que este bean lo definimos cuando definimos la tabla (var="venta"). No entraré en detalle sobre estos atributos para las próximas columnas.
Particularmente para la columna zona definimos un filtro que será una lista desplegable que contiene todas las zonas posibles, a esto lo hacemos en: filterOptions="#{ventasBean.zonasOptions}", el bean ventasBean poseerá una lista especial llamada zonasOptions que otorgará la lista de zonas que mencionamos. Además definimos que cada vez que se selccione una zona se realice una búsqueda por exactamente igual en: filterMatchMode="exact". El filtro en acción se ve así:
<p:column id="clienteCol" filterBy="#{venta.cliente}"
headerText="Cliente" filterMatchMode="startsWith">
<h:outputText value="#{venta.cliente}" />
</p:column>
La diferencia más grande (amén del valor que se muestra) es el filtro, solo hemos definido que se buscará todos aquellos valores que comiencen con: filterMatchMode="startsWith", si no se define otra cosa se utiliza un campo de texto en el cual debemos escribir el criterio de filtrado como se ve en la siguiente imagen:
La clase se llama: ar.com.magm.model.Venta y el código es:
import java.io.Serializable; import java.text.DecimalFormat;
public class Venta implements Serializable { private static final long serialVersionUID = 8060348552656940209L;
public static long getSerialversionuid() { return serialVersionUID; }
private int anio; private String cliente; private int mes; private String mesLetra; private double venta; private String zona;
public Venta(String zona, String cliente, int anio, int mes, String mesLetra, double venta) { super(); this.zona = zona; this.cliente = cliente; this.anio = anio; this.mes = mes; this.mesLetra = mesLetra; this.venta = venta; }
public int getAnio() { return anio; } public String getCliente() { return cliente; } public int getMes() { return mes; } public String getMesLetra() { return mesLetra; } public double getVenta() { return venta; }
public String getVentaFormat() { DecimalFormat df = new DecimalFormat("0.00"); return df.format(venta); }
public String getZona() { return zona; } }
No hay mucho que decir de este bean, solo que tenemos un par de getters especiales, por un lago uno que nos retorna el nombre del mes getMesLetra() y el importe formateado getVentaFormat().
El bean manejado por faces que mantiene las listas de ventas.
Bien, es hora de crear el controlador que es una clase llamada ar.com.magm.web.primefaces.VentasBean y cuya configuración en faces-config.xml es:
<managed-bean>
<managed-bean-name>ventasBean</managed-bean-name>
<managed-bean-class>ar.com.magm.web.primefaces.VentasBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Notemos que el scope de este bean es session, si la lista fuese común a todos los usuarios, el scope correcto seríaapplication ya que no sería necesario crear más instancias. En algún post futuro haremos uso de esto. El código completo del bean es el siguiente: package ar.com.magm.web.primefaces;
import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.faces.context.FacesContext; import javax.faces.model.SelectItem; import javax.servlet.ServletContext; import ar.com.magm.model.Venta;
public class VentasBean implements Serializable { private static final long serialVersionUID = -6690574219803425728L;
private String[] meses = new String[] { "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" };
private String sql = "SELECT year(fecha) as anio, month(fecha) as mes, zona, cliente, sum(importe*cantidad) as ventas FROM dw_ventasfact v INNER JOIN clientes c ON v.idCliente=c.idCliente INNER JOIN zonas z ON z.idZona=c.idZona GROUP BY zona, cliente, anio, mes ORDER BY anio,mes,zona,cliente"; private List<Venta> ventas; private List<Venta> ventasFiltradas; private List<String> zonas;
public VentasBean() { processList(null); }
public SelectItem[] getMesesOptions() { SelectItem[] r = new SelectItem[13]; r[0] = new SelectItem("", "Todos"); for (int t = 0; t < meses.length; t++) r[t + 1] = new SelectItem(meses[t], meses[t]); return r; }
public List<Venta> getVentas() { return ventas; }
public List<Venta> getVentasFiltradas() { return ventasFiltradas; }
public SelectItem[] getZonasOptions() { SelectItem[] r = new SelectItem[zonas.size() + 1]; r[0] = new SelectItem("", "Todas"); for (int t = 0; t < zonas.size(); t++) r[t + 1] = new SelectItem(zonas.get(t), zonas.get(t)); return r; }
private void processList(Object args[]) { ventas = new ArrayList<Venta>(); zonas = new ArrayList<String>(); ServletContext sc = (ServletContext) FacesContext.getCurrentInstance() .getExternalContext().getContext(); Connection cn = (Connection) sc.getAttribute("datasource"); try { PreparedStatement pst = cn.prepareStatement(sql); if (args != null) { for (int t = 0; t < args.length; t++) { pst.setObject(t + 1, args[t]); } } ResultSet rs = pst.executeQuery(); while (rs.next()) { String zona = rs.getString("zona"); Venta venta = new Venta(zona, rs.getString("cliente"), rs.getInt("anio"), rs.getInt("mes"), meses[rs.getInt("mes") - 1], rs.getDouble("ventas")); ventas.add(venta); if (!zonas.contains(zona)) zonas.add(zona); } } catch (SQLException e) { e.printStackTrace(); } }
public void setVentasFiltradas(List<Venta> ventasFiltradas) { this.ventasFiltradas = ventasFiltradas; } }
Como pueden ver en el constructor se llama al método que llena la lista de ventas (List<Venta> ventas) processList(), lo hace en base a los datos obtenidos de la consulta sql además llena una lista con las zonas (List<String> zonas). También es necesario mencionar como obtenemos la conexión que almacenamos en el contexto general, primero obtenemos el contexto: ServletContext sc = (ServletContext) FacesContext.getCurrentInstance() .getExternalContext().getContext(); Luego usamos el contexto para obtener la conexión: Connection cn = (Connection) sc.getAttribute("datasource");
La lista List<Venta> ventasFiltrada junto a los métodos setVentasFiltradas() y getVentasFiltradas() es lo único que necesitamos para que los filtros se procesen correctamente del lado del server. El método getVentas() es el fundamental, el que provee los datos iniciales de la lista de ventas y el que se utiliza para generar las listas filtradas. Por último los métodos getMesesOptions() y getZonasOptions() retornan un arreglo de objetos SelectItem que utilizará el componente tabla para renderizar los filtros de las columnas de zona y mes. Bien, esto es todo por ahora, recuerden que disponen del proyecto eclipse completo en: https://github.com/magm3333/workspace-pftuto Saludos
Mariano
Buenas noches, muy buenos sus
- Inicie sesión para enviar comentarios
Me podrías pasar al menos el
Me podrías pasar al menos el error?
- Inicie sesión para enviar comentarios
Saludos, muy buenos sus
- Inicie sesión para enviar comentarios
Con este componente no se
Con este componente no se puede, siempre tienes que utilizar beans, pero puedes utilizar caches, etc como para mejorar la performance.
Saludos
Mariano
- Inicie sesión para enviar comentarios
A mi tb me da el error que
A mi tb me da el error que comenta el primero, esto es lo que me indica
cannot Deploy pf
deploy is failing=Error occurred during deployment: Exception while loading the app : java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.IllegalArgumentException: java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener. Please see server.log for more details.
- Inicie sesión para enviar comentarios
Podrías adjuntar el log del
Podrías adjuntar el log del server, con el texto que pegas, hay que se un poco más que adivino para sacar conclusiones.
Saludos
Mariano
- Inicie sesión para enviar comentarios
'anónimo', si quieres
'anónimo', si quieres adjuntar un fichero puedes registrarte y crear un tema nuevo del foro con la consulta, por ejemplo en el foro de SW Open Source: http://www.dataprix.com/foros-it/software-it/software-open-source, ya que en los comentarios no se pueden adjuntar archivos.
Saludos,
- Inicie sesión para enviar comentarios
Pantalla principal con menú estilo dock con PrimeFaces (Paso a paso)
Pantalla principal con menú estilo dock con PrimeFaces (Paso a paso) magm 22 Febrero, 2013 - 15:14Estimad@s,
seguimos con PrimeFaces, ahora vamos a crear nuestra pantalla principal, la cual contendrá un menú al estilo Dock:
Implementaremos en esta ocasión, las tres opciones disponibles Ventas|Gauge|Logout, luego, en el futuro, tendremos que agregar alguno más.
Lo primero es eliminar código que ya no usaremos, se trata de la opción de logout de "gauge.xhtml", se deberá eliminar el código que coloco en color rojo y lo que coloco en color azul, lo que coloco en color azul, en realidad lo eliminaremos de este archivo y lo pegaremos en otro.
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:p="https://primefaces.org/ui">
<h:head>
</h:head>
<h:body>
<p:commandLink id="logout" actionListener="#{loginBean.logout}"
style="margin-right:20px;" oncomplete="logout(xhr, status, args)">
<h:outputText value="logout" />
</p:commandLink>
<h:form id="formGauge">
<p:poll interval="2" update="gauge" />
<p:meterGaugeChart id="gauge" value="#{gaugeBean.meterGaugeModel}"
showTickLabels="false" labelHeightAdjust="110"
intervalOuterRadius="130"
seriesColors="66cc66, 93b75f, E7E658, cc6666"
style="width:400px;height:250px" title="Gauge Personalizado"
label="km/h" />
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
function logout(xhr, status, args) {
setTimeout(function() {
window.location = 'login.xhtml';
}, 500);
}
//]]>
</script>
</html>
Luego cambiaremos parte de una línea en LoginBean.java (lo que está en negrita es lo que debe quedar):
...
context.addCallbackParam("estaLogeado", logeado);
if (logeado)
context.addCallbackParam("view", "menu.xhtml");
}
...
Luego agregamos el archivo "menu.xhtml":
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:f="https://java.sun.com/jsf/core"
xmlns:p="https://primefaces.org/ui">
<h:head>
</h:head>
<h:body>
<p:layout fullPage="true">
<p:layoutUnit position="center">
<iframe id="frame" src="ventas.xhtml"
style="width: 100%; height: 100%; text-align: center;"
seamless='seamless' />
</p:layoutUnit>
</p:layout>
<h:form id="form">
<p:dock>
<p:menuitem value="Ventas" icon="/images/ventas.png"
url="javascript:cambioPagina('ventas.xhtml')" />
<p:menuitem value="Gauge" icon="/images/gauge.jpg"
url="javascript:cambioPagina('gauge.xhtml')" />
<p:menuitem value="Logout" icon="/images/logout.png"
actionListener="#{loginBean.logout}"
oncomplete="logout(xhr, status, args)" />
</p:dock>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
var actual = 'ventas.xhtml';
function cambioPagina(pagina) {
if (pagina != actual) {
$('#frame').attr('src', pagina);
actual=pagina;
}
}
function logout(xhr, status, args) {
setTimeout(function() {
window.location = 'login.xhtml';
}, 500);
}
//]]>
</script>
</html>
El código importante está en negrita y lo examinamos a continuación:
Los tags a continuación:
<p:layout fullPage="true">
<p:layoutUnit position="center">
<iframe id="frame" src="ventas.xhtml"
style="width: 100%; height: 100%; text-align: center;"
seamless='seamless' />
</p:layoutUnit>
</p:layout>
Generan un área de contenido al centro que a su vez contiene un iframe que será el contenedor que muestre la página seleccionada, por defecto es "ventas.xhtml", que más decir de este iframe, que no tiene bordes (seamless='seamless'), que ocupa el 100% de ancho y alto.
Luego tenemos:
<h:form id="form">
<p:dock>
<p:menuitem value="Ventas" icon="/images/ventas.png"
url="javascript:cambioPagina('ventas.xhtml')" />
<p:menuitem value="Gauge" icon="/images/gauge.jpg"
url="javascript:cambioPagina('gauge.xhtml')" />
<p:menuitem value="Logout" icon="/images/logout.png"
actionListener="#{loginBean.logout}"
oncomplete="logout(xhr, status, args)" />
</p:dock>
</h:form>
El tag dock permite armar un menú al estilo Dock, por defecto en la parte inferior de la pantalla, aunque esto se puede cambiar sin problemas, un menu dock está compuesto de items (menuitem) con un nombre (atributo value) y un icono, luego podemos hacer que ejecute acciones al hacerle click, en este caso hay dos tipos de acciones, en el caso de Ventas y Gauge se ejecuta una función javascript que carga una página en el iframe, en el caso del logout, solo he trasladado la lógica del comando remoto que estaba en "gauge.xhtml" a un menuitem, esto ya lo he explicado en su momento.
La función cambioPagina() es muy sencilla:
var actual = 'ventas.xhtml';
function cambioPagina(pagina) {
if (pagina != actual) {
$('#frame').attr('src', pagina);
actual=pagina;
}
}
Solo carga la página recibida como argumento en el iframe utilizando un selector de jQuery (con PrimeFaces disponemos siempre de jQuery en el lado del cliente), esto ocurre siempre y cuando la página no se encuentre carga, para eso se utiliza la variable auxiliar actual.
Eso es todo, recuerden que disponen del proyecto eclipse completo en: https://github.com/magm3333/workspace-pftuto
La imágenes de los iconos están aquí, aquí y aquí.
Saludos
Control de acceso a datos automatizado con PrimeFaces (Paso a paso)
Control de acceso a datos automatizado con PrimeFaces (Paso a paso) magm 25 Febrero, 2013 - 16:08Estimad@s,
siguiendo con los "minitutos" de PrimeFaces veremos ahora como mejorar la tabla con datos de ventas que hemos desarrollado en este tutorial y también haciendo uso del "pseudo" sistema de control de acceso que desarrollamos en este tutorial.
Modificando la lógica de validación
La idea general es que en base al usuario que se haya logueado se filtren los datos sin modificar la vista en absoluto. Para conseguir este objetivo comenzaremos modificando "LoginBean.java" agregando al principio de la clase el siguiente código:
public class LoginBean implements Serializable { private static final long serialVersionUID = -2152389656664659476L;
static {
users=new String[101][2];
users[0][0]="admin";
users[0][1]="admin";
for (int t=1;t<101;t++){
users[t][0]="Cliente "+t;
users[t][1]="Clave "+t;
}
}
private static boolean usuarioValido(String nombre, String clave) {
if(nombre == null || clave == null)
return false;
for(int t=0; t<users.length; t++) {
if(users[t][0].equals(nombre) && users[t][1].equals(clave))
return true;
}
return false;
}
...
... public void login(ActionEvent actionEvent) {
RequestContext context = RequestContext.getCurrentInstance();
FacesMessage msg = null;
if (usuarioValido(nombre,clave)) {
logeado = true;
msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Bienvenid@", nombre);
logeado = false;
msg = new FacesMessage(FacesMessage.SEVERITY_WARN, "Login Error", "Credenciales no válidas"); } ... ...
Bien, el código nuevo reemplaza el algoritmo de verificación de cuenta, antes solo admitía el usuario "admin" con la clave "admin", ahora sigue admitiendo al mismo usuario, pero además se crean 100 cuentas más cuyos nombres son"Cliente 1", "Cliente 2", ... , "Cliente 100" y cuyas respectivas claves son: "Clave 1", "Clave 2", ... , "Clave 100". Hay que tener en cuenta que se generan con espacios en blanco, tanto el nombre como la clave de cada uno de esos usuarios.
Algo fundamental para lo que veremos más adelante, es que los nombres de usuario generados, coinciden exactamente con los nombres de clientes que tenemos en la base de datos.La validación en si la derivamos a un método llamado usuarioValido(), el método es por demás sencillo. Luego reemplazamos la lógica del if(...) que se encuentra en el método login() para hacer uso del nuevo algoritmo de validación.
Aplicando el filtrado automático
Hasta aquí no hemos hecho nada para que los datos se filtren automáticamente, a esto lo haremos en el componente que mantiene las listas de ventas en el servidor "VentasBean.java": Lo primero es hacer que la lista ya no se genere para todos igual, para ello comentaremos el llamando en el constructor de la clase:public VentasBean() {
//processList(null);
}
Lo siguiente es modificar la sentencia sql con la que se obtienen los datos para añadirle un parámetro de filtrado:
private String sql = "SELECT year(fecha) as anio, month(fecha) as mes, zona, cliente, sum(importe*cantidad) as ventas FROM dw_ventasfact v INNER JOIN clientes c ON v.idCliente=c.idCliente INNER JOIN zonas z ON z.idZona=c.idZona WHERE cliente like ?GROUP BY zona, cliente, anio, mes ORDER BY anio,mes,zona,cliente";Filtramos las ventas por nombre de cliente, utilizaremos el nombre del usuario logueado para reemplazar el parámetro ?, en el caso de ser "admin" el que se loguea se utilizar se utilizará el comodín '%' lo que implica que "admin" verá la lista completa. A esto lo hace el método que encapsula la lista con las ventas getVentas() que al inicio crea la lista solo si no existe, esto mejora la respuesta, ya que solo se calcula una vez por usuario, la desventaja es que si los datos cambian, este cambio no se verá reflejado. public List<Venta> getVentas() {
if(ventas==null){
HttpSession session = (HttpSession) FacesContext.getCurrentInstance() .getExternalContext().getSession(false); LoginBean loginBean = (LoginBean) session.getAttribute("loginBean");
String parametroNombre=loginBean.getNombre();
if(parametroNombre.equals("admin")){
parametroNombre="%";
}
processList(new String[]{parametroNombre});
}
return ventas;
}
El método ahora funciona como un singleton por sesión, esto es, solo existirá una lista por sesión de usuario. Con las tres líneas que siguen obtenemos el nombre del usuario logueado:
HttpSession session = (HttpSession) FacesContext.getCurrentInstance() .getExternalContext().getSession(false);LoginBean loginBean = (LoginBean) session.getAttribute("loginBean");
String parametroNombre=loginBean.getNombre();
Luego, si se trata de "admin" colocamos en el nombre el comodín '%', de esta manera la variable parametroNombrecontendrá el nombre del usuario logueado o, si se trata de "admin", el comodín, en otras palabras, contendrá el valor exacto que esperamos en el parámetro de la consulta. Finalmente llamamos a processList() con ese valor de parámetro. Ahora si probamos logearnos, por ejemplo con "Cliente 3"/"Clave 3", veremos que la lista se filtra automáticamente como se ve en la siguiente figura:
Conclusión
Lo que hemos visto demuestra la potencia del encapsulamiento y la las capas de abstracción que promueven los frameworks como JSF y PrimeFaces. Aún falta mucho por recorrer y, definitivamente, muchas mejorar por implementa.
Eso es todo, recuerden que disponen del proyecto eclipse completo en: https://github.com/magm3333/workspace-pftuto Saludos Mariano
Integrar Spring en nuestro proyecto JSF/PrimeFaces
Integrar Spring en nuestro proyecto JSF/PrimeFaces magm 22 Marzo, 2013 - 20:42Hola Gente,
avancemos un poco más en nuestro proyecto.
En esta ocasión, la idea es incorporar Spring al proyecto JSF/PrimeFaces que venimos desarrollando.
Introducción:
Antes de comenzar diré un par de palabras sobre Spring, comencemos por decir que es uno de los frameworks más extendidos para desarrollo de aplicaciones Java que requieran escalar, fácil mantenimiento y testing entre otras cosas. En este tutorial no me extenderé en teoría acerca de Spring, para ello existen muchos y muy buenos tutoriales y documentos, por ejemplo este.
Objetivo:
Configurar Spring y hacer que maneje nuestros beans, beans que hasta ahora manejaba JSF.
Descargando Spring:
Lo primero que debemos hacer es descargar Spring, se puede descargar de este sitio. Yo he utilizado particularmente la versión 3.2.1 que he descargado desde aquí, aunque en el momento de escribir este tutorial ya se encuentra disponible la versión 3.2.2.
Configurando las librerías en el proyecto:
Una vez que descargamos Spring, debemos descomprimir el archivo y copiar (o linkear) a nuestro proyecto (carpetaWEB-INF/lib) los siguientes archivos:
spring-beans-3.2.1.RELEASE.jar
spring-context-3.2.1.RELEASE.jar
spring-context-support-3.2.1.RELEASE.jar
spring-core-3.2.1.RELEASE.jar
spring-expression-3.2.1.RELEASE.jar
spring-instrument-3.2.1.RELEASE.jar
spring-instrument-tomcat-3.2.1.RELEASE.jar
spring-instrument-tomcat-3.2.1.RELEASE.jar
spring-jdbc-3.2.1.RELEASE.jar
spring-tx-3.2.1.RELEASE.jar
spring-web-3.2.1.RELEASE.jar
spring-webmvc-3.2.1.RELEASE.jar
También debemos copiar la implementación de JSF que hasta ahora la utilizábamos mediante una referencia de librería de usuario:
javax.faces-2.1.17.jar
También necesitamos:
commons-logging-1.1.1.jar
Antes teníamos:
mysql-connector-java-5.1.17.jar
primefaces-3.5.jar
Nos debería quedar algo así:
Archivo de configuración de Spring:Spring no es la excepción, basa su funcionamiento en archivos de configuración, el más importante es el que crearemos a continuación. En la carpeta WEB-INF debemos crear un archivo llamado applicationContext.xml, y el contenido será:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd">
</beans>
Configuración de web.xml
Ahora debemos decirle a nuestra aplicación web cual es el componente de Spring que procesará el inicio de Spring, entre otras cosas leer el archivo (applicationContext.xml) y crear el contexto y cual procesará los requerimientos http, para ello editamos el archivo WEB-INF/web.xml y agregamos los siguientes listeners:
...
...
<display-name>pf</display-name>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<welcome-file-list>
<welcome-file>login.xhtml</welcome-file>
</welcome-file-list>
...
...
Derivación del manejo de beans:
A esta altura ya tenemos funcionando Spring, solo restan dos tareas:
1) "Decirle" a JSF que Spring manejará los beans. Siempre es una clase la encargada de procesar y resolver las expresiones utilizadas mediante lenguaje de expresiones (EL), JSF tiene una clase asignada por defecto, lo que haremos es cambiar esa clase, para ello editamos el archivo de configuración de JSF WEB-INF/faces-config.xml y agregamos al inicio:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" version="2.1">
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
...
...
2) "Mudar"la configuración del manejo de beans desde JSF a Spring, para los cual:
2.a) Editamos el archivo de configuración de JSF (el del punto anterior) WEB-INF/faces-config.xml y comentamos (<!-- -->) las definiciones de beans que teníamos, el archivo quedará así:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" version="2.1">
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
<!--
<managed-bean>
<managed-bean-name>gaugeBean</managed-bean-name>
<managed-bean-class>ar.com.magm.web.primefaces.GaugeBean</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>ventasBean</managed-bean-name>
<managed-bean-class>ar.com.magm.web.primefaces.VentasBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>loginBean</managed-bean-name>
<managed-bean-class>ar.com.magm.web.primefaces.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
-->
</faces-config>
2.b) Editamos el archivo de configuración de Spring WEB-INF/applicationContext.xml y agregamos la configuración necesaria, el archivo quedará así:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="gaugeBean" class="ar.com.magm.web.primefaces.GaugeBean" scope="singleton"/>
<bean id="ventasBean" class="ar.com.magm.web.primefaces.VentasBean" scope="session"/>
<bean id="loginBean" class="ar.com.magm.web.primefaces.LoginBean" scope="session"/>
</beans>
Notemos que para definir cada bean, es necesario solo un elemento XML si usamos Spring. Lo que en JSF definía el nombre o id del bean (<managed-bean-scope>) era un elemento, en el caso de Spring se trata del atributo id, al igual que la clase que implementa el bean que en Spring también es un atributo. En Spring definimos el alcance y ciclo de vida de los beans con el atributo scope, en el caso de session, es igual que lo hacíamos con JSF, en el caso que necesitemos que un bean sea único para toda la aplicación nuestro scope será ahora singleton.
Bien, eso es todo, si probamos la aplicación, esta seguirá comportándose como antes. En la interface gráfica no hay diferencias.
En breve continuaremos agregando características muy interesantes a nuestro proyecto.
Recuerden que pueden descargarse el war con el proyecto funcionando desde aquí, también pueden sacar del war las librerías (.jar). El proyecto completo sigue estando aquí.
Espero les sea de utilidad.
Tutorial anterior: https://jmagm.blogspot.com/2013/02/control-de-acceso-datos-automatizado.html
Próximo tutorial:
Saludos
Internacionalizando nuestra aplicación PrimeFaces/JSF/Spring
Internacionalizando nuestra aplicación PrimeFaces/JSF/Spring magm 26 Marzo, 2013 - 20:29Hola gente,
intentaremos dar un paso más en la construcción de la aplicación PrimeFaces/JSF/Spring, en este caso agregaremos la característica de Localización (I10N) mediante el agregado de componentes de Internacionalización (I18N).
Introducción
Según wikipedia "La internacionalización es el proceso de diseñar software de manera tal que pueda adaptarse a diferentes idiomas y regiones sin la necesidad de realizar cambios de ingeniería ni en el código. La localización es el proceso de adaptar el software para una región específica mediante la adición de componentes específicos de un locale y la traducción de los textos, por lo que también se le puede denominar regionalización. No obstante la traducción literal del inglés es la más extendida.
En informática, un locale es un conjunto de parámetros que define el idioma, país y cualquier otra preferencia especial que el usuario desee ver en su interfaz de usuario.
Generalmente un identificador de locale consiste como mínimo de un identificador de idioma y un identificador de región. Este concepto es de fundamental importancia en el campo de la localización de idiomas."
Esto parece complejo, pero es sencillo de implementar con JSF.
Manos a la obra!
Archivos de recursos con mensajes
Los archivos de recursos con mensajes son archivos de texto plano que contienen una serie de claves y cada clave contiene un valor asociado, los valores serán las cadenas con mensajes internacionalizados. Luego estos archivos son transformados en objetos java.util.Map para su fácil manipulación.
Crearemos dos archivos de recursos en el paquete ar.com.magm.recursos, los recursos son simples archivos planos, se pueden crear haciendo botón derecho sobre el paquete y seleccionando New > Other... / General / File
Los archivos a crear y sus respectivos contenidos son:
mensajes.properties
lbl.login=Ingreso
lbl.username=Usuario
lbl.password=Clave
lbl.welcome=Bienvenid@
lbl.error.login=Error en el ingreso
lbl.invalidcredentials=Credenciales inválidas
lbl.ventas=Ventas
lbl.gauge=Tacómetro
lbl.logout=Salir
lbl.all.m=Todos
lbl.all.f=Todas
lbl.months=Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Agosto,Septiembre,Octubre,Noviembre,Diciembre
lbl.table.sales.empty=No hay ventas con este criterio de filtrado
lbl.find.all=Buscar en todos
lbl.col.zone=Zona
lbl.col.customer=Cliente
lbl.col.year=Año
lbl.col.month=Mes
lbl.col.salesamount=Importe Ventas
lbl.gauge.title=Tacómetro Personalizado
mensajes_en.properties
lbl.login=Login
lbl.username=User
lbl.password=Password
lbl.welcome=Welcome
lbl.error.login=Login error
lbl.invalidcredentials=Invalid credentials
lbl.ventas=Sales
lbl.gauge=Gauge
lbl.logout=Logout
lbl.all.m=All
lbl.all.f=All
lbl.months=January,February,March,April,May,June,July,August,September,October,November,December
lbl.table.sales.empty=There are no sales for this filter criteria
lbl.find.all=Find all
lbl.col.zone=Zone
lbl.col.customer=Customer
lbl.col.year=Year
lbl.col.month=Month
lbl.col.salesamount=Sales amount
lbl.gauge.title=Custom Gauge
Tres cosas son fáciles de notar con solo ver el contenido y nombre de los archivos, una es que el nombre del archivo contiene como parte de el el locale, en el caso de mensajes_en.properties, el locale es en, si por ejemplo necesitásemos crear un archivo con información de localización para Francia, el archivo debería llamarse mensajes_fr.properties. La extensión debe ser .properties, aunque ya no la mencionaremos más. Lo otro que se puede notar es que ambos archivos poseen las mismas claves, solo los valores son diferentes y por último se nota que uno de los archivos no tiene locale, este es el archivo con la localización por defecto o base, en nuestro caso es por Español.
Configuración de archivos de recursos en JSF
Ahora debemos "decirle" a JSF cual es el archivo de recursos base y cual será el nombre del bean que lo representará en tiempo de ejecución, además configuraremos el locale por defecto y los locales disponibles. Para ello debemos editar el archivo WEB-INF/faces-config.xml y agregar:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://java.sun.com/xml/ns/javaee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd" version="2.1">
<application>
<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
<locale-config>
<default-locale>es</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>ar.com.magm.recursos.mensajes</base-name>
<var>msg</var>
</resource-bundle>
</application>
...
...
Creo que el agregado no merece mucha explicación, la estructura XML y los nombres de elemento hablan por si solos. A partir de aquí solo debemos recordar que accederemos al recurso mediante msg, ya que <var>msg</var> y que el recurso es un mapa, por ello haremos cosas como msg['clave'].
Hasta aquí hemos hecho todo lo necesario a nivel de infraestructura para dar soporte a I18N y L10N, ahora debemos recodificar algunos componentes para que esto realmente funcione.
Componentes del lado del cliente
A continuación copiaré los códigos de los archivos a modificar y resaltaré las modificaciones en negrita.
login.xhtml
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:f="https://java.sun.com/jsf/core"
xmlns:p="https://primefaces.org/ui">
<h:head></h:head>
<h:body style="text-align:center">
<p:growl id="mensajes" showDetail="true" life="2000" />
<h:form>
<p:panel header="#{msg['lbl.login']}" style="width:300px">
<h:panelGrid columns="2" cellpadding="5">
<h:outputLabel for="username" value="#{msg['lbl.username']}:" />
<p:inputText value="#{loginBean.nombre}" id="username" required="true" label="username" />
<h:outputLabel for="password" value="#{msg['lbl.password']}:" />
<p:password value="#{loginBean.clave}" id="password" required="true" label="password" />
<f:facet name="footer">
<p:commandButton id="loginButton" value="#{msg['lbl.login']}" actionListener="#{loginBean.login}" update=":mensajes" oncomplete="manejarLogin(xhr, status, args)" />
</f:facet>
</h:panelGrid>
</p:panel>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
function manejarLogin(xhr, status, args) {
if (!args.validationFailed && args.estaLogeado) {
setTimeout(function() {
window.location = args.view;
}, 1000);
}
}
//]]>
</script>
</html>
menu.xhtml
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:f="https://java.sun.com/jsf/core"
xmlns:p="https://primefaces.org/ui">
<h:head></h:head>
<h:body>
<p:layout fullPage="true">
<p:layoutUnit position="center">
<iframe id="frame" src="ventas.xhtml" style="width: 100%; height: 100%; text-align: center;" seamless='seamless' />
</p:layoutUnit>
</p:layout>
<h:form id="form">
<p:dock>
<p:menuitem value="#{msg['lbl.ventas']}" icon="/images/ventas.png" url="javascript:cambioPagina('ventas.xhtml')" />
<p:menuitem value="#{msg['lbl.gauge']}" icon="/images/gauge.jpg" url="javascript:cambioPagina('gauge.xhtml')" />
<p:menuitem value="#{msg['lbl.logout']}" icon="/images/logout.png" actionListener="#{loginBean.logout}" oncomplete="logout(xhr, status, args)" />
</p:dock>
</h:form>
</h:body>
<script type="text/javascript">
//<![CDATA[
var actual = 'ventas.xhtml';
function cambioPagina(pagina) {
if (pagina != actual) {
$('#frame').attr('src', pagina);
actual=pagina;
}
}
function logout(xhr, status, args) {
setTimeout(function() {
window.location = 'login.xhtml';
}, 500);
}
//]]>
</script>
</html>
gauge.xhtml
<html xmlns="https://www.w3c.org/1999/xhtml"
xmlns:h="https://java.sun.com/jsf/html"
xmlns:p="https://primefaces.org/ui">
<h:head></h:head>
<h:body>
<h:form id="formGauge">
<p:poll interval="2" update="gauge" />
<p:meterGaugeChart id="gauge" value="#{gaugeBean.meterGaugeModel}" showTickLabels="false" labelHeightAdjust="110" intervalOuterRadius="130" seriesColors="66cc66, 93b75f, E7E658, cc6666" style="width:400px;height:250px" title="#{msg['lbl.gauge.title']}" label="km/h" />
</h:form>
</h:body>
</html>
Fácil no?, como se puede observar solo hay que sustituir la cadena que se desee por la expresión EL de forma: #{msg['clave']}
Componentes del lado del server
Los componentes del lado del server también contienen datos que se muestran en las vistas del usuario y deben ser internacionalizados. A continuación se mostrarán las porciones de código y las clases que hay que modificar.
ar.com.magm.web.primefaces.LoginBean (solo método login())
public void login(ActionEvent actionEvent) {
RequestContext context = RequestContext.getCurrentInstance();
FacesContext jsfCtx= FacesContext.getCurrentInstance();
ResourceBundle bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
FacesMessage msg = null;
if (usuarioValido(nombre, clave)) {
logeado = true;
msg = new FacesMessage(FacesMessage.SEVERITY_INFO, bundle.getString("lbl.welcome"), nombre);
} else {
logeado = false;
msg = new FacesMessage(FacesMessage.SEVERITY_WARN,bundle.getString("lbl.error.login"), bundle.getString("lbl.invalidcredentials"));
}
FacesContext.getCurrentInstance().addMessage(null, msg);
context.addCallbackParam("estaLogeado", logeado);
if (logeado)
context.addCallbackParam("view", "menu.xhtml");
}
Con las siguientes líneas:
FacesContext jsfCtx= FacesContext.getCurrentInstance();
ResourceBundle bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
Obtenemos una referencia al archivo de recursos en la variable bundle.
Luego para obtener un valor utilizamos: bundle.getString("clave")
ar.com.magm.web.primefaces.VentasBean
public class VentasBean implements Serializable {
private static final long serialVersionUID = -6690574219803425728L;
private String[] meses ;
private String sql = "SELECT year(fecha) as anio, month(fecha) as mes, zona, cliente, sum(importe*cantidad) as ventas FROM dw_ventasfact v INNER JOIN clientes c ON v.idCliente=c.idCliente INNER JOIN zonas z ON z.idZona=c.idZona WHERE cliente like ? GROUP BY zona, cliente, anio, mes ORDER BY anio,mes,zona,cliente";
private List<Venta> ventas;
private List<Venta> ventasFiltradas;
private List<String> zonas;
private FacesContext jsfCtx;
private ResourceBundle bundle;
public VentasBean() {
jsfCtx = FacesContext.getCurrentInstance();
bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
// processList(null);
}
public SelectItem[] getMesesOptions() {
meses=bundle.getString("lbl.months").split(",");
SelectItem[] r = new SelectItem[13];
for (int t = 0; t < meses.length; t++)
r[t + 1] = new SelectItem(meses[t], meses[t]);
return r;
}
...
...
public SelectItem[] getZonasOptions() {
SelectItem[] r = new SelectItem[zonas.size() + 1];
r[0] = new SelectItem("", bundle.getString("lbl.all.f"));
for (int t = 0; t < zonas.size(); t++)
r[t + 1] = new SelectItem(zonas.get(t), zonas.get(t));
return r;
}
private void processList(Object args[]) {
meses=bundle.getString("lbl.months").split(",");
ventas = new ArrayList<Venta>();
zonas = new ArrayList<String>();
...
...
En el caso de VentasBean solo hemos utilizado la internacionalización para la opción "Todos"/"Todas" de las listas de filtrado y para internacionalizar los meses.
Probando la Aplicación
Para probar la aplicación, solo debemos reconfigurar nuestro brower y cambiar entre los idiomas Español e Inglés, a continuación adjunto un video del funcionamiento de la aplicación internacionalizada.
Como siempre el proyecto completo y el WAR en el repo de GITHUB: https://github.com/magm3333/workspace-pftuto
Espero les sea útil.
Saludos
Mariano
Excelente amigo gracias. Muy
Excelente amigo gracias. Muy buena la explicación.
- Inicie sesión para enviar comentarios
Mejorando el uso de Hibernate en nuestra aplicación PrimeFaces/JSF/Spring
Mejorando el uso de Hibernate en nuestra aplicación PrimeFaces/JSF/Spring magm 6 Abril, 2013 - 03:09
Hola Estimad@s,
en esta ocasión intentaremos mejorar el uso de Hibernate en nuestra aplicación web.
Objetivos
- Utilizar una versión mejorada de HibernateUtil (fuente)
- Crear una sesión Hibernate por petición web y encapsularla en su propio hilo para ser coherentes con lo requerido por la documentación oficial y poder implementar el patrón Open Session In View
- Unificar y simplificar el tratamiento de excepciones (fuente)
- Implementar el patrón DAO
En esta instancia no descargaremos más librerías, ya tenemos todas las necesarias para seguir adelante. Si utilizaremos nuevas clases, muchas de las cuales no se explicarán en profundidad porque pertenecen a otro curso/tutorial el cual está muy bien explicado y desarrollado con mucho detalle. Las clases base que utilizaremos son: ar/com/magm/persistencia/hibernate/util/GenericEventListenerImpl.java ar/com/magm/persistencia/hibernate/util/GenericIntegratorImpl.java ar/com/magm/persistencia/hibernate/util/HibernateUtil.java ar/com/magm/persistencia/hibernate/util/SessionFactoryImplThreadLocal.java ar/com/magm/persistencia/hibernate/util/HibernateUtilInternalState.java
Las clases anteriores contienen la lógica para crear sesiones Hibernate parametrizables y proporciona ayuda para implementar el patrón Open Session In View.
ar/com/magm/persistencia/dao/GenericDAO.java
Interface que define las operaciones de persistencia para los DAO.
ar/com/magm/persistencia/dao/hibernateimpl/GenericDAOImplHibernate.java Implementación DAO que utiliza Hibernate
ar/com/magm/persistencia/exception/Caption.java ar/com/magm/persistencia/exception/BussinessException.java ar/com/magm/persistencia/exception/BussinessMessage.java
Clases que permiten Simplificar y Unificar el manejo de Excepciones, de hecho solo se utilizará BussinessException sin por ello perder el origen de las excepciones.
ar/com/magm/persistencia/hibernate/web/HibernateContextListenerAndFilter.java
Clase que es un WebLIstener y un WebFilter a la vez.
El WebListener se encarga al inicio de la aplicación de inicializar la fábrica de sesiones de Hibernate y al final la cierra.
El WebFilter asegura que exista una sesión Hibernate en un hilo local por petición, de esta forma se puede implementar el patrón Open Session In View.
Y se encuentran en este archivo:
https://github.com/magm3333/workspace-pftuto/blob/master/paquetePersistencia.zip?raw=true
Implementación del patrón Data Access Object (DAO)
El patrón DAO nos permite crear una capa de abstracción entre nuestros objetos y algún sistema de almacenamiento.
Se trata de declarar las operaciones de persistencia que podremos realizar con nuestros objetos y luego tener las implementaciones particulares que deseemos. Trabajar con Spring nos ayudará a realizar la última tarea ya que mediante la inyección de dependencias nos abstraemos del problema de la implementación particular.
Comenzaremos creando la interface GenericDAO, la cual contendrá las operaciones básicas comunes a nuestros DAO, el código es el siguiente:
package ar.com.magm.persistencia.dao;
import java.io.Serializable;
import java.util.List;
import ar.com.magm.persistencia.exception.BussinessException;
public interface GenericDAO<T, ID extends Serializable> {
T create() throws BussinessException;
void saveOrUpdate(T entity) throws BussinessException;
T get(ID id) throws BussinessException;
void delete(ID id) throws BussinessException;
List<T> findAll() throws BussinessException;
}
Como se observa las operaciones son 5:
create: creará una nueva instancia de una entidad que puede persistirse.
saveOrUpdate: asegura la persistencia de una entidad, si no existe la inserta y si existe la actualiza
get: obtiene una instancia de una entidad desde el sistema de almacenamiento por medio de su clave.
delete: elimina una entidad por medio de su clave.
findAll: obtiene todas las entidades de un tipo desde el almacenamiento persistente.
Como puede observarse, tanto la entidad como su identificador son genéricos, en el próximo paso crearemos las versiones particulares para Cliente y Zona
ClienteDAO.java
package ar.com.magm.persistencia.dao;
import ar.com.magm.model.Cliente;
public interface ClienteDAO extends GenericDAO<Cliente, Integer> {
}
ZonaDAO.java
package ar.com.magm.persistencia.dao;
import ar.com.magm.model.Zona;
public interface ZonaDAO extends GenericDAO<Zona, Integer> {
}
Bien simple las versiones particulares no?
Para completar, nos resta realizar al menos una implementación y será para Hibernate.
Implementación DAO para Hibernate
Ya existe una clase llamada ar.com.magm.persistencia.dao.hibernateimpl.GenericDAOImplHibernate, la cual contiene la implementación de los métodos genéricos definidos en GenericDAO, el análisis de esta clase escapa a este tutorial y además ya existe una buena explicación aquí, nos remitiremos solo a crear las implementaciones particulares para ClienteDAO y ZonaDAO.
ClienteDAOImplHibernate.java
package ar.com.magm.persistencia.dao.hibernateimpl;
import ar.com.magm.model.Cliente;
import ar.com.magm.persistencia.dao.ClienteDAO;
public class ClienteDAOImplHibernate extends
GenericDAOImplHibernate<Cliente, Integer> implements ClienteDAO {
}
ZonaDAOImplHibernate.java
package ar.com.magm.persistencia.dao.hibernateimpl; import ar.com.magm.model.Zona;
import ar.com.magm.persistencia.dao.ZonaDAO;
public class ZonaDAOImplHibernate extends
GenericDAOImplHibernate<Zona, Integer> implements ZonaDAO {
}
Buen enfoque el uso de Genéricos no?, teniendo una buena implemetación genérica, las implementaciones particulares son triviales.
Usando Spring para independizarnos de la implementación
Ya tenemos nuestros DAO definidos y una implementación particular para Hibernate, pero podríamos tener cuantas implementaciones deseemos, si ese fuera el caso, sería muy bueno no tener que reescribir clases ni recompilarlas cuando cambiemos de implementación, aquí es donde Spring "nos da una mano".
A continuación crearemos un controlador que procesará peticiones requiriendo información de clientes, en futuros tutoriales formalizaremos el concepto de controlador y veremos como utilizar el framework Modelo Vista Controlador(MVC) de Spring.
El controlador
ClienteTestController.java
package ar.com.magm.model.dao.controller;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import ar.com.magm.model.Cliente;
import ar.com.magm.persistencia.dao.ClienteDAO;
import ar.com.magm.persistencia.exception.BussinessException;
@Component("clienteController")
@Scope("request")
public class ClienteTestController {
@Autowired
private ClienteDAO clienteDAO;
public void processRequest(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String idCl = request.getParameter("idCliente");
int idCliente = 33;
if (idCl != null)
idCliente = Integer.parseInt(idCl);
String salida;
try {
Cliente cl = clienteDAO.get(idCliente);
StringBuilder str = new StringBuilder();
if (cl != null) {
str.append("Cliente: " + cl.getCliente() + " (Id: "
+ cl.getIdCliente() + ") - Cta Habilitada: "
str.append("\tZona: " + cl.getZona().getZona() + " (Id: "
+ cl.getZona().getIdZona() + ")");
} else {
str.append("No existe el cliente con id=" + idCliente);
}
salida = str.toString();
} catch (BussinessException ex) {
salida = "Error el obtener el cliente\n" + ex.getMessage();
}
response.getWriter().print(salida);
response.getWriter().flush();
}
}
Esta clase controlador es un bean manejado por Spring, solo que no se ha utilizado el archivo xml (applicationContext.xml) para configurarlo, en vez de ello se han utilizado anotaciones, en particular:
@Component("clienteController")
@Scope("request")
sería lo mismo que colocar:
<bean id="clienteController" class="ar.com.magm.model.dao.controller.ClienteTestController" scope="request"/>el archivo xml (applicationContext.xml).
El scope = "request" indica que se creará una instancia del controlador por cada petición del cliente, es un tiempo de vida muy corto y solo dura lo que dura la petición.
Respecto a la línea anotada con:
@Autowired
private ClienteDAO clienteDAO;
si analizamos la clase con detenimiento, podremos notar que nunca se instancia de forma explícita (new), esta es la línea que nos abstrae de la implementación, será Spring el encargado de inyectar aquí el código (o dependencia) correcto, Spring buscará alguna clase que implemente alguna clase que implemente la interface ClienteDAO, más adelante, en el archivo de configuración (applicationContext.xml), le diremos a Spring cuales son las posibilidades que tiene, en otras palabras, cuales son las clases que implementan esta interfaz.
También notemos que con solo Cliente cl = clienteDAO.get(idCliente); obtenemos la instancia de la entidad Cliente que deseamos.
Podemos ver el manejo simplificado de excepciones, solo capturamos BussinessException, sin embargo siempre tendremos la posibilidad de obtener toda la información de la excepción particular cuando así lo deseemos.
Configurando la detección de anotaciones y los beans candidatos para la inyección de dependencias
Como he adelantado, debemos realizar un par de tareas de configuración en el archivo applicationContext.xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<context:component-scan base-package="ar.com.magm.model.dao.controller"/>
<bean class="ar.com.magm.persistencia.dao.hibernateimpl.ClienteDAOImplHibernate" />
<bean class="ar.com.magm.persistencia.dao.hibernateimpl.ZonaDAOImplHibernate" />
<bean id="gaugeBean" class="ar.com.magm.web.primefaces.GaugeBean" scope="singleton"/>
<bean id="ventasBean" class="ar.com.magm.web.primefaces.VentasBean" scope="session"/>
<bean id="loginBean" class="ar.com.magm.web.primefaces.LoginBean" scope="session"/>
</beans>
La línea <context:annotation-config/>, habilita el uso de anotaciones en Spring, con esta línea podemos usar anotaciones como @Autowired
Con la línea<context:component-scan base-package="ar.com.magm.model.dao.controller"/>
le indicamos a Spring a partir de que jerarquía de paquete deberá buscar los componentes marcados con la anotación @Component Por último las líneas <bean class="ar.com.magm.persistencia.dao.hibernateimpl.ClienteDAOImplHibernate" />
<bean class="ar.com.magm.persistencia.dao.hibernateimpl.ZonaDAOImplHibernate" />
Hacen que Spring cree una instancia de cada implementación particular de DAO para Hibernate, uno de estos beans será inyectado en:
@Autowired
private ClienteDAO clienteDAO;
Por ello, si quisiéramos cambiar de implementación, solo deberíamos modificar estas líneas y agregar las nuevas implementaciones al classpath.
Una prueba inyteresante podría ser agregar la siguiente línea a ClienteTestController:
...
Cliente cl = clienteDAO.get(idCliente);
System.out.println(clienteDAO.getClass().toString());
StringBuilder str = new StringBuilder();
...
Si lo ejecutamos veremos en la consola la siguiente salida:
class ar.com.magm.persistencia.dao.hibernateimpl.ClienteDAOImplHibernate
Con esto queda confirmado que la inyección de dependencia funciona correctamente.
Un servlet para probar que todo funcione
Crearemos ahora un servlet llamado TestHibernateConSpring, nos servirá, como el anterior, para probar que todo funcione correctamente. El código del servlet es el siguiente:
package ar.com.magm.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import ar.com.magm.model.dao.controller.ClienteTestController;
@WebServlet("/TestHibernateConSpring")
public class TestHibernateConSpring extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
ApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(this.getServletContext());
ClienteTestController controller = (ClienteTestController) applicationContext
.getBean("clienteController");
controller.processRequest(request, response);
}
}
En las líneas
ApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(this.getServletContext());
ClienteTestController controller = (ClienteTestController) applicationContext
.getBean("clienteController");
Obtenemos la instancia del controlador, recordemos que se crea una instancia por petición, por ello podemos decir que esta instancia es exclusiva para este requerimiento.
Luego la línea
controller.processRequest(request, response);
le cede el control a la instancia del controlador, de esta forma organizamos un poco mejor nuestra aplicación aunque todavía falta un tramo por recorrer. Creando una sesión Hibernate cuando se requiere Aún nos resta una cosa para poder probar nuestra aplicación. Modificaremos la clase: ar/com/magm/persistencia/hibernate/web/HibernateContextListenerAndFilter.java
La cual hemos explicado brevemente al comienzo de este texto.
Esta clase actúa entre otras cosas como filtro, los filtros se ejecutan antes que cualquier componente en un requerimiento, por otro lado se debe definir ante que requerimientos se debe ejecutar el filtro, esto se hace mediante la definición de patrones de URL. Esto hará que ante algunas URLs en particular se ejecute el filtro y ante otras no, por ejemplo no sería conveniente que este filtro cree sesiones Hibernate cuando la URL requiere algún recurso estático, por ejemplo un archivo .jpg o .gif. La modificación que debemos realizar es: antes: @WebFilter(urlPatterns = { "*.html" }) ahora: @WebFilter(urlPatterns = { "/TestHibernateConSpring" }) De esta forma solo se crearán recursos necesarios para la persistencia cuando sea necesario y no en otro momento, de esta forma ahorramos recursos en el server. En el futuro modificaremos nuevamente este componente. Probando Ahora vamos a nuestro navegador y con la url que ya hemos utilizado colocamos los datos de login:Cambiamos la url por: https://localhost:8080/pf/TestHibernateConSpring, presionamos enter y veremos un resultado similar al siguiente:
Agregar soporte para Hibernate a nuestra Aplicación PrimeFaces/JSF/Spring
Agregar soporte para Hibernate a nuestra Aplicación PrimeFaces/JSF/Spring magm 28 Marzo, 2013 - 20:55Hola Estimad@s,
seguimos avanzando con nuestra aplicación, en este post veremos como hacer para que nuestra aplicación tenga soporte para utilizar un mapeador Objeto-Relacional (ORM), particularmente Hibernate.
Introducción
Como puede notarse en los diferentes posts referidos a esta aplicación, en ningún caso profundizamos en los temas, solo vamos definiendo lo mínimo a medida que lo necesitamos, de lo contrario esto sería un "megamanual" y esa no es la intención. Para un manejo más profundo de la temática recomiendo este curso, el cual es de muy alta calidad, el titulo del curso es Curso de "Hibernate con Spring", aunque en realidad solo profundiza (y muy bien) con Hibernate. De he he utilizado algunas clases utilitarias que propone el autor del curso, oportunamente haré la referencia.
Solo diré que hoy en día el uso de los ORM está muy extendido y facilita enormemente la tarea de la persistencia y el mantenimiento de la aplicación, además de dar solución a diferentes problemas inherentes a la temática, por ejemplo: validaciones, caching, logging, tuning, independencia del SGBDR, etc. Hibernate es lejos el ORM Open Source más usado y robusto del que disponemos.
Descargando Hibernate
La versión que utilizaremos es la 4.1.10.Final y se la puede descargar desde:
hibernate-release-4.1.10.Final.zip (Sistemas tipo window$)
o
hibernate-release-4.1.10.Final.tgz (Sistemas tipo Unix)
Una vez descargada la versión, descomprimimos el archivo en una carpeta que denominaremos [HIBERNATE_HOME].
Descargando Hibernate Validator
También utilizaremos Hibernate Validator, al cual nos referiremos en el futuro, por ello debemos descargarlo desde:
hibernate-validator-4.3.1.Final-dist.zip (Sistemas tipo window$)
o
hibernate-validator-4.3.1.Final-dist.tar.gz (Sistemas tipo Unix)
Una vez descargada la versión, descomprimimos el archivo en una carpeta que denominaremos [HIBERNATEVALIDATOR_HOME].
Copiando las librerías a nuestro proyecto
Ahora debemos copiar los archivos jar a nuestra carpeta WEB-INF/lib, el listado de archivos necesarios es:
[HIBERNATE_HOME]/lib/required
antlr-2.7.7.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-4.0.1.Final.jar
hibernate-core-4.1.10.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.15.0-GA.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.0.Final.jar
[HIBERNATEVALIDATOR_HOME]/
hibernate-validator-4.3.1.Final.jar
hibernate-validator-annotation-processor-4.3.1.Final.jar
[HIBERNATEVALIDATOR_HOME]/lib/required
validation-api-1.0.0.GA.jar
[HIBERNATEVALIDATOR_HOME]/lib/optional
joda-time-1.6.jar
jsoup-1.6.1.jar
Archivo de configuración de Hibernate
El archivo de configuración principal de Hibernate es hibernate.cfg.xml y debemos colocarlo en el paquete por defecto de nuestras clases Java, en otras palabras en la carpeta src. El contenido inicial será:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
Donde:
la propiedad<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
define la conexión a la base de datos que utilizaremos, en este caso le diremos a Hibernate que utilice la fuente de datos JNDI que ya hemos definido en esta aplicación. Una forma de crear una conexión JDBC genérica sería: <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost/practico</property> <property name="connection.username">root</property> <property name="connection.password">xxxx</property>
la propiedad<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
Define la implementación del dialecto que se utilizará, en este caso MySQL
la propiedades
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
Definen que se mostrar las sentencias SQL que se generan en el uso de Hibernate y que a su vez se muestren formateadas para su mejor lectura.
por último, la propiedad<property name="hibernate.hbm2ddl.auto">update</property>
Define que las sentencias DDL que se generen serán en forma de delta, esto es que se generarán las modificaciones necesarias para realizar los mapeos correctos entre las clases java y las tablas de MySQL. Bien, hasta aquí hemos cubierto lo que yo llamo las necesidades básicas de infraestructura para que el framework funcione. A continuación haremos un prueba básica para corroborar el correcto funcionamiento. Beans que requieren persistencia Persistiremos dos beans, uno que representa un Cliente y el otro que representa una Zona a la cual el Cliente pertenece. Las tablas que almacenan a los clientes y zonas ya existen en la base de datos que estamos utilizando, el modelo relacional de esas dos tablas es:
public class Cliente { private String cliente; private boolean cuentaHabilitada; private int idCliente; private Zona zona;
public String getCliente() { return cliente; } public int getIdCliente() { return idCliente; } public Zona getZona() { return zona; } public boolean isCuentaHabilitada() { return cuentaHabilitada; } public void setCliente(String cliente) { this.cliente = cliente; } public void setCuentaHabilitada(boolean cuentaHabilitada) { this.cuentaHabilitada = cuentaHabilitada; } public void setIdCliente(int idCliente) { this.idCliente = idCliente; } public void setZona(Zona zona) { this.zona = zona; } } La clase que representa la zona (Zona.java) tiene el siguiente código: package ar.com.magm.model;
public class Zona { private int idZona; private String zona;
public int getIdZona() { return idZona; } public String getZona() { return zona; } public void setIdZona(int idZona) { this.idZona = idZona; } public void setZona(String zona) { this.zona = zona; } }
Archivos de mapeo de los beans
Es necesario "decirle" a Hibernate cuales son las correspondencias entre las clases Java y las tablas relacionales, Hibernate permite hacerlo con anotaciones o con archivos de configuración XML, he optado por los archivos XML.
Ambos archivos se alojan en el mismo paquete que las clases y son:
ar/com/magm/model/Cliente.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ar.com.magm.model.Cliente" table="clientes">
<id column="idCliente" name="idCliente" type="integer" />
<property name="cliente" />
<property name="cuentaHabilitada" />
<many-to-one name="zona" class="ar.com.magm.model.Zona" column="idZona"/>
</class>
</hibernate-mapping>
el elemento
<class name="ar.com.magm.model.Cliente" table="clientes">
Mapea la clase ar.com.magm.model.Cliente con la tabla clientes
el elemento<id column="idCliente" name="idCliente" type="integer" />
Define que la propiedad idCliente de la clase es la clave principal y que se corresponde con la columna idCliente y es de tipo entero.
los elementos
<property name="cliente" />
<property name="cuentaHabilitada" />
mapean los atributos simples cliente y cuentaHabilitada, a los tipos los calcula automáticamente Hibernate
el elemento<many-to-one name="zona" class="ar.com.magm.model.Zona" column="idZona"/>
define la propiedad compleja zona y la clave foránea que se debe utilizar para unir la tabla clientes con la tabla zonasque es idZona
ar/com/magm/model/Zona.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ar.com.magm.model.Zona" table="zonas">
<id column="idZona" name="idZona" type="integer" />
<property name="zona"/>
</class>
</hibernate-mapping>
No explicaré este archivo, ya que la explicación anterior es suficiente para entenderlo.
Nuevamente el archivo de configuración principal de Hibernate
Modificaremos ahora el archivo hibernate.cfg.xml agregando las siguientes líneas:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.datasource">java:/comp/env/jdbc/practico</property>
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="ar/com/magm/model/Cliente.hbm.xml" />
<mapping resource="ar/com/magm/model/Zona.hbm.xml" />
</session-factory>
</hibernate-configuration>
Ambos elementos le icen"a Hibernate cuales son los archivos de mapeos que debe tener en cuenta.
Un Servlet de prueba
Crearemos ahora un Servlet con el fin de probar el correcto funcionamiento de Hibernate y los mapeos que hemos definido, la clase tiene el siguiente código:
package ar.com.magm.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import ar.com.magm.model.Cliente;
@WebServlet("/TestHibernate")
public class TestHibernate extends HttpServlet {
private Session session;
public TestHibernate() {
Configuration configuration = new Configuration();
configuration.configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
SessionFactory sf = configuration.buildSessionFactory(serviceRegistry);
session = sf.openSession();
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int idCliente = 33;
Cliente cl = (Cliente) session.get(Cliente.class, idCliente);
StringBuilder str = new StringBuilder();
if (cl != null) {
str.append("Cliente: " + cl.getCliente() + " (Id: "
+ cl.getIdCliente() + ") - Cta Habilitada: "
+ cl.isCuentaHabilitada() + "\n");
str.append("\tZona: " + cl.getZona().getZona() + " (Id: "
+ cl.getZona().getIdZona() + ")");
} else {
str.append("No existe el cliente con id=" + idCliente);
}
response.getWriter().print(str);
response.getWriter().flush();
}
}
En el constructor obtenemos una sesión, la cual nos permitirá realizar operaciones con Hibernate.
Luego en el método doGet(), ejecutamos:
Cliente cl = (Cliente) session.get(Cliente.class, idCliente);
para que Hibernate obtenga los datos de la base de datos, instancie una clase Cliente y establezca los valores para todos los atributos que hemos mapeado. El resto del código es trivial, por ello omitiré cualquier comentario. Ejecutando el servlet Ahora vamos a nuestro navegador y con la url que ya hemos utilizado colocamos los datos de login:
Cambiamos la url por: https://localhost:8080/pf/TestHibernate, presionamos enter y veremos un resultado similar al siguiente:
Con esto hemos comprobado el correcto funcionamiento del framework.
Recomiendo ver la salida de la vista "Consola" de eclipse, en la cual se podrán apreciar las sentencias SQL que ha emitido Hibernate para obtener los datos relacionales con los cuales se crearon las instancias de las clases Cliente yZona.
En mi caso es la siguiente:
Hibernate:
select
cliente0_.idCliente as idCliente0_0_,
cliente0_.cliente as cliente0_0_,
cliente0_.cuentaHabilitada as cuentaHa3_0_0_,
cliente0_.idZona as idZona0_0_
from
clientes cliente0_
where
cliente0_.idCliente=?
Hibernate:
select
zona0_.idZona as idZona1_0_,
zona0_.zona as zona1_0_
from
zonas zona0_
where
zona0_.idZona=?
Bien, eso es todo por ahora.
Espero les sea útil.
Saludos
Mariano
Migrando la lista de ventas a Hibernate en nuestra aplicación PrimeFaces/JSF/Spring/Hibernate
Migrando la lista de ventas a Hibernate en nuestra aplicación PrimeFaces/JSF/Spring/Hibernate magm 9 Abril, 2013 - 18:59Hola gente,
seguimos con nuestra aplicación, en esta ocasión migraremos la obtención de la lista de ventas para que sea manejada por Hibernate.
Objetivo
Utilizar la capacidad de trabajar con consultas sql nativas parametrizadas de Hibernate.
Desarrollo
En esta ocasión no es mucho el trabajo, comenzaremos configurando:
ar.com.magm.web.primefaces.VentasBean
...
...
SessionFactory sessionFactory;
public VentasBean() {
jsfCtx = FacesContext.getCurrentInstance();
bundle = jsfCtx.getApplication().getResourceBundle(jsfCtx, "msg");
sessionFactory = HibernateUtil.getSessionFactory();
// processList(null);
}
Solo agregamos una instancia de SessionFactory para poder trabajar con Hibernate en las instancias de esta clase.
Luego debemos modificar el método que genera la lista de ventas:
private void processList(Object args[]) {
meses = bundle.getString("lbl.months").split(",");
ventas = new ArrayList<Venta>();
zonas = new ArrayList<String>();
Session session = sessionFactory.getCurrentSession();
Query query = session.createSQLQuery(sql);
if (args != null) {
for (int t = 0; t < args.length; t++) {
query.setParameter(0, args[t]);
}
}
List<Object[]> vtaTmp = query.list();
for (Object[] vo : vtaTmp) {
Venta venta = new Venta(vo[2].toString(),
vo[3].toString(),
Integer.parseInt(vo[0].toString()),
Integer.parseInt(vo[1].toString()),
meses[Integer.parseInt(vo[1].toString()) - 1],
Double.parseDouble(vo[4].toString()));
ventas.add(venta);
if (!zonas.contains(venta.getZona()))
zonas.add(venta.getZona());
}
/*
ServletContext sc = (ServletContext) FacesContext.getCurrentInstance()
.getExternalContext().getContext();
Connection cn = (Connection) sc.getAttribute("datasource");
try {
PreparedStatement pst = cn.prepareStatement(sql);
if (args != null) {
for (int t = 0; t < args.length; t++) {
pst.setObject(t + 1, args[t]);
}
}
ResultSet rs = pst.executeQuery();
while (rs.next()) {
String zona = rs.getString("zona");
Venta venta = new Venta(zona, rs.getString("cliente"), rs.getInt("anio"),
rs.getInt("mes"), meses[rs.getInt("mes") - 1],
rs.getDouble("ventas"));
ventas.add(venta);
if (!zonas.contains(zona))
zonas.add(zona);
}
} catch (SQLException e) {
e.printStackTrace();
}
*/
}
Lo primero que haremos en este método es inhabilitar todo el código antiguo, en aquel en que utilizábamos JDBC. Se puede ver que hemos utilizado comentarios de bloque: /* .. */
Luego en la línea
Query query = session.createSQLQuery(sql);
Obtenemos un objeto Query, el cual nos permite trabajar con sentencias SQL nativas, el método recibe como parámetro la consulta, que no es ni más ni menos que la que ya estábamos utilizando:
for (int t = 0; t < args.length; t++) {
query.setParameter(0, args[t]);
}
}
En la líneaList<Object[]> vtaTmp = query.list();
obtenemos una lista que en cada elemento contiene un arreglo de objetos que representa una fila, en nuestro caso una entidad de ventas. El orden de los elementos del array coinciden con lo definido en la consulta SQL, en nuestro caso: [0]=año [1]=mes [2]=zona [3]=cliente [4]=ventas
El código que sirve para poblar la lista (el ciclo while) casi no ha cambiado, solo se reemplazó la línea que instancia la venta, ahora se utilizan los elementos del array y también se reutiliza la zona ya obtenida, por ello se quitó la primera línea en la cual se obtenía.
Esta es toda la modificación que hay que realizar en cuanto a código, ahora solo falta crear sesiones para que puedan ser utilizadas en este componente para lo cual modificaremos HibernateContextListenerAndFiltercomo ya lo hemos hecho antes: ... @WebFilter(urlPatterns = { "/TestHibernateConSpring", "*.xhtml" }) ... Solo agregamos el patrón "*.xhtml" para que se creen sesiones ante peticiones de componentes JSF. Bien, ya podemos probar y no notaremos ninguna diferencia, todo seguirá funcionando como antes del lado del cliente, solo que ahora Hibernate maneja algo más. En la consola podemos ver las sentencias SQL que Hibernate envía al motor. Espero les sea de utilidad Saludos
Reimplementar administración de Usuarios en nuestra aplicación PrimeFaces/JSF/Spring/Hibernate (Parte 1/2)
Reimplementar administración de Usuarios en nuestra aplicación PrimeFaces/JSF/Spring/Hibernate (Parte 1/2) magm 23 Abril, 2013 - 16:25Hola Estimad@s,
a continuación comenzaremos con una serie de cambios en nuestra aplicación a fin de implementar la persistencia de las cuentas de usuario con Hibernate, en esta primera parte solo inicializaremos las cuentas de usuario de los clientes y la cuenta admin, pero nos servirá para reforzar lo que hemos venido haciendo en los últimos posts.
Desarrollo
Comenzamos creando la clase Usuario, la cual encapsulará los datos pertenecientes a una cuenta de usuario.
Creamos las clases nuevas
Usuario.java (representa los datos de cuenta de un usuario)
package ar.com.magm.model;
public class Usuario {
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
private int id;
private String nombre;
private String password;
private boolean admin;
}
Cliente.java (agregamos al final de la clase el atributo usuario)
...
...
private Usuario usuario;
public Usuario getUsuario() {
return usuario;
}
public void setUsuario(Usuario usuario) {
this.usuario = usuario;
}
}
Creamos los mapeos nuevos
Usuario.hbm.xml (mapeo nuevo)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "https://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="ar.com.magm.model.Usuario" table="usuarios">
<id column="id" name="id" type="integer" />
<property name="nombre" />
<property name="password" />
<property name="admin" />
</class>
</hibernate-mapping>
Cliente.hbm.xml (modificamos el existente)
...
...
<many-to-one name="zona" class="ar.com.magm.model.Zona"
column="idZona"/>
<many-to-one name="usuario" class="ar.com.magm.model.Usuario"
column="idUsuario"/>
</class>
...
...
Hibernate.cfg.xml (modificamos el existente)
...
...
<mapping resource="ar/com/magm/model/Usuario.hbm.xml" />
<mapping resource="ar/com/magm/model/Cliente.hbm.xml" />
...
...
Definimos e implementamos los DAO
Lo que se plantea a continuación debería servirnos para reforzar el procedimiento a seguir para crear un nuevo DAO para una nueva clase del modelo.
UsuarioDAO.java (primero la interfaz con los servicios)
package ar.com.magm.persistencia.dao;
import ar.com.magm.model.Usuario;
public interface UsuarioDAO extends GenericDAO<Usuario, Integer> {}
UsuarioDAOImplHibernate.java (luego la implementación particular para Hibernate)
package ar.com.magm.persistencia.dao.hibernateimpl;
import ar.com.magm.model.Usuario;
import ar.com.magm.persistencia.dao.UsuarioDAO;
public class UsuarioDAOImplHibernate extends
GenericDAOImplHibernate<Usuario, Integer> implements UsuarioDAO {
}
applicationContext.xml (modificamos el existente)
...
...
<bean class="ar.com.magm.persistencia.dao.hibernateimpl.ZonaDAOImplHibernate" />
<bean class="ar.com.magm.persistencia.dao.hibernateimpl.UsuarioDAOImplHibernate" />
...
...
Creamos el controlador
InicializaCuentasClientesController.java
package ar.com.magm.model.dao.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import ar.com.magm.model.Cliente;
import ar.com.magm.model.Usuario;
import ar.com.magm.persistencia.dao.ClienteDAO;
import ar.com.magm.persistencia.dao.UsuarioDAO;
import ar.com.magm.persistencia.exception.BussinessException;
@Component("inicializaCuentasClientesController")
@Scope("request")
public class InicializaCuentasClientesController {
@Autowired
private ClienteDAO clienteDAO;
@Autowired
private UsuarioDAO usuarioDAO;
public void processRequest(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String salida = "";
try {
// Parte 1
// Inicializamos usuario admin si no existe
Usuario usuarioAdmin = usuarioDAO.get(33333);
if (usuarioAdmin == null) {
usuarioAdmin = new Usuario();
usuarioAdmin.setId(33333);
usuarioAdmin.setAdmin(true);
usuarioAdmin.setNombre("admin");
usuarioAdmin.setPassword("admin");
usuarioDAO.saveOrUpdate(usuarioAdmin);
salida += "Inicializada cuenta admin.\n";
} else {
salida += "La cuenta admin ya se encontraba inicializada.\n";
}
int cuentasInit = 0;
// Parte 2
// Inicializamos cuentas de usuario de cada cliente si no existe
List<Cliente> clientes = clienteDAO.findAll();
for (Cliente c : clientes) {
Usuario usuario = usuarioDAO.get(c.getIdCliente());
if (usuario == null) {
cuentasInit++;
usuario = new Usuario();
usuario.setId(c.getIdCliente());
usuario.setAdmin(false);
usuario.setNombre(c.getCliente());
usuario.setPassword("Clave " + c.getIdCliente());
c.setUsuario(usuario);
usuarioDAO.saveOrUpdate(usuario);
clienteDAO.saveOrUpdate(c);
}
}
salida += "Cuentas de cliente inicializadas=" + cuentasInit;
} catch (BussinessException ex) {
ex.printStackTrace();
salida = "Error inicializando cuentas de clientes y admin\n"
+ ex.getMessage();
}
response.getWriter().print(salida);
response.getWriter().flush();
}
}
El controlador en muy sencillo, consta de dos partes, en la primera comprueba si existe la cuenta "admin", a esto lo hace intentando cargar el id=33333, si no existe crea una instancia de Usuario y utiliza el método DAO saveOrUpdate() para almacenarlo.La parte 2 obtiene la lista de todos los clientes mediante el método DAO findAll(), luego chequeamos uno a uno si existe la cuenta de usuario asociada, si no existe se crea una y se asegura el almacenamiento.
En breve mejoraremos el mapeo de estas entidades para no tener que llamar a los métodos de persistencia de ambas.
Creamos el servicio
InicializarCuentasClientes.java
package ar.com.magm.web.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import ar.com.magm.model.dao.controller.InicializaCuentasClientesController;
@WebServlet("/InicializarCuentasClientes")
public class InicializarCuentasClientes extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
ApplicationContext applicationContext = WebApplicationContextUtils
.getWebApplicationContext(this.getServletContext());
InicializaCuentasClientesController controller = (InicializaCuentasClientesController) applicationContext
.getBean("inicializaCuentasClientesController");
controller.processRequest(request, response);
}
}
Nada que decir de este servicio, es similar a los anteriores, solo que utiliza otro controlador y que se ejecuta antes la URL /InicializarCuentasClientes, debemos agregar esta URL al filtro para disponer de sesiones Hibernate.
Agregamos la URL al filtro
HibernateContextListenerAndFilter
@WebFilter(urlPatterns = { "/TestHibernateConSpring", "*.xhtml", "/InicializarCuentasClientes" })
Creando las cuentas
Ahora solo debemos loguearnos al sistema y una vez en la pantalla principal cargar la URL: 'https://localhost:8080/pf/InicializarCuentasClientes'
La primera vez veremos el siguiente resultado:
Si lo ejecutamos nuevamente veremos:
Si chequeamos el estado de las tablas en MySQL (en este caso usando MySQL Query Browser):
Podemos ver que ya disponemos (de forma automática) de la clave foránea en la tabla Clientes.
Y se ha creado la tabla Usuarios:
Eso es todo por ahora, espero les sea de utilidad.
Saludos
Mariano
Gracias! =)
Subido por Gutenberg Alexis (no verificado) el 23 Octubre, 2013 - 18:03
Muchísimas gracias...
Subido por Francisco (no verificado) el 6 Noviembre, 2013 - 18:47
Me sirvió mucho, gracias.
Subido por Anonimo (no verificado) el 16 Noviembre, 2013 - 16:41
con las imagenes y flechas
Subido por felor69 (no verificado) el 7 Enero, 2014 - 06:56
Me alegra mucho que les haya
Subido por magm el 7 Enero, 2014 - 13:05
Me alegra mucho que les haya sido de utilidad.
Saludos
Mariano
yo intenté hacer lo mismo con
Subido por Anonimo (no verificado) el 28 Enero, 2014 - 20:54
yo intenté hacer lo mismo con Oracle weblogic 11g y no me funcionó. ¿qué puedo hacer?
Podrás explicitar que es
Subido por magm el 29 Enero, 2014 - 14:37
Podrás explicitar que es "hacer lo mismo"?
Saludos
Mariano
Buenas amigo, he seguido
Subido por Anonimo (no verificado) el 30 Octubre, 2014 - 11:59
Buenas amigo, he seguido todos los pasos de este tutorial y me lanza este error al ejecutar el servidor tomcat:
java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/faces/webapp/FacesServlet
¿Que estoy haciendo mal?
Muchas gracias y un saludo a todos.
Ajá, respondiste los
Subido por Anonimo (no verificado) el 22 Enero, 2015 - 21:53
Ajá, respondiste los comentarios de agradecimientos, pero no ayudas en los errores que están dando.
me parecio bastante bueno.yo
Subido por miguel-informa… (no verificado) el 2 Febrero, 2015 - 19:14
me parecio bastante bueno.yo trabajo con jsf y primefaces aplicandolos en la gestion y control de informacion,ya saben,mostrar datos en tablas(la tipica forma).Me gustaria que publicaran algo como esto con las animaciones