Minientrada

Breve introducción a las expresiones lambda.

Expresiones lambda

Las expresiones lambda no son complejas de usar y entre otras ventajas permiten pasar comportamiento como valor, algo muy utilizado en Javascript, y abreviar líneas de código sin perder legibilidad en el código. Es por esto que Java las adoptó definitivamente en la JavaSE 8. Pero, para que las expresiones lambda sean verdaderamente útiles, hay que llegar a entenderlas. Hasta que eso ocurre pueden llegar a ser bastante confusas.

Si nos atenemos a la definición oficial de expresión lambda diríamos que son funciones anónimas que implementan una interfaz funcional, que así dicho suena muy técnico, pero si te lo dicen así sin más, puede dejarte algo frío. A mi es lo que me pasó, así que me puse manos a la obra para intentar aterrizar el concepto y desgranar la definición teórica haciéndola comprensible para mortales como yo. De ese germen surgió esta entrada.

Lo que me quedó claro de la definición es qué, para entender las expresiones lambda hay que entender qué es una función anónima y que es una interfaz funcional.

Función anónima

Las funciones anónimas permiten crear instancias de un objeto que implementa un interfaz en particular sin que para ello sea necesario desarrollar la clase que implementa esa interfaz explícitamente.

¡Toma ya!

Esto se ve claro con un ejemplo. Imaginemos una interfaz que ofrece métodos para una calculadora:

package com.tecnicomio.pruebas.funcionesanonimas;

public interface ICalculadora {

    Integer suma (Integer operando1, Integer operando2);

}

De manera tradicional tendríamos una clase que implementara la interfaz:

package com.tecnicomio.pruebas.funcionesanonimas;

public class Calculadora implements ICalculadora {

    @Override
    public Integer suma(Integer operando1, Integer operando2) {
        return operando1 + operando2;
    }
}

Posiblemente, utilizaríamos esta clase en otra parte del código de la siguiente manera:

[...]
       ICalculadora calculadora = new Calculadora();
       Integer operando1 = 10;
       Integer operando2 = 22;
       Integer resultado = calculadora.suma(operando1, operando2);
       System.out.println(operando1 + "+" + operando2 + "=" + resultado);
[...]

Ahora haré una aproximación con funciones anónimas. La interfaz se mantiene, pero la clase desaparece.

El código resultado sería:

[...]

       Integer operando1 = 10;
       Integer operando2 = 22;

       System.out.println(operando1 + "+" + operando2 + "=" +
                new ICalculadora() {
                    @Override
                    public Integer suma(Integer operando1, Integer operando2) {
                        return operando1 + operando2;
                    }
                }.suma(operando1, operando2));
[...]

Repasando qué hemos conseguido con la función anónima, llegamos a las siguientes conclusiones:

  • Nos hemos ahorrado la definición y creación de la clase, que en el caso de clases que se usan en un único sitio, puede ser un gran ahorro. Así evitamos contaminar el proyecto con esas clases que no se reutilizan en otras partes.
  • Cuando la implementación es corta, como en el caso de este ejemplo, en el que el código se encuentre directamente en el lugar donde se usa, puede hacer que éste sea más entendible (aunque tal vez menos legible).
  • Permite seguir manejando correctamente las variables locales y miembros de la clase sin tener que definir un constructor para poder recibirlas y usarlas.

En este ejemplo, es probable que el ahorro de crear la clase, versus la disminución de legibilidad, haga que no merezca suficientemente la pena, pero pongamos un ejemplo donde verdaderamente las funciones anónimas dan la talla. Imaginemos una ventana con 15 botones y sus respectivos 15 event listeners cada vez que el usuario hace click en ellos. En este caso, habría que crear 15 clases donde cada una implementa el interfaz ActionListener. Clases que solo se usan en el punto del código donde se implementa el formulario. En este caso, usar funciones anónimas es mucho más rentable.

Interfaz Funcional

Un interfaz funcional es aquel interfaz que tiene únicamente un método y este método es abstracto, es decir un método sin implementar. Las interfaces funcionales fueron agregadas a partir de la versión JavaSE 8 y vienen de la mano de las expresiones lambda.

A continuación dos ejemplos de interfaz funcional. El primero es un ejemplo al uso, con un único método abstracto.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalSimple {
  public String saludo(String nombre);
}

