19.11.18

Spring: Internationalization & JSP/JSTL

Primero de todo deberíamos configurar el pom.xml con las dependencias adecuadas, para usar JSP y JSTL:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

Después, proporcionar un componente/bean "LocaleResolver" encargado de resolver la localización de la petición del cliente. Hay varios tipos, pero de los que yo he visto el más adecuado es CookieLocaleResolver, dado que guarda el idioma en una cookie, recordando la última elección.

@SpringBootApplication
public class Application implements WebMvcConfigurer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ViewResolver getViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Bean 
    public LocaleResolver localeResolver()
    {
        return new CookieLocaleResolver();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("l");
        registry.addInterceptor(localeChangeInterceptor);
    }    
}

La parte del "addInterceptors" proviene de la interfaz WebMvcConfigurer (menos mal que con Java 8 las interfaces pueden tener implementaciones por defecto, sino hay que extender WebMvcConfigurerAdapter, supongo). Esta interfaz permite configurar el MVC de Spring vía Java en varios aspectos, como añadir "Interceptores" (método addInterceptors), que permite interceptar la petición antes de ser pasada al controller, o después. En este caso se usa para cargar un "Locale" en base a la información de la cookie, es decir, la localización.


Ahora se podría cambiar el idioma agregando el parámetro "l" a la URI: "/?l=en" (siempre que messages_en.properties esté creado).

Podemos definir varios archivos "messages_XX.properties", uno por idioma (dejando messages.properties para el idioma usado cuando no es especifique nada). Un ejemplo de "messages_es.properties":

home.welcome = Bienvenidos
home.listOfNumbers = Lista de números elegidos al azar 
home.range= {0} números elegidos al azar entre {1} y {2}

Después se usarían en el JSP así (en este caso con JSTL + EL):

<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix = "fmt" uri = "http://java.sun.com/jstl/fmt"%>
<html>
    <head>
         <link rel="stylesheet" type="text/css" href="main.css">
    </head>
    <body>        
        <H1><fmt:message key="home.listOfNumbers"/></H1>   
        <fmt:message key="home.range">
            <fmt:param>${combinacion.getN()}</fmt:param>
            <fmt:param>${combinacion.getMin()}</fmt:param>
            <fmt:param>${combinacion.getMax()}</fmt:param>
        </fmt:message>

        <ul>
            <c:forEach items="${combinacion.getNumbers()}" var="numero">
            <li>
                ${numero}
            </li>           
            </c:forEach>
        </ul>        
    </body>
</html>

El controlador podría ser algo así:


@Controller
public class BasicController  {
  
    public static class Combinacion {
        private Set<Integer> numbers;        
        private int min;
        private int max;
        private int n;
        
        Combinacion (int n, int a, int b)
        {            
            Random r=new Random();
            numbers=new TreeSet<>();            
            min=Math.min(a,b);
            max=Math.max(a,b);
            this.n=n;
            if (max-min<n) max=min+n-1;
            while (numbers.size()<n)
            {
                int number=r.nextInt(max-min+1)+min;
                if (!numbers.contains(number))
                    numbers.add(number);
            }            
        }
        
        public Set<Integer>getNumbers()
        {            
            return numbers;
        }        
        
        public int getMin() {
            return min;
        }

        public int getMax() {
            return max;
        }

        public int getN() {
            return n;
        }
    }    

    @RequestMapping("/")
    public String index(Model model, 
            @RequestParam(defaultValue="6") int n,
            @RequestParam(defaultValue="1") int min, 
            @RequestParam(defaultValue="49") int max) {       
        model.addAttribute("combinacion", new Combinacion(n, min, max));        
        return "home";
    }    

}

En fin... esto es todo.

17.11.18

Ejemplo de uso Spring vista JSP

Versión Spring Boot 2.1.0.RELEASE

Añadir las dependencias (no poner <scope>provided</scope> si no es para un WAR que se desplegara en un servidor de aplicaciones):

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

Luego, crear el ViewResolver con el sufijo y el directorio dentro de src/main/webapp:

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    
    @Bean
    public ViewResolver getViewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
    
    @Bean
    public Operacion operacionSuma5en5 ()
    {
        return new Operacion() {
            private int a=0;
            private int b=0;
            @Override
            public void establecerValorA(int a) {
                this.a=a;
            }

            @Override
            public void establecerValorB(int b) {
                this.b=b;
            }

            @Override
            public double realizarCalculo() {
                return IntStream.rangeClosed(Math.min(a,b),Math.max(a,b))
                        .filter(i->(i-a)%5==0).sum();
            }
            
        };
    }
    
}

