Wednesday, November 12, 2014

Integrating Grails 2.4.4 with CMS

In our company we use Typo3 CMS to maintain our dynamic contents - copywriting, descriptions etc. The content is actually stored as GSP pages - meaning even in our CMS we use tags such as <a:message/>or our custom tags <z:whatever/>. Basically the way it works we go to CMS every time our URLMapping matches certain pattern to extract and display the content. Once we retrieve the information we use the GroovyPagesTemplateEngine, as provided by Grails, to render the page. Now, with this rendered content we just call the render method to get the actual content rendered providing the layout parameter so the content nicely fits into our app. And it works surprisingly. In a bit of pseudo-code it looks like this:


def template = HTTPClient.get (cmsURL)
def template = groovyPagesTemplateEngine.createTemplate(template, "whateverName")
def result = new StringWriter()
template.make().writeTo(result)
def pageContent = result.toString()
render(text:pageContent, encoding:"UTF-8", type:"text/html", layout: layout)

As you can see it is a fairly straight forward solution. No catches here. Obviously under the hood we do pretty crazy stuff as Grails has to parse the template and execute it in the context of the current request, however from our point of view it looks simple.

All this worked great on Grails 2.3. Then we decided to move onto Grails 2.4. We upgraded a couple of dependancies and so on and were looking forward to see our pages in the new shiny Grails 2.4 look and feel. That didn't happen though. The reason was our CMS. Pages were fetched, templates rendered however the output was stripped, we could only see blank pages and we were asking WHY!?

After several hours of looking under the bonnet of our application we'd finally figured out the reason. To actually explain the why let me just briefly explain how layouts work in Grails 2.3 and Grails 2.4. In version 2.3 Grails uses Sitemesh filter. Basically all layout related operations are stored in a request object and once they leave controllers they're processed by the Sitemesh filter. From this point of view it doesn't matter at what stage you do what with layouts - all is post-processed in the filter. Grails 2.4 has ditched the filter and applies Sitemesh in the render method. Although it looks like a minor change it actually means a lot. It most importantly means that timing is important now. If you just render your pages then the impact is zero - if you compile your GSPs on the fly you have to do several steps to make the process working.

First you have to prepare the request for the initial GSP rendering. That would normally happen during the rendering but we run our GSP before we call render so we have to do it manually. Now when rendering templates all layout elements are processed by the RenderTagLib. This library finds all layout related tags and saves them into a GSPSitemeshPage instance stored in a request. So what we do now is that before calling the initial rendering of our template coming from CMS we create an instance of that class and store it in a request. Once this is done we move that particular instance into a different place in request where the layout rendering can locate it and we do the layout rendering. After that we have our HTML nicely stored in a String variable. The last step is to render it as a text. In a pseudo code it could look like:


static final GSP_SITEMESH_PAGE ='org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutView.GSP_SITEMESH_PAGE'
static final PAGE = "__sitemesh__page";


def template = HTTPClient.get (cmsURL)
request.setAttribute(GSP_SITEMESH_PAGE, request.getAttribute(GSP_SITEMESH_PAGE)?: new GSPSitemeshPage(false)) // that's where we prepare the empty sitemesh page
def sitemeshPage = request.getAttribute(GSP_SITEMESH_PAGE)
def template = groovyPagesTemplateEngine.createTemplate(template, "whateverName")
def result = new StringWriter()
template.make().writeTo(result)
def pageContent = result.toString()
def template = (GroovyPageTemplate) findLayout(request, layoutName)
result = new StringWriter()
request.setAttribute(PAGE, page) //layout rendering needs the simemesh page here
template.make()writeTo(result)
pageContent = result.toString()

render(text:pageContent, encoding:"UTF-8", type:"text/html")


And that's the end of the story. Just keep in mind that every GSP template you load from CMS is actually compiled into Java class and loaded by the classloader. That means that naming the templates is more important than you might have actually thought!