El segundo es un ejemplo un poco más rebuscado pero igualmente válido; un único método abstracto y varios métodos default.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalRebuscada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

Para asegurarnos que la interfaz cumple con las reglas de las interfaces funcionales podríamos anotar la interfaz con @FunctionalInterface. En este caso si introdujéramos más de un método abstracto (sin implementación) el compilador nos daría el error: «Multiple non overriding abstract methods found in interface com.tecnicomio.pruebas.interfazfuncional.InterfazFuncionalAnotada». En caso de no introducir ningún método abstracto, nos daría el error: «No Target method found».

package com.tecnicomio.interfazfuncional;

@FunctionalInterface
public interface IInterfazFuncionalAnotada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

En el ejemplo de la calculadora, la interfaz ICalculadora es un claro ejemplo de Interfaz Funcional, sin anotar.

Expresiones lambda

Y ahora que ya han quedado un poco más claros los conceptos «funciones anónimas» e «interfaces funcionales» volvamos a la definición de expresión lambda: las expresiones lambda son funciones anónimas que implementan una interfaz funcional.

Por tanto, podríamos decir que son una evolución de las funciones anónimas que pretenden simplificar aún más el código, pero que para conseguir esta simplificación, obligan a que la funcionalidad de la expresión lambda implemente una interfaz funcional.

Sintaxis de las expresiones lambda

La sintaxis de las expresiones lambda es:

	
(Parametros) -> { cuerpo expresión lambda }

Teniendo en cuenta qué:

  • El operador lambda (->) separa la declaración de parámetros del cuerpo de la función.
  • Parámetros
    • Cuando se tiene un solo parámetro pueden omitirse los paréntesis.
    • Cuando no se tienen parámetros, o cuando se tienen dos o más, sí es necesario su uso.
  • Cuerpo de la expresión lambda
    • Cuando el cuerpo de la expresión lambda tiene una única línea pueden omitirse las llaves y no se necesita especificar la cláusula return en el caso de que se devuelva valor.

Ejemplos de expresiones lambda pueden ser:

  • Expresión lambda con un único parámetro y una única línea: num -> num+10
  • Expresión lambda sin parámetros y una única línea: () -> System.out.println(«Hola mundo»)
  • Expresión lambda con dos parámetros y una única línea: (int operando1, int operando2) -> operando1 * operando2
  • Expresión lambda con múltiples parámetros y varias líneas de función: (String nombre) -> {String retorno=»Hola «; retorno=retorno.concat(nombre); return retorno;}

La calculadora con expresiones lambda

Y como lo mejor para entender algo es verlo con un ejemplo, apliquemos las expresiones lambda al ejemplo de la calculadora que podría quedar así:

        ICalculadora calculadora = (Integer operando1, Integer operando2)->(operando1+operando2);

        Integer operando1 = 10;
        Integer operando2 = 22;

        System.out.println(operando1 + "+" + operando2 + "=" + calculadora.suma(operando1,operando2));

O si queremos ahorrar aún más líneas de código sin perder legibilidad podría quedar de esta otra forma:

    System.out.println("En una línea:10+22=" + ((ICalculadora)(Integer operando1, Integer operando2)->(operando1+operando2)).suma(10,22));

Conclusiones

Como se puede ver en el ejemplo de la calculadora utilizar expresiones lambda tienen claros y algunos obscuros.

Para mí los claros son:

  • Al hacer uso de funciones anónimas sin necesidad de crear clases anónimas se crea código más claro y conciso.
  • Acercan Java a la programación funcional muy utilizado en lenguajes de script, como Javascript, donde las funciones juegan un papel protagonista. Esto permite poder pasar funciones como valores de variables, valores de retorno o parámetros de otras funciones, es decir, gracias a las expresiones lambda se puede pasar comportamiento como valor.
  • Al usarlas en combinación con la API Stream se pueden realizar operaciones de tipo filtro/mapeo sobre colecciones de datos de forma secuencial o paralela siendo la implementación transparente al desarrollador.
  • Logran un código más compacto y fácil de leer.
  • Reducen la escritura de código.

El oscuro para mí es claro:

  • Hay que entenderlas para sacarles el máximo partido. Y entenderlas es cambiar la manera de pensar del javero de toda la vida.

Links

Minientrada

Internacionalizando una aplicación web basada en JSF 2.x

