« Back

JSP Include Buffer

Company Blogs August 22, 2011 By Ray Augé Staff

There are plenty of times when you might want to alter the default views of the portal. I'm not going to go into the why since that is hugely subjective. What I do want to show is the how you can do this in the most maintainable way possible.

The largest concern with core JSP modification in projects is of course maintainability. JSPs have no "extensability" feature of their own and so when taking modified core JSPs into your project you run the risk of causing yourself a tone of heartache during future upgrades.

JSPs don't have any contract that allows for cleanly identifying when something has changed or gone wrong between versions. At least with pure source code you can take a leap and just try compiling against the latest version and although you likely run into many errors, they are quite literally spelled out for you. With JSPs you don't have this luxery and it's quite possible to go weeks or months with JSPs that seem to work after an upgrade but that suddenly start to exhibit bad behavior as your system starts to hit those edge cases or outright fail for the same reason. This is typically because of some unforseen change in the core logic.

Developers often try to mitigate this by doing things like using some complex or just "obvious" comments explaining changed code, hoping they are still around to review the changes on future upgrades...

What's likely to happen is that there ends up being so many JSPs changed and/or that the same developer is not around for the next upgrade, a complete and thorough review of those changes simply doesn't get done, risks are run and the ensuing issues become costly.

So goes the story of "JSP Hell"!

Meanwhile JSPs are still the best performing RAD view techology in java, so they are still used quite heavily.

What to do?

Liferay has long experienced this scenario and though we knew that changing from JSP was either unlikely or unreasonable considering the alternatives, we still had to come up with a good solution to the problem.

The solution is quite simple, the JSP Include Buffer.