También se podrían añadir en application.properties en la carpeta src/main/resources.

Ver este post para componentes Operacion.

Después, controlador sin @ResponseBody (se enviará a vista):

@Controller
public class BasicController implements ApplicationContextAware  {
    
    AtomicInteger i;
  
    ApplicationContext ac;
    
    @Autowired
    @Qualifier("sumarRango")
    private Operacion op1;

    @Autowired
    @Qualifier("sumarPares")
    private Operacion op2;
       
    private Operacion op3;    
    
    @PostConstruct
    public void init()
    {
        i=new AtomicInteger(0);
        op3=ac.getBean("operacionSuma5en5", Operacion.class);
        op1.establecerValorA(0);
        op2.establecerValorA(0);
        op3.establecerValorA(0);
    }

    @RequestMapping("/")
    public String index(Model model) {
        int i=this.i.getAndIncrement();
        op1.establecerValorB(i);
        op2.establecerValorB(i);
        op3.establecerValorB(i);
        String[] results=new String[3];
        results[0]="The sum from "+0+" to "+i+" is " + op1.realizarCalculo()+".";
        results[1]="The sum of even numbers from "+0+" to "+i+" is " + op2.realizarCalculo()+".";
        results[2]="The sum of numbers every 5 from "+0+" to "+i+" is " + op3.realizarCalculo()+".";        
        model.addAttribute("resultados", results);        
        return "home";
    }    
    
    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        this.ac=ac;
    }

}

Añade atributo a través de "addAttibute" de model y retorna el nombre de la vista.

Y por último, la vista (home.jsp en este caso):

<%   
    String[] resultados=(String[])request.getAttribute("resultados");    
%>
<html>
    <body>
        <H1>Lista de resultados</H1>
        <ul>
            <% for(String resultado:resultados) { %>
                <li><%=resultado%></li>
            <% } %>
        </ul>
    </body>
</html>

Y esto es todo.

16.11.18

Ejemplo de uso de @Bean (Spring Framework)

(Ver primero este) ... primero declaramos el método:


import java.util.Arrays;
import java.util.stream.IntStream;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Operacion operacionSuma5en5 ()
    {
        return new Operacion() {
            private int a=0;
            private int b=0;
            @Override
            public void establecerValorA(int a) {
                this.a=a;
            }

            @Override
            public void establecerValorB(int b) {
                this.b=b;
            }

            @Override
            public double realizarCalculo() {
                return IntStream.rangeClosed(Math.min(a,b),Math.max(a,b))
                        .filter(i->(i-a)%5==0).sum();
            }
            
        };
    }
}

Y luego lo usamos en el contenedor (qualifier el nombre del método):


import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
public class BasicController  {
    
    AtomicInteger i;
  
    @Autowired
    @Qualifier("sumarRango")
    private Operacion op1;

    @Autowired
    @Qualifier("sumarPares")
    private Operacion op2;

    @Autowired
    @Qualifier("operacionSuma5en5")
    private Operacion op3;    
    
    @PostConstruct
    public void init()
    {
        i=new AtomicInteger(0);
        op1.establecerValorA(0);
        op2.establecerValorA(0);
        op3.establecerValorA(0);
    }

    @RequestMapping("/")
    public String[] index() {
        int i=this.i.getAndIncrement();
        op1.establecerValorB(i);
        op2.establecerValorB(i);
        op3.establecerValorB(i);
        String[] results=new String[3];
        results[0]="The sum from "+0+" to "+i+" is " + op1.realizarCalculo()+".";
        results[1]="The sum of even numbers from "+0+" to "+i+" is " + op2.realizarCalculo()+".";
        results[2]="The sum of numbers every 5 from "+0+" to "+i+" is " + op3.realizarCalculo()+".";
        return results;
    }    

}

Pero también se podría hacer así (implementando ApplicationContextAware y usando el método getBean) :


import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
public class BasicController implements ApplicationContextAware  {
    
    AtomicInteger i;
  
    ApplicationContext ac;
    
    @Autowired
    @Qualifier("sumarRango")
    private Operacion op1;