En esta entrada voy a realizar una aplicación muy básica con JSF 2.x y voy a internacionalizarla, de forma que se pueda ejecutar en dos lenguajes diferentes: español e inglés.

Es conveniente si tenemos claro que la aplicación debe aceptar varios idiomas que la internacionalización la apliquemos en la fase más temprana posible. Incorporar la internacionlización cuando la aplicación ya está muy avanzada suele ser bastante más costoso que hacerlo en etapas tempranas por varios motivos, pero principalmente las diferencias que hay entre los diferentes idiomas en cuanto a decir lo mismo con más o menos palabras pueden afectar al diseño de las páginas.

Requisitos para desarrollar el ejemplo.

Para que se pueda reproducir el ejemplo y esté operativo al 100% comento qué entorno he utilizado para hacerlo funcionar.

IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x
Tecnología: JSF 2.2 (Java EE 7)

Recomiendo la lectura de la entrada JSF 2.x Hola Mundo pues en ella comento todos los aspectos de configuración que en esta entrada voy a dar por supuestos, como la elección de librerías, configuración de servlets, etcétera.

Módulo web de aplicación.

Este ejemplo es tan sencillo que va a contar con un único módulo; el módulo web. El módulo web genera un WAR que se despliega directamente en Apache Tomcat.

Estructura

2016-09-09_14-18-13

Fichero: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>es.egv.jee6.jsf2</groupId>
 <artifactId>i18nBasico</artifactId>
 <version>1.0.0</version>
 <packaging>war</packaging>

 <dependencies>
   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-api</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-impl</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>javax.el</groupId>
     <artifactId>javax.el-api</artifactId>
     <version>3.0.1-b04</version>
   </dependency>

   <dependency>
     <groupId>javax.servlet.jsp.jstl</groupId>
     <artifactId>jstl-api</artifactId>
     <version>1.2</version>
   </dependency>
 </dependencies>

</project>

Fichero: WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>BasicoI18N</display-name>
  <welcome-file-list>
    <welcome-file>i18nwelcome.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
 
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>
  
  <context-param>
    <param-name>javax.faces.CONFIG_FILES</param-name>
    <param-value>WEB-INF/faces-config.xml</param-value>
  </context-param>

  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  
</web-app>

Destacar la inclusión de un parámetro de configuración que indica donde se encuentra el fichero de configuración de JSF. En este ejemplo vamos a incluir este fichero de configuración ya que va a ser necesario indicar los ficheros donde se encuentran las traducciones del idioma y el idioma por defecto de la aplicación.

Fichero: WEB-INF/faces-config.xml

<faces-config 
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">
    <application>
        <locale-config>
            <default-locale>es</default-locale>
        </locale-config>
        <resource-bundle>
            <base-name>i18n.i18nBasico</base-name>
            <var>i18n</var>
        </resource-bundle>
    </application>
</faces-config>

El campo <locale-config> sirve para definir las configuraciones relativas a la situación geográfica, entre ellas las localizaciones permitidas <supported-locale> o la localización por defecto <default-locale>. La localización es imprescindible para internacionalizar la aplicación.

El campo <resource-bundle> es la piedra angular de la internacionalización, ya que en él se indica donde va a encontrar la aplicación los ficheros que contienen las etiquetas en sus respectivos idiomas <base-name> y el nombre de la variable que se va a utilizar para hacer referencias a ellas <var>. Así podremos encontrar en las páginas dinámicas código como el siguiente donde se hace referencia a la etiqueta i18nwelcome.saludo del grupo de recursos identificado por el nombre de variable i18n:

<h:outputText value="#{i18n['i18nwelcome.saludo']}" />

El fichero faces-config.xml añade un montón de posibilidades de configuración más, pero se escapan al ámbito de esta entrada. Pueden consultarse en el siguiente link.

Los ficheros de recursos para los idiomas.

Estos ficheros contienen las traducciones a diferentes idiomas de las etiquetas de la aplicación. El nombre es el indicado en el campo <base-name> al que se le concatena el lenguaje de la variable Locale. Por ejemplo, para contener las etiquetas en inglés de la aplicación, el fichero se llama i18nBasico_en.properties.

Fichero: i18n/i18nBasico_en.properties

