2010-04-28 11 views
6

Atención avanzó Freemarker gurús:Utilice una plantilla FreeMarker única para mostrar tablas de POJOs arbitrarias

Quiero usar una plantilla FreeMarker única para poder tablas de salida de POJOs arbitrarias, con las columnas para mostrar definido por separado de la datos. El problema es que no puedo encontrar la forma de manejar una función en un pojo en tiempo de ejecución, y luego tener Freemarker invocar esa función (estilo lambda). De rozar los documentos parece que Freemarker es compatible con la programación funcional, pero parece que no puedo formular el hechizo apropiado.

Creé un ejemplo concreto simplista. Digamos que tengo dos listas: una lista de personas con un nombre y apellido y una lista de automóviles con marca y modelo. le gustaría salida de estas dos tablas:

<table> 
    <tr> 
    <th>firstName</th> 
    <th>lastName</th> 
    </tr> 
    <tr> 
    <td>Joe</td> 
    <td>Blow</d> 
    </tr> 
    <tr> 
    <td>Mary</td> 
    <td>Jane</d> 
    </tr> 
</table> 

y

<table> 
    <tr> 
    <th>make</th> 
    <th>model</th> 
    </tr> 
    <tr> 
    <td>Toyota</td> 
    <td>Tundra</d> 
    </tr> 
    <tr> 
    <td>Honda</td> 
    <td>Odyssey</d> 
    </tr> 
</table> 

Pero yo quiero usar la misma plantilla, ya que esto es parte de un marco que tiene que lidiar con los diferentes tipos de POJO docenas.

Dado el siguiente código:

public class FreemarkerTest { 

    public static class Table { 
    private final List<Column> columns = new ArrayList<Column>(); 

    public Table(Column[] columns) { 
     this.columns.addAll(Arrays.asList(columns)); 
    } 

    public List<Column> getColumns() { 
     return columns; 
    } 

    } 

    public static class Column { 
    private final String name; 

    public Column(String name) { 
     this.name = name; 
    } 

    public String getName() { 
     return name; 
    } 
    } 

    public static class Person { 
    private final String firstName; 
    private final String lastName; 

    public Person(String firstName, String lastName) { 
     this.firstName = firstName; 
     this.lastName = lastName; 
    } 

    public String getFirstName() { 
     return firstName; 
    } 

    public String getLastName() { 
     return lastName; 
    } 
    } 

    public static class Car { 
    String make; 
    String model; 

    public Car(String make, String model) { 
     this.make = make; 
     this.model = model; 
    } 

    public String getMake() { 
     return make; 
    } 

    public String getModel() { 
     return model; 
    } 
    } 

    public static void main(String[] args) throws Exception { 
    final Table personTableDefinition = new Table(new Column[] { new Column("firstName"), new Column("lastName") }); 
    final List<Person> people = Arrays.asList(new Person[] { new Person("Joe", "Blow"), new Person("Mary", "Jane") }); 
    final Table carTable = new Table(new Column[] { new Column("make"), new Column("model") }); 
    final List<Car> cars = Arrays.asList(new Car[] { new Car("Toyota", "Tundra"), new Car("Honda", "Odyssey") }); 

    final Configuration cfg = new Configuration(); 
    cfg.setClassForTemplateLoading(FreemarkerTest.class, ""); 
    cfg.setObjectWrapper(new DefaultObjectWrapper()); 
    final Template template = cfg.getTemplate("test.ftl"); 

    process(template, personTableDefinition, people); 
    process(template, carTable, cars); 
    } 

    private static void process(Template template, Table tableDefinition, List<? extends Object> data) throws Exception { 
    final Map<String, Object> dataMap = new HashMap<String, Object>(); 
    dataMap.put("tableDefinition", tableDefinition); 
    dataMap.put("data", data); 
    final Writer out = new OutputStreamWriter(System.out); 
    template.process(dataMap, out); 
    out.flush(); 
    } 

} 

Todo lo anterior es un hecho para este problema. Así que aquí está la plantilla que he estado pirateando. Tenga en cuenta el comentario donde estoy teniendo problemas.

<table> 
    <tr> 
<#list tableDefinition.columns as col> 
    <th>${col.name}</th> 
</#list> 
    </tr> 
<#list data as pojo> 
    <tr> 
<#list tableDefinition.columns as col> 
    <td><#-- what goes here? --></td>  
</#list> 
    </tr> 
</#list> 
</table> 

So col.name tiene el nombre de la propiedad a la que quiero acceder desde el pojo. He intentado un par de cosas, tales como

pojo.col.name 

y

<#assign property = col.name/> 
${pojo.property} 

pero por supuesto esto no funciona, acabo de ellas incluyen para ayudar a transmitir mi intención. Estoy buscando una forma de manejar una función y hacer que freemarker la invoque, o tal vez algún tipo de característica de "evaluación" que pueda tomar una expresión arbitraria como una cadena y evaluarla en tiempo de ejecución.

Respuesta

2

Encontrado la respuesta.

${("pojo." + col.name)?eval} 
5

?eval es (casi?) Siempre una mala idea, ya que a menudo viene con inconvenientes de rendimiento (por ejemplo, una gran cantidad de análisis) y los problemas de seguridad (por ejemplo, "FTL inyección").

Un mejor enfoque está utilizando la sintaxis de corchetes:

Existe una sintaxis alternativa si queremos especificar el nombre subvariable con una expresión: libro [ "título"]. En los corchetes puede dar cualquier expresión siempre que se evalúe como una cadena.

(Desde el FreeMarker documentation about retrieving data from a hash)

En tu caso te recomiendo algo así como ${pojo[col.name]}.

Cuestiones relacionadas