Menu

accessing spring.ftl in freemarker includes

Marc
2008-06-28
2013-05-30
  • Marc

    Marc - 2008-06-28

    Hi,

    I haven't been able to access spring.ftl in freemarker includes and Sitemesh decorators. What is the best way to do it?

    This is what they had to say about it in the Spring forum:

    Examine the source for springframework/web/servlet/view/AbstractTemplateView.java and you can see this code

                           // Expose RequestContext instance for Spring macros.
                           model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
                                           new RequestContext(request, getServletContext(), model));

    at line 168.. which is where the FreeMarkerView sub-class receives the macro context as a request attribute.

    A similar bit of code would have to be added to the Sitemesh-FreemarkerServlet or you can sub-class that
    class yourself and add code to insert the macro context on the HttpRequest object before Sitemesh invokes
    Freemarker on the decorator template.    You probably would have to use Dependancy injection into your
    sub-class version of the Sitemesh-FreemarkerServlet to get access to the macro context object to add to the
    request object.

    Cheers,

    Marc

     
    • Nobody/Anonymous

      If by accessing spring.ftl you mean #import-ing it, then it has nothing to do with the model. The usual way of accessing macro libraries is <#import "/spring.ftl" as spring> or something like that. If it's different in Sitemesh, I don't know....

       
    • JohnA

      JohnA - 2008-07-03

      There is a basic conflict between the Spring framework and SiteMesh.  SiteMesh-Freemarker is actually implemented
      as a Servlet filter  (the Sitemesh-Freemarker Servlet just does a pre template process setup).  The "Page Filter"
      is what really does all the work and it does not do what the FreeMarkerView in spring does, which is how the
      spring.ftl file, embedded inside the spring.jar (or is it spring-webmvc.jar?) is made accessable
      (by default in spring in spite of what the doc say) to the FM template used as views in a web application.

      The problem occurs when a web-app trys to use/include spring.ftl in the sitemesh decorator.ftl file.  When
      the sitemesh "Page Filter" invokes the Freemarker.template.process() method to convert the decorator.ftl
      file into the real HTTP response page, the data model Sitemesh creates and used does NOT contain an entry
      for the spring.ftl, that is automatically and silently provided by the Spring Freemarker.view code.

      SO IT DON'T WORK, and short of binding the Sitemesh Page Filter directly to Spring (to which i expect the
      Sitemesh folks would strongly object) they are not likely to change SiteMesh to fix this.  I am currently
      building a web-app using Sitemesh and Freemarker with Spring and ran this issue down in the Sitemesh
      source.

      There is also another NOTEABLE difference between the way Spring invokes FreeMarker in FreeMarkerView and
      the way the FreeMarker Servlet does; specifically the Freemarker Servlet builds the 4 xxxxHashModels for
      "page", Request, Session, and Application attributes/properties and inserts them all into the data model
      using a wrapper named  AllHttpScopesHashModel. 

      What this model adds is automatic NOT FOUND fall-through searching of all 4 scopes for a Freemarker variable reference (usually in an interpolation,  e.g. ${message}); I.E is ${message} is not found in page, then
      Request is searched, and if not found, then Session is searched, ...

      What i discovered is that Spring puts the same xxxHashModels into the data model, but it does NOT go the extra
      step of using the AllHttpScopesHashModel to wrap them, and thus the fall-through searching of all 4 scopes
      doesn't happen;

      THUS if your ${message} was an attribute of the Session scope (from the HttpSession object) a page rendered
      by the FreemarkerServlet (or it's sub-class Sitemesh-Freemarker Servlet) will find ${message} but it will
      have to be referenced as ${Request.message} in a template rendered by FreeMarkerView in Spring.

      I hacked up the FreeMarkerView.java code (cloning AllHttpScopesHashModel as an inner class) and re-coded
      FreeMarkerView.java to correct this design difference and reported it to Spring JIRA as
         [Spring-JIRA] Updated: (SPR-4962) "FreeMarkerView does NOT setup the model as AllHttpScopesHashModel"
      and included the modified FreeMarkerView.java src if anyone wants to tryout the fix this latter issue.

      I just got notified today, by Spring that my "Bug" was demoted to an "Improvement" request.

      FreeMarker's AllHttpScopesHashModel for some odd reason i don't get, does NOT have a public constructor
      (an oversight??) LIKE all the other xxxxHashModels do!, so that is why i had to clone it into spring.

      IF the constructor was made public the spring fix could just use the AllHttpScopesHashModel as is, but
      there may be something i don't understand about Freemarker that mades package scope preferable;
      i haven't got my head around the entire Freemarker source tree, yet.

       
      • Nobody/Anonymous

        Just as a side note, since #include/#import itself is not influenced by the contents of the data-model, that you describe is hardly related to the "spring.ftl" not-found issue itself, that the initial question was about.

         
      • Attila Szegedi

        Attila Szegedi - 2008-08-15

        I made the AllHttpScopesHashModel constructor public -- there wasn't any good reason it was package protected. This'll become available as of 2.3.13

         
        • Attila Szegedi

          Attila Szegedi - 2008-08-15

          Sorry, meant to say 2.3.14 - 2.3.13 is already out.

           
    • JohnA

      JohnA - 2008-07-04

      Actually access to the macros file spring.ftl IS dependent on the data-model, as the following code from
      as the following code from spring AbstractTemplateView.java shows

              if (this.exposeSpringMacroHelpers) {
                  if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
                      throw new ServletException(
                              "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
                              "' because of an existing model object of the same name");
                  }
                  // Expose RequestContext instance for Spring macros.
                  model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
                          new RequestContext(request, getServletContext(), model));
              }

      where     public static final String SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE = "springMacroRequestContext";

      which the spring.ftl bind macros must have to work,  as this fragment from spring.ftl shows

      <#-- _________________________________________________________________________________
      * messageText
      *
      * Macro to translate a message code into a message,
      * using the given default text if no message found.
      -->
      <#macro messageText code, text>${springMacroRequestContext.getMessage(code, text)}</#macro>

      You comment is also technically correct and spring silently adds to the Freemarker template loader path when FreeMarker is configured
      and includes an extra path that allows loading spring.ftl from within spring-webmvc.jar file for the package
      "org.springframework.web.servlet.view.freemarker" .

      In effect spring is providing all the access for both loading spring.ftl and execution of the macros in spring.ftl
      As i recall the FreeMarkerConfigurer.java is the source file that adds the loader path for spring.ftl,
      so if configuration was done another way, the missing extra path will cause the <#include/import   >  to fail.

       
    • JohnA

      JohnA - 2008-07-04

      And even more to the point, is that the template loader path used by the Sitemesh "Page filter" is not configured by the FreeMarkerConfigurer bean
      and so the "silent" addition spring makes to the template loader path is NOT available when the PageFilter invokes Freemarker's template.process() method.

      But even if you copy spring.ftl out of the jar into your web-app context and setup sitemesh so it can load it, that doesn't create the missing
      request context referenced by all the spring.ftl macros.

      Essentially, what this means is that you can use the spring macros in a fooform.ftl file that IS renedered by Freemarker view which becomes
      the  ${body} to be decorated, but NOT use them i the decorator.ftl file itself (or it's include/imports)

       
    • JohnA

      JohnA - 2008-07-05

      I have been reading the Freemarker,  Sitemesh, and Spring sources and i believe there is a fix that will solve this
      problem.  I think it will require adding a method to the Sitemesh-FreemarkerServlet to override createConfiguration()
      so that a new method can be added to allow injection by the spring IOC container of the Freemarker Config used by
      a Spring for the FreemarkerView, so that the SiteMesh-FreemarkerServlet can pass that pre-created Configuration
      object back to the FreemarkerServlet as the result from createConfiguraiton().  This "should" cause SiteMesh to
      end up with the same template loader path as spring and allow access to spring.ftl inside the spring-webmvc.jar.

      I think it can even be done by sub-classing the Sitemesh-FreemarkerServlet ( SpringSiteMeshFreeMarkerServlet ? say that 3 time fast!)

      But it's 4:35 AM (in CA) and past time to get some sleep, so i will report back later if i can create a fix.

       
    • JohnA

      JohnA - 2008-07-07

      After extensive analysis of the Sitemesh and Spring sources i conclude that there is no straight forward solution
      that will connect the Sitemesh FreemarkerDecoratorServlet (i was using the wrong name above) with the Spring
      application context so that the parent FreemarkerServlet (of which SitemeshDecoratorServlet is a sub-class) uses
      the Spring Freemarker Configuration object created by Springs FreeMarkerConfigurer which is auto-detected by
      springs FreeMarkerView class. 

      This is due to the fact that the Sitemesh/FreemarkerServlet is not a Spring servlet managed by the spring IOC
      container, but a stand alone servlet in the Servlet context, but not in the Spring application context. 
      This is as it should be, because the SitemeshServlet is a generic decorator tool, that can decorate output from
      all types of view implementations, .jsp,  .vm, etc, and  to decorate the resulting whole html pages without modifying
      the original.

      I conclude that a better Spring specific "all-Freemarker" solution is to create a FreeMarkerMeshView and performs the
      equivalent multi template rendering directly in Spring.  Since i have been using Sitemesh, but find the incompatibility
      noted in my first comment, about the differing lookup logic, a real problem, i am going in this other direction. 

      I expect this to be a simple solution, that will allow passing any number of named rendered "html content strings" to a base
      layout view template, by using Freemaker directly (not indirectly via a separate servlet rendering in an isolated context).
      This will avoid all the complexity of the sitemesh approach (and give up its generality) and be faster, as it will avoid the
      need to "extract by parsing" the ${head}, ${body}, ${title} content from the separately rendered "sub panels".  A side effect
      of this is that the sub-panel templates will be html fragments of just the sub-panel content, not complete html pages,
      a trade-off made for performance to eliminate the need to parse the content at all.  IE this is a Freemarker composition
      solution, not a Site Mesh solution, nor at the level of the Tile2 solution, but since i am creating a NEW web-app, thats OK 4 me.

       

Log in to post a comment.

MongoDB Logo MongoDB