i18nwelcome.titulo = JSF 2.x internacionalization example.
i18nwelcome.saludo = Internacionalization with JSF 2.x. 
i18nwelcome.etiquetaIdioma = Language
i18nwelcome.descripcion-metodo1 = Combo Box Method
i18nwelcome.descripcion-metodo2 = Image Method

Fichero: i18n/i18nBasico_es.properties

i18nwelcome.titulo = Ejemplo de internacionalización con JSF 2.x.
i18nwelcome.saludo = Internacionalización con JSF 2.x. 
i18nwelcome.etiquetaIdioma = Idioma
i18nwelcome.descripcion-metodo1 = Método basado en una lista seleccionable. 
i18nwelcome.descripcion-metodo2 = Método basado en imágenes.

Se puede ver que el nombre de las etiquetas es el mismo en ambos ficheros, pero el valor de las mismas está traducido al idioma que se indica en el nombre del fichero.

El Managed Bean que maneja la lógica de presentación del idioma.

Fichero: es.egv.jee6.jsf2.i18n.IdiomaBean.java

package es.egv.jee6.jsf2.i18n;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;

@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {


    private static final long serialVersionUID = 1L;

    private String codigoIdioma;
    
    private Map<String,Object> listaIdiomas;
    
    public IdiomaBean() {
        super();
        this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        this.listaIdiomas = new LinkedHashMap<String,Object>();
        this.listaIdiomas.put("Español", new Locale("es"));
        this.listaIdiomas.put("Inglés", new Locale("en"));
    }

    public String getCodigoIdioma() {
        return codigoIdioma;
    }
    
    public void setCodigoIdioma(String codigoIdioma) {
        this.codigoIdioma = codigoIdioma;
    }
    
    public Map<String, Object> getListaIdiomas() {
        return listaIdiomas;
    }
    
    public void doCambioIdiomaConLista(ValueChangeEvent e)
    {

        String newCodigoIdioma = e.getNewValue().toString();
        System.out.println("newCodigoIdioma=" + newCodigoIdioma);
        System.out.println("idiomaBean=" + this.toString());

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(newCodigoIdioma))
               {
                   System.out.println("Asignando nueva locale al contexto de Faces.");
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }
    
    public void doCambioIdiomaConImagen(String nuevoIdioma)
    {

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(nuevoIdioma))
               {
                   System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
                   this.codigoIdioma = nuevoIdioma;
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }    
   
    @Override
    public String toString() {
        return "IdiomaBean [codigoIdioma=" + codigoIdioma + ", listaIdiomas=" + listaIdiomas + "]";
    }     
}

Defino un managed bean específico para manejar el idioma. Será el encargado de conservar el idioma seleccionado por el usuario, una lista de idiomas seleccionables, y las diferentes acciones que se llevan a cabo cuando se realizan acciones relacionadas con el idioma.

@ManagedBean(name="idiomaBean")
@SessionScoped
public class IdiomaBean implements Serializable {

Este ManagedBean lo defino en el ámbito de la sesión @SessionScoped. De esta manera, el idioma queda almacenado en todas las navegaciones que realice el usuario hasta que cierre el navegador, o la sesión.

    private String codigoIdioma;
    private Map<String,Object> listaIdiomas;

Se definen la variable codigoIdioma para contener el idioma seleccionado por el usuario y la variable listaIdiomas para contener los idiomas que van a mostrarse al usuario para proceder a la traducción de la aplicación.Se incluyen también sus getters y sus setters.

    public IdiomaBean() {
        super();
        this.codigoIdioma = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        this.listaIdiomas = new LinkedHashMap<String,Object>();
        this.listaIdiomas.put("Español", new Locale("es"));
        this.listaIdiomas.put("Inglés", new Locale("en"));
    }

Se incluye un constructor para rellenar el código del idioma con el idioma configurado por JSF en el arranque, que será el idioma que se ha configurado en el <default-locale> del fichero faces-config.xml, y rellenar la lista de idiomas con los idiomas válidos para la aplicación.

   public void doCambioIdiomaConLista(ValueChangeEvent e)
    {

        String newCodigoIdioma = e.getNewValue().toString();
        System.out.println("newCodigoIdioma=" + newCodigoIdioma);
        System.out.println("idiomaBean=" + this.toString());

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(newCodigoIdioma))
               {
                   System.out.println("Asignando nueva locale al contexto de Faces.");
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }

Se añade un método para manejar el cambio del idioma cuando se seleccione un idioma de la lista.

<h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">

Y, se añade un método que se ejecutará cuando se seleccione un cambio de idioma pinchando en la imágen del mismo.