    @Autowired
    @Qualifier("sumarPares")
    private Operacion op2;
       
    private Operacion op3;    
    
    @PostConstruct
    public void init()
    {
        i=new AtomicInteger(0);
        op3=ac.getBean("operacionSuma5en5", Operacion.class);
        op1.establecerValorA(0);
        op2.establecerValorA(0);
        op3.establecerValorA(0);
    }

    @RequestMapping("/")
    public String[] index() {
        int i=this.i.getAndIncrement();
        op1.establecerValorB(i);
        op2.establecerValorB(i);
        op3.establecerValorB(i);
        String[] results=new String[3];
        results[0]="The sum from "+0+" to "+i+" is " + op1.realizarCalculo()+".";
        results[1]="The sum of even numbers from "+0+" to "+i+" is " + op2.realizarCalculo()+".";
        results[2]="The sum of numbers every 5 from "+0+" to "+i+" is " + op3.realizarCalculo()+".";
        return results;
    }    
    
    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        this.ac=ac;
    }

}

Pero ya se le pierde la gracia a la inyección de dependencias claro está.

15.11.18

Ejemplo de uso de @Qualifier (Spring framework)

Un ejemplo sencillo del uso de @Qualifier en Spring Framework. Una interfaz:
public interface Operacion
{
    public void establecerValorA (int a);
    public void establecerValorB (int b);
    public double realizarCalculo();    
}
Dos clases que implementan dicha interfaz:
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("sumarRango")
public class OperacionSumarRango implements Operacion {

    private int a;
    private int b;

    @Override
    public void establecerValorA(int a) {
        this.a=a;
    }

    @Override
    public void establecerValorB(int b) {
        this.b=b;
    }

    @Override
    public double realizarCalculo() {
        return IntStream.rangeClosed(Math.min(a,b), Math.max(a,b)).parallel().sum();
    }
}
import java.util.stream.IntStream;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
@Qualifier("sumarPares")
public class OperacionSumarPares implements Operacion {
    private int a;
    private int b;

    @Override
    public void establecerValorA(int a) {
        this.a=a;
    }

    @Override
    public void establecerValorB(int b) {
        this.b=b;
    }

    @Override
    public double realizarCalculo() {
        return IntStream.rangeClosed(Math.min(a,b), Math.max(a,b)).parallel().filter(n->n%2==0).sum();
    }
}
Y el controlador:
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
public class HelloController {
    
    AtomicInteger i;

    @Autowired
    @Qualifier("sumarRango")
    private Operacion op1;

    @Autowired
    @Qualifier("sumarPares")
    private Operacion op2;

    @PostConstruct
    public void init()
    {
        i=new AtomicInteger(0);
        op1.establecerValorA(0);
        op2.establecerValorA(0);
    }

    @RequestMapping("/")
    public String[] index() {
        int i=this.i.getAndIncrement();
        op1.establecerValorB(i);
        op2.establecerValorB(i);
        String[] results=new String[2];
        results[0]="The sum from "+0+" to "+i+" is " + op1.realizarCalculo()+".";
        results[1]="The sum of even numbers from "+0+" to "+i+" is " + op2.realizarCalculo()+".";
        return results;
    }    
}
Y esto es todo.

Ejemplo de uso de @Autowire (Spring framework)

Un ejemplo sencillo. El componente, etiquetado como @Component.

import java.util.stream.IntStream;
import org.springframework.stereotype.Component;

@Component
public class MiComponente
{
    public int sumRange (int i, int j)
    {                
        return IntStream.rangeClosed(Math.min(i,j), Math.max(i,j)).parallel().sum();
    }
}

El controlador:

import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
public class HelloController {
    
    AtomicInteger i;

    @Autowired
    private MiComponente miComponente;

    @PostConstruct
    public void init()
    {
        i=new AtomicInteger(0);
    }

    @RequestMapping("/")
    public String index() {
        int i=this.i.getAndIncrement();
        return "Oh! My gosh! The sum from "+0+" to "+i+" is " + miComponente.sumRange(0,i)+".";
    }
    
}

El componente es creado e inyectado por el contenedor Swing en el controlador.



3.1.18

Sketchs con p5.js

Como buen propósito de año nuevo, me he propuesto retomar mi blog, intentando publicar algo todas las semanas (espero que puede hacerlo durante todo el año). Y esta semana ya se por donde empezar.

