martes, 26 de marzo de 2013

FREEMARKER VS VELOCITY

Hoy nos ponemos en faena para comparar dos conocidos motores de plantillas: FreeMarker y Velocity. Veremos las ventajas de utilizar uno contra otro y como siempre un ejemplo sencillo de cada uno de los motores con Eclipse y Java. Vosotros decidiréis cual utilizar para vuestro proyecto en cuestión. 

Utilizando tanto un motor como el otro podremos realizar nuestras vistas o informes dinámicos mediante plantillas pasando los datos mediante un programa en Java (puede incluso utilizarse para sustituir a las JSPs). También nos permitirán generar salidas para diferentes formatos de archivo: HTML, RTF, XML, etc... y ser  integrados en aplicaciones web dependientes de un servidor como en aplicaciones 'standalone'.

Imagen: memegenerator




Velocity vs FreeMarker


Velocity es un motor más simple, sin embargo, con FreeMarker se pueden realizar algunas tareas que con Velocity es imposible de realizar, además de tener un lenguaje de plantillas más potente.


Algunas tareas complejas comunes a muchas aplicaciones están implementadas en el core de FreeMarker, sin embargo, en Velocity no es así y deberemos utilizar soluciones como añadir objetos java a nuestras plantillas (lo que viola la idea del patron MVC de no incluir código en nuestras vistas). 

Una ventaja de Velocity es que cuenta con el soporte de una mayor comunidad de usuarios, por lo que será fácil dar solución a los problemas con los que nos vayamos encontrando por el camino. Según los autores de FreeMarker, este motor no necesitará de tanto soporte debido a que muchas características que querremos incorporar a nuestras aplicaciones ya las tendremos implementadas en el core.

Como siempre, que el sentido común decida por vosotros. Dependerá del tamaño de vuestra aplicación y lo que queráis hacer con el motor de plantillas en la misma. 

Para descargar la última versión de FreeMarker pulsa aquí y para descargar la última versión de Velocity aquí.


Ejemplo con FreeMarker

Nuestro proyecto resultante en Eclipse debe tener la siguiente estructura:



Lo primero que hacemos es crear nuestras plantillas auxiliares de FreeMaker con extensión .ftl, en nuestro caso, serán header.ftl y footer.ftl. La carpeta para crearlas será templates/common

header.ftl

<title>Tutoriales de JPASCU: ${mensaje1}</title>
<body> 

footer.ftl

</body> 

Posteriormente creamos nuestra plantilla principal main.ftl en el directorio templates. En ella incluimos las otras plantillas. A partir de ella el motor de FreeMarker genera una página HTML con los valores que se pasan desde una clase Java.

main.ftl



<#include "./common/header.ftl"> 
<#if container??>
  <div id="${container}">
<#else>
  

<div id="default">
</#if>


<ul>
<#list jugadores as jugador>
<li>${jugador_index + 1}.${jugador.getName()}</li>
</#list>
</ul>
<h1>
${mensaje2.getName()}</h1>
</div>
<#include "./common/footer.ftl">

Destacamos tres puntos en el código de la plantilla:

  • Utilizamos la directiva <#include> para incluir otras plantillas
  • Podemos utilizar condiciones para poner un código HTML u otro dependiendo de las variables pasadas desde Java. Por ejemplo, <#if container??> es verdadero si la variable container es pasada al motor de FreeMaker. Si no existe la expresión será falsa. OJO: el ?? no funciona en versiones antiguas de FreeMaker.
  • También podemos utilizar bucles y llamar a métodos de objetos Java desde nuestra plantilla .ftl.


Por último, creamos las clases Java, una clase holder (podría ser un String) y la clase de prueba en la que creamos una instancia del motor y le pasamos las variables para que haga el 'merge' con la plantilla. El código Java de las clases es el siguiente:

JugadorFutbol.java

package es.jpascu.engine.freemarker;

public class JugadorFutbol {

 private String name;

 public JugadorFutbol(String name) {
  super();
  this.name = name;
 }

 public String getName() {
  return name;
 }

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



TestFreeMaker.java


package es.jpascu.engine.freemarker;

import java.io.File;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import freemarker.template.Configuration;
import freemarker.template.Template;

public class TestFreeMarker {

  public static void main(String[] args) {

    // Creamos la configuración
    Writer file = null;
    Configuration cfg = new Configuration();

    try {
      // Seleccionamos el directorio donde se encuentran nuestras plantillas
      cfg.setDirectoryForTemplateLoading(new File("templates"));
      // Cargamos las plantillas
      Template template = cfg.getTemplate("main.ftl");

      // Pasamos los parámetros
      Map input = new HashMap();
      input.put("mensaje1", "Ejemplo con FreeMarker");
      input.put("container", "test");

   
      List jugadores = new ArrayList();

      jugadores.add(new JugadorFutbol("Messi"));
      jugadores.add(new JugadorFutbol("Iniesta"));
      jugadores.add(new JugadorFutbol("Xavi"));
      jugadores.add(new JugadorFutbol("Villa"));
      jugadores.add(new JugadorFutbol("CR7"));

      input.put("jugadores", jugadores);

      JugadorFutbol exampleObject = new JugadorFutbol("¿Pero qué equipo es este?");
      input.put("mensaje2", exampleObject);

      // Escribimos en un fichero HTML
      file = new FileWriter(new File("output.html"));
      template.process(input, file);
      file.flush();

      // Escribimos en la salida estandar
      Writer out = new OutputStreamWriter(System.out);
      template.process(input, out);
      out.flush();

    } catch (Exception e) {
      System.out.println(e.getMessage());

    } finally {
      if (file != null) {
        try {
          file.close();
        } catch (Exception e2) {
        }
      }
    }

  }
} 

En la clase TestFreeMaker cabe destacar lo siguiente:


