Use of Sitemesh in Grails 3
Asked Answered
E

1

6

I'm migrating a set of grails 2.0.4 applications to version 3.x. All of them are deployed in the same server together with a number of java applications. Both sets of java and grails applications have a common look and feel using sitemesh and freemarker templates. But with grails 3.x I can't make the commond decoration work, the application insists in using layouts/main.gsp to render my gsp instead.

So far (grails 2.0.4) providing a common decoration is rather straight; the file /WEB-INF/decorators.xml of each grails application provides with references to the applicable freemarker templates. And the web.xml includes the sitemesh filter and the freemarker decorator servlet declarations and mappings

decorators.xml:

<?xml version="1.0" encoding="UTF-8"?>
<decorators defaultdir="/">
    <excludes>
        <pattern>/ND/*</pattern>
        <pattern>/*/ND/*</pattern>
     </excludes>
     <decorator name="freemarker" page="myftl.ftl">
         <pattern>/*</pattern>
     </decorator>
</decorators>

Sitemesh filter and freemarker servlet from web.xml:

<filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
    <servlet-name>sitemesh-freemarker</servlet-name>
    <servlet-class>com.opensymphony.module.sitemesh.freemarker.FreemarkerDecoratorServlet</servlet-class>
    <init-param>
      <param-name>TemplatePath</param-name>
      <param-value>class://</param-value>
    </init-param>    
    <init-param>
      <param-name>default_encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>          
    <load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>sitemesh-freemarker</servlet-name>
    <url-pattern>*.ftl</url-pattern>
</servlet-mapping>

What I've tried:

  • I've moved the decorators.xml under src/main/webapp/WEB-INF
  • In grails 3.x sitemesh filter is not present anymore and so, I've removed sitemesh.xml
  • web.xml is not used, so now I've defined the freemarker servlet at spring/resources.groovy:

resources.groovy:

beans = {
    sitemeshFreemarkerServlet(ServletRegistrationBean) {
        servlet = bean(FreemarkerDecoratorServlet)
        urlMappings = ["*.ftl"]
        loadOnStartup = 2
    }
}

However, the grails 3.x applications insists in using layouts/main.gsp to render my gsp pages. It seems that decorators.xml is not being processed. What am I missing?

Erlond answered 2/2, 2016 at 15:10 Comment(0)
D
4

Maybe is a ugly hack, but you could superpose your sitemesh processing to grails one:

  • Register a custom sitemesh filter in Application class (or spring/resources.groovy) :

    @Bean
    FilterRegistrationBean sitemeshFilterRegistrationBean() {
        FilterRegistrationBean reg=new FilterRegistrationBean()
        reg.setFilter(new MySitemeshFilter());
        reg.setInitParameters(["configFile":"WEB-INF/my.sitemesh.xml"])
        reg.setUrlPatterns(["/*"])
        reg.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ERROR);
        reg.setOrder(0);
        return reg;
    }

  • The sitemesh config must not be the default one, cause grails continues reading it
  • Register the freemarker servlet to proccess ftl's:

    @Bean
    ServletRegistrationBean freeMarkerServletRegistrationBean(){
        ServletRegistrationBean reg=new ServletRegistrationBean(new 
          FreemarkerDecoratorServlet(),"*.ftl");
        reg.addInitParameter("TemplatePath", "class://");
        reg.addInitParameter("default_encoding", "UTF-8");
        // etc
        return reg;
    }

  • Add the custom sitemesh filter:
 
import com.opensymphony.module.sitemesh.Config;
import com.opensymphony.module.sitemesh.Factory
import com.opensymphony.module.sitemesh.factory.DefaultFactory;
import com.opensymphony.sitemesh.ContentProcessor;
import com.opensymphony.sitemesh.DecoratorSelector;
import com.opensymphony.sitemesh.compatability.DecoratorMapper2DecoratorSelector;
import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;

import grails.util.Holders;
import javax.servlet.FilterConfig

class MySitemeshFilter extends com.opensymphony.sitemesh.webapp.SiteMeshFilter {

    private static final String MY_SITEMESH_FACTORY = "my.sitemesh.factory";
    private FilterConfig filterConfig;
    @Override   
    public void init(FilterConfig filterConfig) {
        super.init(filterConfig);
        filterConfig.getServletContext().setAttribute("grailsApplication", Holders.grailsApplication);
        this.filterConfig=filterConfig;
    }

    protected Factory getFactory(FilterConfig filterConfig) {
        Config config=new Config(filterConfig)
        Factory f=(Factory)config.getServletContext().getAttribute(MY_SITEMESH_FACTORY);
        if (f==null) {
            f=new DefaultFactory(config);
            config.getServletContext().setAttribute(MY_SITEMESH_FACTORY, f);
        }
        return f;
    }

    @Override
    protected DecoratorSelector initDecoratorSelector(SiteMeshWebAppContext webAppContext) {
        Factory factory = getFactory(filterConfig);
        factory.refresh();
        return new DecoratorMapper2DecoratorSelector(factory.getDecoratorMapper());
    }
}

  • In this filter you have to override the decorator selector in a new sitemesh factory, cause the default one is a singleton (sic) and grails already registered it for its internal gsp proccesing
  • You should not have to override the content proccesor (initContentProcessor method), to let grails proccess gsp's with the default sitemesh factory
  • If you want to get more features (like excluded patterns) you will need to override the entire doFilter method to use your new contentProcessor in contentProcessor.handles(webAppContext)
  • Add your my.sitemesh.xml and decorators.xml in src/main/webapp/WEB-INF
  • I've added grailsApplication to servlet context in the filter initialization so you can use it in ftl templates
  • If you want to skip some layouts, add conditions by environment in the internal grails layout
Documentation answered 9/2, 2016 at 6:53 Comment(1)
Maybe is not the best solution but it works! Thanks a lot!Rolph

© 2022 - 2024 — McMap. All rights reserved.