    public void doCambioIdiomaConImagen(String nuevoIdioma)
    {

        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : listaIdiomas.entrySet()) 
        {

               if(entry.getValue().toString().equals(nuevoIdioma))
               {
                   System.out.println("Pinchado en imagen " + nuevoIdioma + ". Asignando nueva locale al contexto de Faces");
                   this.codigoIdioma = nuevoIdioma;
                   FacesContext.getCurrentInstance().getViewRoot().setLocale((Locale)entry.getValue());
               }
        }
    }
<h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">

Destacar que se ha incluido la asignación del idioma seleccionado a la variable codigoIdioma del bean para que quede guardada la sección.

this.codigoIdioma = nuevoIdioma;

Páginas web dinámicas.

El ejemplo consta de tres páginas web dinámicas: i18nwelcome.xhtml que es la página básica de bienvenida, i18nidiomametodo1.xhtml que es la página con el manejo de idioma basado en una lista seleccionable, e i18nidiomametodo2.xhtml que es la página con el manejo del idioma basado en imágenes.

2016-09-09_15-17-48

Fichero: i18nwelcome.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">

<h:body>
    <h1>#{i18n['i18nwelcome.titulo']}</h1>
    <h:form>
        <h2>
            <h:outputText value="#{i18n['i18nwelcome.saludo']}" />
        </h2>        
        <ui:include src="i18nidiomametodo1.xhtml" />        
        <ui:include src="i18nidiomametodo2.xhtml" />
    </h:form>
</h:body>
</html>

Esta página muestra dos etiquetas a internacionalizar; i18nwelcome.titulo e i18nwelcome.saludo. Para mostrar correctamente el idioma se utiliza expresiones EL que son interpretadas por JSF en tiempo de ejecución.

#{i18n['i18nwelcome.titulo']}

Esta expresión indica que se va a recoger del bundle i18n la etiqueta i18nwelcome.titulo.

Por claridad, los dos métodos que se van a utilizar para la selección del idioma se han realizado en dos páginas dinámicas independientes que se incluyen en la página básica mediante la sentencia <ui:include src=»»/>.

<ui:include src="i18nidiomametodo1.xhtml" />

Fichero: i18nidiomametodo1.xhtml

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
        <h:outputText value="#{i18n['i18nwelcome.descripcion-metodo1']}."/>
        <h:panelGrid columns="2">
            <h:outputText value="#{i18n['i18nwelcome.etiquetaIdioma']}:" />
            <h:selectOneMenu value="#{idiomaBean.codigoIdioma}" onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}">
                <f:selectItems value="#{idiomaBean.listaIdiomas}" />
            </h:selectOneMenu>
        </h:panelGrid>
</ui:composition>

Estos ficheros como se han diseñado para ser incluidos en un fichero padre y no para llamarse directamente se crean como elementos <ui:composition/>.

El valor seleccionado en la lista seleccionable es el codigoIdioma almacenado en el Managed Bean.

<h:selectOneMenu value="#{idiomaBean.codigoIdioma}"

La lista se carga con los valores almacenados en la listaIdiomas del Managed Bean.

<f:selectItems value="#{idiomaBean.listaIdiomas}" />

Cuando se cambia el elemento seleccionado se lanza el método doCambioIdiomaConLista.

onchange="submit()"
                valueChangeListener="#{idiomaBean.doCambioIdiomaConLista}"

Fichero: i18nidiomametodo2.xhtml

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
        <h:outputText value="#{i18n['i18nwelcome.descripcion-metodo2']}."/>
        <h:panelGrid columns="1">
            <h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('en')}">
                <h:graphicImage library="images" name="bandera-en.png" rendered="#{idiomaBean.codigoIdioma == 'es'}"/>
            </h:commandLink>
            <h:commandLink action="#{idiomaBean.doCambioIdiomaConImagen('es')}">
                <h:graphicImage library="images" name="bandera-es.png" rendered="#{idiomaBean.codigoIdioma == 'en'}"/>
            </h:commandLink>
        </h:panelGrid>
</ui:composition>