  • Cargamos la configuración (cfg) del motor con la clase freemarker.template.Configuration.
  • Podemos seleccionar el directorio de plantillas con cfg.setDirectoryForTemplateLoading
  • Cargamos la plantilla con  cfg.getTemplate("main.ftl")
  • Generamos una salida con  template.process(input, file), donde input es un map de tipo parámetro-valor con los valores con los que queremos rellenar nuestra plantilla y file es un stream de salida (salida estandar, fichero HTML, fichero RTF...).

Ejemplo con Velocity

Nuestro proyecto resultante en Eclipse debe tener la siguiente estructura:




Lo primero que hacemos es crear nuestras plantillas auxiliares de Velocity con extensión .vm, en este ejemplo serán header.vm y footer.vm

header.vm


<title>Tutoriales de JPASCU: $mensaje1</title>
<body>

footer.vm

</body>

Posteriormente creamos nuestra plantilla principal main.vm. En ella incluimos las otras plantillas. A partir de ella el motor de Velocity genera una página HTML con los valores que se pasan desde una clase Java.

main.vm



#parse("header.vm")
#if ($container)
  <div id="$container">
#else
  <div id="default">
#end
<ul>
#set ($counter = 0)
#foreach ($jugador in $jugadores)
 #set ($counter = $counter + 1)
<li>$counter.$jugador.getName()</li>
#end
</ul>
<h1>
$mensaje2.getName()</h1>
</div>
#parse("footer.vm")

Veamos ahora las diferencias de notación con FreeMaker:

  • Utilizamos la directiva #parse para incluir otras plantillas con variables. Si queremos incluir alguna plantilla con texto o código HTML estático podemos utilizar #include.
  • También podemos utilizar condiciones para poner un código HTML u otro dependiendo de las variables pasadas desde Java. Utilizamos la directiva #if para realizar una comparación de una variable. Deberemos cerrar las sentencias en las plantillas con #end.
  • También podemos utilizar bucles con la sentencia #foreach y llamar a métodos de objetos Java desde nuestra plantilla .vm con la notación punto igual que en FreeMaker.

Por último, creamos las clases Java, una clase holder (podría ser un String) y la clase de test en la que creamos una instancia del motor de Velocity y le pasamos las variables para que haga el 'merge' con la plantilla, igual que en el caso anterior. El código Java de las clases es el siguiente:

JugadorFutbol.java

package es.jpascu.engine.freemarker;

public class JugadorFutbol {

 private String name;

 public JugadorFutbol(String name) {
  super();
  this.name = name;
 }

 public String getName() {
  return name;
 }

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


TestVelocity.java


package es.jpascu.engine.velocity;

import java.io.File;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

public class TestVelocity {

 public static void main(String[] args) {

  Writer file = null;
  /* Inicializamos el motor de Velocity */
  VelocityEngine ve = new VelocityEngine();
  ve.init();

  try {

   // Cargamos las plantillas

   Template t = ve.getTemplate("main.vm");

   // Creamos el contexto donde pasaremos los parámetros
   VelocityContext context = new VelocityContext();

   // Pasamos los parámetros
   Map input = new HashMap();
   context.put("mensaje1", "Ejemplo con Velocity");
   context.put("container", "test");

   List jugadores = new ArrayList();

   jugadores.add(new JugadorFutbol("Messi"));
   jugadores.add(new JugadorFutbol("Iniesta"));
   jugadores.add(new JugadorFutbol("Xavi"));
   jugadores.add(new JugadorFutbol("Villa"));
   jugadores.add(new JugadorFutbol("CR7"));

   context.put("jugadores", jugadores);

   JugadorFutbol exampleObject = new JugadorFutbol(
     "¿Pero qué equipo es este?");
   context.put("mensaje2", exampleObject);

   // Escribimos en un fichero HTML
   file = new FileWriter(new File("output.html"));
   t.merge(context, file);
   file.flush();

   // Escribimos en la salida estandar
   Writer out = new OutputStreamWriter(System.out);
   t.merge(context, out);
   out.flush();

  } catch (Exception e) {
   System.out.println(e.getMessage());

  } finally {
   if (file != null) {
    try {
     file.close();
    } catch (Exception e2) {
    }
   }
  }

 }
}


En la clase TestVelocity  cabe destacar lo siguiente:


  • Inicializamos el motor de Velocity (org.apache.velocity.app.VelocityEngine)
  • El directorio de plantillas lo configuramos en el archivo de configuración de Velocity velocity.properties, en el que también configuramos otras propiedades del motor.
  • Cargamos la plantilla con   ve.getTemplate("main.vm")
  • Creamos el contexto que encapsulara los parámetros que le pasaremos al motor de Velocity para generar la salida.
  • Generamos una salida con  t.merge(context, file), donde context es el objeto Contexto de Velocity (org.apache.velocity.VelocityContext) de tipo parámetro-valor con los valores con los que queremos rellenar nuestra plantilla y file es un stream de salida (salida estandar, fichero HTML, fichero RTF...).


Por último, os muestro el contenido del fichero velocity.properties, en el que le decimos al motor que cargue la plantillas del classpath:

velocimacro.library=macros/VM_global_library.vm

resource.loader=classpath 
classpath.resource.loader.description=Velocity Classpath Resource Loader 
classpath.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader 
classpath.resource.loader.cache=true 
classpath.resource.loader.modificationCheckInterval=2


Espero que el ejemplo sea útil para que profundicéis en el mundo de los motores de plantillas.

Ahh, se me olvidaba, la página resultante para el caso de Velocity es la siguiente:




Salu2.




2 comentarios:

  1. Muy buena explicación amigo, gracias por compartir tus conocimientos, ya que la mayoría de la información esta en ingles!
    Saludos.

    ResponderEliminar