Hace tiempo publique un post con un ejemplo de uso de Processing. Se trataba de una animación que generaba una ilusión óptica, algo relativamente sencillo, que ahora, al echar un vistazo en el tiempo he visto que ya no funciona.

El asunto es que me había propuesto arreglarlo, pero al mirar en la página del proyecto Processing he visto el proyecto p5.js y claro... que mejor que rehacerlo en JavaScript, el lenguaje de moda.

Primero echemos un vistazo a la ilusión óptica en sí, y luego, explicaré un poco por encima como funciona esto de p5.js y para que se puede utilizar. La ilusión óptica es simple, las líneas rojas son líneas rectas realmente, pero aparentemente se curvan...

El código responsable de lo anterior es el siguiente:

Lo primero es, ¿qué es esto y para qué sirve? Pues Processing en si es un lenguaje/entorno pensado para aprender a programar, o eso dicen, que está enfocado al desarrollo de arte visual y prototipado rápido.

p5.js es una adaptación a JavaScript de este entorno. Digo adaptación porque no es 100% igual a Processing. p5.js permite crear bosquejos rápidamente en un entorno web usando el elemento Canvas de HTML5. Al igual que Processing, p5.js activa la imaginación del programador/a y lo sitúa en una posición diferente... experimentar creando.

Para mi p5.js y processing es un entorno fantástico para aprender a programar, dado que ofrece un feedback inmediato de lo que estás haciendo, e invita literalmente continuar experimentando. Pero eso es solo una opinión obviamente.

Nuestro bosquejo p5.js empieza simplemente cargando la librería en nuestra página:

<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.16/p5.js"></script>

Con esto ya tenemos cargado el entorno. Después, se habrá que implementar varias funciones clave:


Estas son las funciones setup y la función draw, que son funciones estructurales:
  • setup: es una función que se ejecutará una sola vez y que se utiliza para inicializar el entorno. Dentro de esa función usamos funciones como "createCanvas" para crear el canvas donde se dibujará nuestro bosquejo.
  • draw: es una función que se ejecutará hasta el infinito una y otra vez, salvo que nosotros le indiquemos que no. Esto se hace con la función "noLoop()", que hará que solo se ejecute una vez más la función draw (si se invoca desde fuera del método draw), o que termine el método draw y no vuelva a ejecutarse más (si se invoca desde dentro del método draw).
El método draw lo usaremos por tanto para ir dibujando. El método draw se invocará un número de veces cada segundo (como si fueran los fotogramas de una película), y esto se puede controlar con el método frameRate.

En el ejemplo anterior he quitado el "noLoop()" para dejar que el código se ejecute infinitamente. Imaginemos ahora que queremos detener la animación al hacer clic, y volver a reanudarla nuevamente al hacer clic otra vez. Para esto tendríamos que implementar un método manejador, que se ejecutará cuando el evento "clic" se produzca:


El método sería "mouseClicked". Pero todo este código tiene varios problemas, ¿qué ocurre si queremos poner varios bosquejos en una página? ¿podemos evitar que las variables se creen en el contexto global? La respuesta es sí, simplemente encapsulando el código dentro de una función a la que se pasará como parámetro una variable que será el sketch que estamos manipulando:


Fíjate que todas las funciones de p5.js ahora se invocan siguiendo la siguiente forma: "bosquejo.metodo"; esto permite diferenciar un bosquejo de otro. Para empezar con esto es suficiente. Si quieres seguir investigando consulta la página de referencia de funciones de p5.js, y, simplemente, experimenta todo lo que puedas.

23.10.16

Aprendiendo Node.js


Llevaba tiempo queriendo terminar el tutorial de node (learnyounode) de NodeSchool.io desde hace tiempo y ya lo he terminado. La verdad es que me ha gustado esta forma de afrontar el aprendizaje, en este caso, de Node.

La idea de aprender, resolviendo casos prácticos sencillos, que van incrementando en dificultad, está muy en la línea de la forma en la que yo aprendo mejor.

Así que, si eres de l@s que aprenden mejor haciendo que leyendo... esta es una gran forma de aprender Javascript, Node, Express.js, MongoDB y un montón de cosas más.

Ahora que he terminado el de Node.js, voy a por el de GIT (git-it) y después a por el de Electron.