The basic principle is to simply buffer, then extend the result that is output from the original JSP. Thus we can change only the parts we care about, and latter if there is a change in the original, it will either cause our extension to fail, or it will be seamlessly integrated, exactly the way a source code change would (even though it won't be a build time error, it will still be far more evident than the previous alternative).

There is one limitation, the JSP that you want to perform this operation on must NOT be a jsp fragment (a fragement is a JSP that you include using the low level <%@ include file="****.jsp" %> directive.

Rather it should be one using an include tag, such as <jsp:include /> or in the case of Liferay <liferay-util:include />, i.e. one that is invoked through a request dispatcher.

The examples I'm going to show will be using the <liferay-uitl:include /> tag.

From a JSP Hook

Liferay offers a plugin type called a "Hook" that allows you to define JSPs that should override (not overwrite) JSPs in the core, and on deploy these jsps replace those in the core. BUT the core JSPs are not lost (remember override not overwrite). The core JSPs are simply re-located, thus they are still accessible.

The core JSPs are relocated to a new name which injects the keyword .portal between the file name and extension. Thus a core JSP having the name start.jsp after being "Hooked" would be located under the name start.portal.jsp (at the exact same path).

How does this help us with the above problem?

Simply it means that we have the original to work with and so we don't need to completely re-implement or reproduce the complete code in our "extended" implementation. We can still call the original, buffer it's output and then manipulate the result to our needs.

Here is a very simple example or wrapping the output of the original with a simple black box (you could do this via CSS but what's the fun in that, and this example could serve many more uses that simple css styling can ever achieve).

<%@ taglib uri="http://liferay.com/tld/util" prefix="liferay-util" %>

<liferay-util:buffer var="html">
    <liferay-util:include
        page="/html/taglib/ui/page_iterator/start.portal.jsp"
        useCustomPage="<%= false %>"
    />
</liferay-util:buffer>

<div style="border: 4px solid red; padding: 4px;">
    <%= html %>
</div>

From a FreeMarker Template

Can the same be achieved from Freemarker you ask? Certainly! And here is the same example as above.

 <#assign liferay_util = PortalJspTagLibs["/WEB-INF/tld/liferay-util.tld"] />

<@liferay_util["buffer"] var="html">
    <@liferay_util["include"]
        page="/html/taglib/ui/page_iterator/start.jsp"
        strict=true
        useCustomPage=false
    />
</@>

<div style="border: 4px solid red; padding: 4px;">
    ${html}
</div>

From a Velocity Template

Hold up! You're kidding right? Velocity can't do jsp tags...

Well it can! It's just not pretty. Why would you do this from a Velocity anyway? Well, you never know what people want to do in their themes and the next blog post I write about how to override JSPs from theme templates will make this here feature all the more interesting for theme developers, AND by supporting Velocity, won't force all those developers to convert their existing themes to Freemarker just so they can override the odd JSP.

So, here goes (remember I don't promise that this is beautiful, only that it works and works well).

#set ($bufferTagClass = $portal.class.forName("com.liferay.taglib.util.BufferTag"))
#set ($includeTagClass = $portal.class.forName("com.liferay.taglib.util.IncludeTag"))

#set ($bufferTag = $bufferTagClass.newInstance())
#set ($V = $bufferTag.setPageContext($pageContext))
#set ($V = $bufferTag.setParent(null))
#set ($V = $bufferTag.setVar('html'))

#if ($bufferTag.doStartTag() == 2)
    #set ($V = $bufferTag.setBodyContent($pageContext.pushBody()))
    #set ($V = $bufferTag.doInitBody())

    #set ($includeTag = $includeTagClass.newInstance())
    #set ($V = $includeTag.setPageContext($pageContext))

    #set ($V = $includeTag.setPage('/html/taglib/ui/page_iterator/start.jsp'))

    #set ($V = $includeTag.setStrict(true))
    #set ($V = $includeTag.setUseCustomPage(false))
    #set ($V = $includeTag.runTag())
    #set ($V = $bufferTag.doAfterBody())
    #set ($V = $pageContext.popBody())
    #set ($V = $bufferTag.doEndTag())
#end

<div style="border: 4px solid red; padding: 4px;">
    ${pageContext.findAttribute('html')}
</div>

And there you have it!

Now you'll notice that I bolded a coupled of lines above in the template examples. These lines, which set the field strict on the include tag to true, are VERY important. They tell the underlying tag NOT check if there are overrides for the specified JSP include (otherwise you may end up with infinite recursion and likely stack overflow errors ;) ).

 

I hope this can help some of you to biuld more maintainable solutions on Liferay with less fear of the upgrade process.

Threaded Replies Author Date
[...] Ok, so in the last post I talked about... Anonymous August 22, 2011 1:12 PM
[...] Ok, so in the last post I talked about... Anonymous August 22, 2011 1:22 PM
Our approach to maintainability in this area is... Kieran Brady August 23, 2011 5:31 AM
Hey Kieran, Liferay has been using this... Ray Augé August 23, 2011 9:19 AM
Hi Ray - thanks for the reply. I guess I'm just... Kieran Brady August 24, 2011 3:10 AM
Hi Ray, I am new to Liferay so, please forgive... Raymond Gardner August 26, 2011 12:43 PM
I guess that is the case, to modify the markup... Sergio Sánchez September 23, 2011 2:47 AM
You're right on both counts... I wasn't saying... Ray Augé September 23, 2011 9:46 AM
[...] Basically you can't:... Anonymous April 8, 2013 6:48 AM
Hi Ray, Thanks for the post. The code... Hari babu September 10, 2013 2:41 AM
Ok, the issue is that CMS template don't have... Ray Augé September 12, 2013 6:29 AM
So how can i achieve this in vm? Luca Orlandi December 4, 2014 7:12 AM
Hi Ray, I used what you suggest but I got an... Nabil Bahtat March 13, 2014 2:25 PM

[...] Ok, so in the last post I talked about JSP Include Buffer. That post talked about how you'd override JSPs from Liferay's core in a maintainable way. Now I'd like to introduce you to another Liferay... [...] Read More
Posted on 8/22/11 1:12 PM.
[...] Ok, so in the last post I talked about JSP Include Buffer. That post talked about how you'd override JSPs from Liferay's core in a maintainable way. Now I'd like to introduce you to a Liferay Theme... [...] Read More
Posted on 8/22/11 1:22 PM.
Our approach to maintainability in this area is that whenever we want to override a JSP in a hook, we take the original Liferay version and check it into our hook project in SCM, then apply our customisations and check those in. This way we can make a diff of exactly what the changes are and can re-apply them if neccesary when upgrading. Simple and easy to explain.

Having to manipulate the buffered HTML as described sounds like crazy talk to me for all but the simplest use cases - why provide a method to override JSP in hooks, then give a further method to side-step the override to get to the original, and then re-process the result!? This makes your hook code less maintainable/fathomable and more spaghettified IMHO.
Posted on 8/23/11 5:31 AM.
Hey Kieran,

Liferay has been using this pattern for hooks already for a couple of years. We are now just extending it to themes since JSP changes are global, and the requirement was for themes to support non-global changes to the view tier.

As much as you may like the diff approach it has been our experience that, beyond a certain magnitude, it becomes unmaintainable. The buffer approach has proven itself over the course of several versions in several projects to be far better than the diff approach.

Note we use this technique _from_ hooks to avoid having to replicate the original code. This is much closer to "extends" behavior of java than a "diff" and also results in our hooks having far less code than using the diff approach.

Another point is that you can't place JSPs in a theme and so you'd have to re-write the entire logic of the JSP in a template language, which I don't have to tell you will be itself a challenge on top of having to maintain the converted mapping over several versions of the product.
Posted on 8/23/11 9:19 AM in reply to Kieran Brady.
Hi Ray - thanks for the reply. I guess I'm just thinking about our specific use cases and what suits us - as you say, its great to be able to 'extend' in the OO sense, it just seems somewhat unreachable through the manipulation of buffered HTML, particularly if there is a core element or set of elements in that page that you want to change.
Posted on 8/24/11 3:10 AM in reply to Ray Augé.
Hi Ray,

I am new to Liferay so, please forgive my naiveness.

All I see happening here is a red colored border being wrapped around the start.jsp output. That is great if your objective is this simple.

How do you change the guts of a jsp using this method? Do we have to parse the html variable and seek out places to add and places to replace?

I'm somewhat lost.
Posted on 8/26/11 12:43 PM in reply to Kieran Brady.
I guess that is the case, to modify the markup generated by the core JSP file.
I think that could be useful for small modifications like deleting some tags (if we want to disable some functionality an there are not any other menas to do that)
If we want to change a lot the behaviour of the core JSP file, wouln't it be better to modify the JSP file as we were doing until now?
Also, another use case could be to attach custom JSP code before or after the core JSP code. In that case this new tag could be very useful.
Am I right?
Posted on 9/23/11 2:47 AM in reply to Raymond Gardner.
You're right on both counts... I wasn't saying that this is the only way, just another method to add to your toolbox. The Liferay methodology is to use the solution that is the cleanest for the current problem. There are no one size fits all solutions. Otherwise, we'd have all been out of a job log ago.
Posted on 9/23/11 9:46 AM in reply to Sergio Sánchez.
[...] Basically you can't: http://www.liferay.com/community/forums/-/message_boards/message/14269385 BUT, if you take a look here: http://www.liferay.com/web/raymond.auge/blog/-/blogs/jsp-include-buffer... [...] Read More
Posted on 4/8/13 6:48 AM.
Hi Ray, Thanks for the post.

The code that you mentioned under section "From a Velocity Template" is not working in Liferay CMS Templates. because I only see the red border with the following text " ${pageContext.findAttribute('html')}" displayed, instead of the actual start.jsp output.

Also I unchecked the "Cacheable" checbox of CMS template to have the http-request available to CMS template.

Request you to please help me here...

Thanks,
Hari.
Posted on 9/10/13 2:41 AM.
Ok, the issue is that CMS template don't have access to "true" request/response/pageContext as a JSP or theme template has.

So this context variable: $pageContext does not exist.
Posted on 9/12/13 6:29 AM in reply to Hari babu.
Hi Ray,
I used what you suggest but I got an error stackoverflow when I try
to manipulate the resulting html string in order to add some specific code. For example, when I search a string in html var using stringutil.contains or html.indexof, I got an java.lang.StackOverflowError

Thanks for your help
Nabil
Posted on 3/13/14 2:25 PM.
So how can i achieve this in vm?
Posted on 12/4/14 7:12 AM in reply to Ray Augé.