Apache-Tiles3 and Freemarker where freemarker result is processed from String, renderer output is too early

StackOverflow https://stackoverflow.com/questions/17779865

Frage

I'm integrating struts2, tiles3 and Freemarker (templates from a DB). The integration is mostly done, but the freemarker renderer is rendering to the result immediately instead of inserting its content into the template at the expected location.

Any advice on how to correct this?

Expected Output:

<!DOCTYPE html>
<html>
    <body>
        <h1>Template</h1>
<strong>A bold statement!</strong><div>a message from the Action...</div><div> I came from: the_location</div><div>a message from the Action...</div>
    </body>
</html>

Actual Output (Not formatted for readability because this is a template issue and not an html issue so accuracy of output counts):

<strong>A bold statement!</strong><div>a message from the Action...</div><div> I came from: the_location</div>


<!DOCTYPE html>
<html>
    <body>
        <h1>Template</h1>

    </body>
</html>

tiles-defs.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
    <definition name="test_def2" template="/WEB-INF/template/template.jsp"> 
        <put-attribute name="body" value="the_location" type="db_freemarker"/>
    </definition>
</tiles-definitions>

/WEB-INF/template/template.jsp

<%@taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@taglib prefix="s" uri="/struts-tags"%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
    <body>
        <h1>Template</h1>
        <tiles:insertAttribute name="body"/>
    </body>
</html>

Created and registered a DbFreemarkerTilesRenderer with key "db_freemarker", this is the implementation of DbFreemarkerTilesRenderer:

package com.quaternion.struts2.result.freemarker;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tiles.request.Request;
import org.apache.tiles.request.freemarker.render.FreemarkerRenderer;
import org.apache.tiles.request.render.CannotRenderException;
import org.apache.tiles.request.servlet.ServletRequest;


public class DbFreemarkerTilesRenderer extends FreemarkerRenderer{
    public DbFreemarkerTilesRenderer(){
        super(null); //Expects a AttributeValueFreemarkerServlet  
    }