Destacar que se muestra el botón con la bandera del idioma que no está seleccionado, para así poder cambiarlo. Para ello se utiliza el atributo rendered y expresiones EL.

rendered="#{idiomaBean.codigoIdioma == 'es'}"

Demo

El pase de diapositivas requiere JavaScript.

Link

Configurar Aplicaciones JSF
Documentación oficial JSF 2.x

 

Minientrada

JSF 2.x Hola Mundo

En esta entrada voy a explicaros como hacer una aplicación muy básica con JSF 2.x, un Hola Mundo. JSF (o Java Server Faces) es la tecnología estándar de Java para simplificar la interfaz de usuario de aplicaciones web.

La versión 2.0 de JSF fue lanzada en Agosto de 2009 y corresponde con Java EE 6. La versión 2.2 de JSF fue lanzada en Abril de 2013 y corresponde con Java EE 7.

Para probar la aplicación voy a publicarla en un contenedor de servlets Apache Tomcat 8.5. Al no ser un servidor de aplicaciones homologado con Java EE habrá que incluir algunas librerías que no serían necesarias si la publicación se hiciera en un contenedor de aplicaciones homologado.

Al tener que incluir las librerías en la aplicación Java voy a utilizar la versión 2.2 de JSF. Hay que tener en cuenta que si se va a publicar en un servidor de aplicaciones existente la versión de JSF que incorporan depende de la Java EE homologada que implementen; Java EE 5 – JSF 1.2, Java EE 6 – JSF 2.0, Java EE 7 – JSF 2.2.

Entorno utilizado para desarrollar el ejemplo.

Para que se pueda reproducir el ejemplo y esté operativo al 100% comento qué entorno he utilizado para hacerlo funcionar.

IDE desarrollo: Eclipse 3.6 (Neon).
Máquina virtual Java: JDK 1.8.0_65.
Servidor de aplicaciones: Apache Tomcat 8.5
Ciclo de vida: Maven 3.x
Tecnología: JSF 2.2 (Java EE 7)

Módulo web de aplicación.

Este ejemplo es tan sencillo que va a contar con un único módulo; el módulo web. El módulo web genera un WAR que se despliega directamente en Apache Tomcat.

Fichero: pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>es.egv.jee6.jsf2</groupId>
 <artifactId>BasicoJSF2</artifactId>
 <version>1.0.0</version>
 <packaging>war</packaging>

 <dependencies>
   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-api</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>com.sun.faces</groupId>
     <artifactId>jsf-impl</artifactId>
     <version>2.2.11</version>
   </dependency>

   <dependency>
     <groupId>javax.el</groupId>
     <artifactId>javax.el-api</artifactId>
     <version>3.0.1-b04</version>
   </dependency>

   <dependency>
     <groupId>javax.servlet.jsp.jstl</groupId>
     <artifactId>jstl-api</artifactId>
     <version>1.2</version>
   </dependency>
 </dependencies>

</project>

En este caso, al desplegarse la aplicación en un contenedor de Servlets y no en un servidor de aplicaciones se incluyen como dependencias las librerías: jsf-api, jsf-impl (mojarra), javax.el-api y jstl-api. Si hubieramos desplegado esta aplicación en un servidor de aplicaciones homologado con la Java EE 6 o superiores, solo deberíamos incluir como dependencia la librería jsf-api, ya que el resto serían proporcionadas por el contenedor. En ese caso tener en cuenta también que la versión Java EE 6 incluye la versión 2.0 de JSF.

Fichero: web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>BasicoJSF2</display-name>
  <welcome-file-list>
    <welcome-file>holamundo.xhtml</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
 
    <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
  </servlet-mapping>

  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
</web-app>

Destacar dos cosas importantes en el fichero de configuración del módulo web:

  • Al cargarse el módulo web en el servidor se ejecuta el servlet que JSF incorpora para interpretar sus páginas (javax.faces.webapp.FacesServlet).
  • Mientras se esta desarrollando la aplicación es conveniente informar el parámetro JSF javax.faces.PROJECT_STAGE con el valor Development. De esta forma el servidor proporciona mucha información de DEBUG que de otra manera no estaría disponible. Cuando se quiera poner la aplicación en producción se sustituye ese valor por el de Production.

JSF 2.x Managed Bean

