Integracion WebWork/Freemarker con JSP
Jueves, 9 de Febrero de 2006Una de las muchas cosas que agradezco a Webwork es haber conocido a Freemarker. Este motor de plantillas es realmente potente y se integra completamente con Webwork.
Sin embargo, hay todavía algunas incompatibilidades cuando usamos a la vez Webwork/Freemarker y JSP.
1. Jsp desde Freemarker
Por un lado vemos en la documentación de Freemarker que es posible importar taglibs y utilizarlas desde nuestras plantillas. Esto permite eliminar nuestra dependencia a los JSPs si estamos acostumbrados a utilizar ciertos taglibs.
Para esto solo tenemos que utilizar lo siguiente en nuestras plantillas ftl:
<#assign bean=JspTaglibs["/WEB-INF/struts-bean.tld"]>
Y ya podemos utilizar las etiquetas con la sintaxis:
<@bean.message key="welcome.title"/>
Pero esto es la teoría y lo que dice la documentación de Freemarker, porque en la práctica, y solo cuando utilizamos Freemarker desde Webwork, no funciona. Es un bug conocido que, creo, están intentando arreglar (digo creo porque el bug está reportado en el foro de Webwork y en el jira de Opensymphony desde Octubre del 2005 y a fecha de hoy no se ha arreglado todavía).
2. Freemarker desde Jsp
Por otro lado, tenemos la integración a la inversa: podemos utilizar desde nuestros JSPs plantillas ftl incrustadas. Freemarker incluye un taglib, con solo una etiqueta para este fin. Para utilizarla solo tendremos que añadir lo siguiente en nuestro JSP:
<%@ taglib prefix="fm" uri="freemarker" %> codigo JSP <fm:template> ?codigo freemarker? </fm:template>
?Y funciona! La integración JSP-Freemarker es correcta.
3. Freemarker desde Jsp con Webwork
Cuando utilizamos Freemarker desde Webwork directamente, Webwork crea un "modelo" con todos los objetos de Webwork necesarios que utilizaremos desde nuestra plantilla ftl. Este modelo incluye el stack OGNL, el Action del que venimos, el request y el response (mirad el Variable resolution de la documentación de Freemarker para Webwork).
Pero, ?qué pasa cuando utilizamos Webwork, redirigimos la salida de nuestra acción a un JSP, y desde este JSP utilizamos Freemarker con la etiqueta <fm:template>?
Pues que este modelo no está, ya que la etiqueta que utilizamos esta desarrollada por el equipo de Freemarker y no por Webwork, por lo que este modelo que estamos acostrumbados a utilizar no existe.
O sea, ya no podemos utilizar en nuestro ftl incrustado ${actionbean.atributo} para referirnos a los beans de nuestras acciones como lo hacíamos cuando utilizábamos las plantillas ftl desde Webwork directamente.
?Y cuál es la solución? Modificar el código del taglib y modificar el dispatcher de JSPs de Webwork ligeramente para que incluya este modelo.
Estas son, punto por punto, las modificaciones que hay que realizar.
1. Crearemos en nuestro proyecto una clase dipatcher como la siguiente:
package freemarker.ext.jsp;
import com.opensymphony.webwork.dispatcher.*;
import com.opensymphony.webwork.*;
import com.opensymphony.xwork.*;
import javax.servlet.jsp.*;
import javax.servlet.http.*;
public class ServletFreemarkerDispatcherResult extends ServletDispatcherResult {
public static String REQUEST_ID = "ServletFreemarkerDispatcherResult.ActionInvocation";
public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
PageContext pageContext = ServletActionContext.getPageContext();
if (pageContext == null) {
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute(REQUEST_ID, invocation);
}
super.doExecute(finalLocation, invocation);
}
}
Esta clase solo hereda de ServletDispatcherResult para poder añadir el Action actual como atributo del request, que servirá para que la etiqueta modificada pueda crear el modelo correcto a partir del Action. Después simplemente llama al método doExecute() de la clase padre, por lo que el funcionamiento sigue siendo es el mismo.
2 Modificamos nuestro xwork.xml para redefinir el result-type "dispatcher" al nuevo que acabamos de crear.
<result-types>
<!-- result-type name="debug" class="si.web.result.DebugResult" default="false"/-->
<result-type name="dispatcher"
class="freemarker.ext.jsp.ServletFreemarkerDispatcherResult"
default="true"/>
</result-types>
3 Creamos nuestra nueva etiqueta modificada
-
package freemarker.ext.jsp;
-
-
import java.io.IOException;
-
-
import javax.servlet.*;
-
import javax.servlet.http.*;
-
import javax.servlet.jsp.JspException;
-
import javax.servlet.jsp.PageContext;
-
import javax.servlet.jsp.tagext.BodyContent;
-
import javax.servlet.jsp.tagext.BodyTag;
-
import javax.servlet.jsp.tagext.Tag;
-
-
import freemarker.template.*;
-
import com.opensymphony.webwork.*;
-
import com.opensymphony.webwork.views.freemarker.*;
-
import com.opensymphony.xwork.util.*;
-
import com.opensymphony.xwork.*;
-
-
/**
-
* Simple implementation of JSP tag to allow use of FreeMarker templates in
-
* JSP. Inspired by similar class in Velocity template engine developed by
-
* <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
-
*
-
* @author Attila Szegedi
-
*/
-
public class FreemarkerWebworkTag implements BodyTag {
-
private Tag parent;
-
private BodyContent bodyContent;
-
private PageContext pageContext;
-
// private SimpleHash root;
-
private TemplateModel root;
-
private Template template;
-
private boolean caching = true;
-
-
public boolean getCaching() {
-
return caching;
-
}
-
-
public void setCaching(boolean caching) {
-
this.caching = caching;
-
}
-
-
this.name = name == null ? "" : name;
-
}
-
-
public Tag getParent() {
-
return parent;
-
}
-
-
public void setParent(Tag parent) {
-
this.parent = parent;
-
}
-
-
public int doStartTag() {
-
return EVAL_BODY_BUFFERED;
-
}
-
-
public void setBodyContent(BodyContent bodyContent) {
-
this.bodyContent = bodyContent;
-
}
-
-
public void setPageContext(PageContext pageContext) {
-
this.pageContext = pageContext;
-
root = null;
-
}
-
-
public void doInitBody() {
-
}
-
-
public int doAfterBody() {
-
return SKIP_BODY;
-
}
-
-
public void release() {
-
root = null;
-
template = null;
-
name = "";
-
}
-
-
public int doEndTag()
-
throws JspException {
-
if (bodyContent == null) {
-
return EVAL_PAGE;
-
}
-
-
try {
-
Configuration cfg = FreemarkerManager.getInstance().getConfiguration(ServletActionContext.getServletContext());
-
if (template == null) {
-
// template = new Template(name, bodyContent.getReader());
-
-
template = new Template(name, bodyContent.getReader(), cfg);
-
}
-
-
if (root == null) {
-
// root = new SimpleHash();
-
// root.put("page", new JspContextModel(pageContext, JspContextModel.PAGE_SCOPE));
-
// root.put("request", new JspContextModel(pageContext, JspContextModel.REQUEST_SCOPE));
-
// root.put("session", new JspContextModel(pageContext, JspContextModel.SESSION_SCOPE));
-
// root.put("application", new JspContextModel(pageContext, JspContextModel.APPLICATION_SCOPE));
-
// root.put("any", new JspContextModel(pageContext, JspContextModel.ANY_SCOPE));
-
-
root = createModel(cfg.getObjectWrapper());
-
}
-
// System.out.println("FreemarkerWebworkTag");
-
template.process(root, pageContext.getOut());
-
try {
-
pageContext.handlePageException(e);
-
}
-
catch (ServletException e2) {
-
throw new JspException(e2.getMessage());
-
}
-
throw new JspException(e2.getMessage());
-
}
-
} finally {
-
if (!caching) {
-
template = null;
-
}
-
}
-
-
return EVAL_PAGE;
-
}
-
-
protected TemplateModel createModel(ObjectWrapper wrapper) throws TemplateModelException {
-
ServletContext servletContext = ServletActionContext.getServletContext();
-
HttpServletRequest request = ServletActionContext.getRequest();
-
HttpServletResponse response = ServletActionContext.getResponse();
-
OgnlValueStack stack = ServletActionContext.getContext().getValueStack();
-
-
ActionInvocation invocation = (ActionInvocation) request.getAttribute(ServletFreemarkerDispatcherResult.REQUEST_ID);
-
-
Object action = null;
-
if (invocation != null) {
-
action = invocation.getAction(); //Added for NullPointException
-
}
-
return FreemarkerManager.getInstance().buildTemplateModel(stack, action, servletContext, request, response, wrapper);
-
}
-
}
El código original que no es necesario está solamente comentado. El cambio consiste en eliminar la creación del objeto "root" (el modelo) con un SimpleHash() y lo sustituimos por el resultado del método createModel() (cuyo código está sacado del propio Webwork, cuando utilizamos el result de Freemarker directamente). Para crear este modelo, es necesario el Action actual, el cual nuestro dispatcher modificado se ha encargado de dejar en el request. Utilizamos este Action para llamar a FreemarkerManager.getInstance().buildTemplateModel(), el cual es el que realmente crea el modelo.
4 Modificamos el taglib para que utilice nuestra nueva etiqueta.
<tag>
<name>template</name>
<tagclass>freemarker.ext.jsp.FreemarkerWebworkTag</tagclass>
<bodycontent>tagdependent</bodycontent>
<info>Allows evaluation of FreeMarker templates inside JSP</info>
<attribute>
<name>caching</name>
<required>false</required>
</attribute>
</tag>
Y con todo esto ya podremos utilizar nuestras plantillas ftl dentro de nuestros JSP accediendo al modelo de beans de Webwork.