        /** {@inheritDoc} */
    @Override
    public void render(String location, Request request) throws IOException {
        if (location == null) {
            throw new CannotRenderException("Cannot dispatch a null path");
        }
        ServletRequest servletRequest = org.apache.tiles.request.servlet.ServletUtil.getServletRequest(request);
        HttpServletRequest httpRequest = servletRequest.getRequest();
        HttpServletResponse httpResponse = servletRequest.getResponse();

        DataBaseTemplateLoader dataBaseTemplateLoader = new DataBaseTemplateLoader();
        try {
            dataBaseTemplateLoader.render(location, httpRequest, httpResponse);
        } catch (Exception ex) {
            Logger.getLogger(DbFreemarkerTilesRenderer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

The "DataBaseTemplateLoader" actually loads a freemarker template from a hard coded string, but it will load from a DB once this issue is resolved (see the render method the rest of the class is included for context, Struts2 developers will recognize it as a Result type, the idea being the Freemarker DB functionality can be provided directly to Struts2 as a Result):

package com.quaternion.struts2.result.freemarker;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.ServletDispatcherResult;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.util.ContextUtil;


public class DataBaseTemplateLoader extends ServletDispatcherResult {

    private static final Logger log = Logger.getLogger(DataBaseTemplateLoader.class.getName());
    private String defaultEncoding = "ISO-8859-1"; //TODO: Hardcoded?
    private FreemarkerManager freemarkerManager;
    private Configuration configuration;

    //Inject works if this is a Struts2 component (Result) however
    //when using this from Tiles, the following is not injected...
    @Inject
    public void setFreemarkerManager(FreemarkerManager mgr) {
        this.freemarkerManager = mgr;
    }

    public DataBaseTemplateLoader() {
        super();
    }

    public DataBaseTemplateLoader(String location) {
        super(location); //TODO: look up template based on "location" string
    }

    protected Configuration getConfiguration() throws TemplateException {
        if (freemarkerManager == null){
            //Force injection
            freemarkerManager  = ActionContext.getContext().getContainer().getInstance(FreemarkerManager.class);
        }
        return freemarkerManager.getConfiguration(ServletActionContext.getServletContext());
    }

    @Override
    public void doExecute(String location, ActionInvocation invocation) throws Exception {
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        render(location, request, response);
    }

    public void render(String location, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //location is String passed from Struts2 action which will formated 
        //in such a way to apply conventions though to view
        //Format: namespace#action#method 
        //setLocation(location);

        StringTemplateLoader stringLoader = new StringTemplateLoader();

        //BOTH "name" and "source" will be retrieved from the DB
        String name = "firstTemplate";
        String source = "<strong>A bold statement!</strong><div>${action.message}</div><div> I came from: " + location + "</div>";

        stringLoader.putTemplate(name, source);
        // It's possible to add more than one template (they might include each other)
        // String secondTemplate = "<#include \"greetTemplate\"><@greet/> World!";
        // stringLoader.putTemplate("greetTemplate", secondTemplate);
        freemarker.template.Configuration cfg = getConfiguration();
        cfg.setTemplateLoader(stringLoader);
        Template template = cfg.getTemplate(name);
        Map map = ContextUtil.getStandardContext(ActionContext.getContext().getValueStack(), request, response);

        response.setContentType("text/html;charset=" + defaultEncoding);
        template.process(map, response.getWriter());
    }
}

The Struts2 Action (not needed, just shows where "a message from the Action..." comes from:

package com.quaternion.tilesdb.action;

import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.Result;

/**
 * Tests Tiles with Freemarker integration, where the freemarker template 
 * is parsed from a String, this is to allow later integration with freemarker
 * database templates
 */
@ParentPackage("quaternion-results")
@Result(type="tiles", location="test_def2")
public class TilesFtlTest2 extends ActionSupport{
    private String message = "a message from the Action...";

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
War es hilfreich?

Lösung

I just want to close this question off, the answer is I was missing tiles configuration from web.xml (the Freemarker servlet configuration).

Here is the web.xml which allows the above to work:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <listener>
        <listener-class>com.quaternion.struts2.result.freemarker.MoreCompleteAutoloadTilesListener</listener-class>
        <!--<listener-class>org.apache.tiles.extras.complete.CompleteAutoloadTilesListener</listener-class>-->
    </listener>    
    <!-- Following is required to enable freemarker(ftl) support-->
    <servlet>
        <servlet-name>freemarker</servlet-name>
        <servlet-class>org.apache.tiles.request.freemarker.servlet.SharedVariableLoaderFreemarkerServlet</servlet-class>

        <!-- FreemarkerServlet settings: -->
        <init-param>
            <param-name>TemplatePath</param-name>
            <param-value>/WEB-INF/template/</param-value>
        </init-param>
        <init-param>
            <param-name>NoCache</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>ContentType</param-name>
            <param-value>text/html</param-value>
        </init-param>

        <!-- FreeMarker settings: -->
        <init-param>
            <param-name>template_update_delay</param-name>
            <param-value>0</param-value> <!-- 0 is for development only! Use higher value otherwise. -->
        </init-param>
        <init-param>
            <param-name>default_encoding</param-name>
            <param-value>ISO-8859-1</param-value>
        </init-param>
        <init-param>
            <param-name>number_format</param-name>
            <param-value>0.##########</param-value>
        </init-param>
        <init-param>
            <param-name>org.apache.tiles.request.freemarker.CUSTOM_SHARED_VARIABLE_FACTORIES</param-name>
            <param-value>tiles,org.apache.tiles.freemarker.TilesSharedVariableFactory</param-value>
        </init-param>
        <load-on-startup>5</load-on-startup>
    </servlet>
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <welcome-file-list>
        <welcome-file>/index.action</welcome-file>
    </welcome-file-list>
</web-app>
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top