Los managed bean son clases java que se registran en el framework de JSF, lo que permite que puedan ser consumidos desde las páginas dinámicas. Desde la versión 2.0 de JSF este registro se puede llevar a cabo mediante la anotación @javax.faces.bean.ManagedBean.

Los managed bean contienen métodos get y set para poder guardar y recoger datos de la sesión y métodos de lógica de negocio, aunque si la aplicación es compleja la lógica de negocio recomiendo que recaiga en componentes EJB.

Fichero: HolaMundoMBean.java

package es.egv.jee6.jsf2.mbean;

import java.io.Serializable;
import java.text.SimpleDateFormat;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="holaMundoMBean")
@SessionScoped
public class HolaMundoMBean implements Serializable {
    private static final long serialVersionUID = -239729940657225276L;
    
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCurrentTime() {
         
        return new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new java.util.Date().getTime()); // Older version, SimpleDateFormat is not thread safe
    }
    
    
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("HolaMundoMBean [name=");
        builder.append(name);
        builder.append("]");
        return builder.toString();
    }    
    
}

JSF 2.x Páginas dinámicas

El formato recomendado al desarrollar páginas dinámicas en JSF es el formato XHTML (Extensible HTML) .

Para el ejemplo de hola mundo voy a realizar dos páginas dinámicas; holamundo.xhtml y holamundobienvenida.xhtml. Desde la página inicial se viaja a la de bienvenida llevándose la información del nombre para que la bienvenida sea personalizada.

Fichero: holamundo.xhtml

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">
<h:head>
    <title>Aplicación JSF 2.x</title>
</h:head>
<h:body bgcolor="white">
    <h2>Ejemplo. Hola Mundo en JSF 2.x</h2>
    <h:form>
        <h3>Fecha y hora: <h:outputLabel> #{holaMundoMBean.currentTime}</h:outputLabel></h3>
        <h3>A continuacion escribe tu nombre en el siguiente campo:</h3>
        <h:inputText value="#{holaMundoMBean.name}"/>
        <h:commandButton value="Pulsa" action="holamundobienvenida"/>
    </h:form>
</h:body>
</html>

Fichero: holamundobienvenida.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
 
    <h:head>
        <title>Aplicación JSF 2.x</title>
    </h:head>
    <h:body bgcolor="cyan">
        <h2>Ejemplo. Hola Mundo en JSF 2.x</h2>
        <h3>Hola #{holaMundoMBean.name}, bienvenido al ejemplo Hola Mundo con JSF 2.x.</h3>
        <h3>Fecha y hora: #{holaMundoMBean.currentTime} </h3>
        <h3>Hasta Luego.</h3>
        <h:form>
            <h:commandButton value="Volver" action="holamundo"/>
        </h:form>
    </h:body>
</html>

Hay que fijarse en las siguientes peculiaridades:

  • En JSF 1.x es obligatorio declarar una «navigation rule» en el fichero faces-config.xml para indicarle a la aplicación a qué página hay que ir cuando se pulsa un botón. Sin embargo, en JSF 2.x para facilitar la navegación se puede poner el nombre de la página directamente en el atributo action del botón. En aplicaciones pequeñas esta ventaja es un buen recurso. En aplicaciones grandes es recomendable seguir utilizando el fichero faces-config.xml para definir las reglas de navegación.
  • El campo #{…} indica una expresión dinámica de JSF que se interpreta en tiempo de ejecución. En la página holamundo.xhtml la expresión  #{holaMundoMBean.name} en el componente inputText indica que cuando se hace submit del formulario, JSF encuentra el Managed Bean holaMundoMBean, y en la propiedad name recoge el valor del componente mediante el método set correspondiente. Cuando en la página holamundobienvenida.xhtml se ejecute la expresión #{holaMundoMBean.name}, esta accede a la propiedad correspondiente del Managed Bean y recupera el valor incluido en la anterior página.

Resultado final.

A continuación se pueden ver los pantallazos con la aplicación funcionando.

En la primera página se teclea el nombre y se pulsa el botón de submit del formulario.

2016-08-29_14-45-14

Una vez tecleado y cuando se pulsa el botón se muestra la página de bienvenida.

2016-08-29_14-46-20

Si se pincha el botón Volver se regresa a la página holamundo.xhtml.

Links

Java Server Faces en la Wikipedia

Java EE Historial de versiones

Extensible HTML