2

I have several portlets which each use an aggregation of pooled CSS and JS files within a single web application. Currently each portlet will add appropriate head tags in doHeaders(). However, this causes duplicate tags within the head when more than one portlet is on the same page.

Currently the portlets are deployed on eXo which runs on GateIn. eXo has it's own JS AMD framework and portlet skin system, but we add head elements using doHeaders() to be as platform-agnostic as possible for risk mitigation.

Head elements are added in the following manner:

@Override
public void doHeaders(RenderRequest request, RenderResponse response)
{
    Element element = response.createElement("link");
    element.setAttribute("type", "text/css");
    element.setAttribute("rel", "stylesheet");
    element.setAttribute("href", request.getContextPath() + "/service/resource/themes/stylesheet.css");
    response.addProperty(MimeResponse.MARKUP_HEAD_ELEMENT, element);
}

I need to remove duplicate entries from the head or prevent duplicates from being written in the first place.

I am attempting to write a common RenderFilter which could strip duplicate head elements. But I cannot seem to get access to the current Element properties from the RenderResponse; I can only setProperty() or addProperty().

I could also write the RenderFilter to replace each individual portlet's doHeaders() method and add the entire CSS and JS pool to the head. But I cannot ensure that this logic would run only per user session page render.

Benjamin Soddy
  • 557
  • 1
  • 6
  • 22
  • What portlet container are you using? Configuration of "header" files in Liferay is done differently. What is rendering the common page in your system? – Charles Forsythe Nov 05 '13 at 18:41
  • We are currently using [eXo Platform](http://www.exoplatform.com/company/en/home) on a Tomcat server. We tailor the web application code base to be as generic as possible for risk management, should we need to support other platforms or migrate. This is why all header references are currently done through doHeaders(), even though eXo has it's own JS AMD framework and portlet skin system. – Benjamin Soddy Nov 05 '13 at 18:46
  • Sadly I don't think there is a standard way in the portlet world for that. But I believe you can achieve this in a generic way by saving in a list (in a custom service or in the portlet session with application scope or in a thread local, ...) the already added resources, then call the addProperty only if the resource is not present in this list. And of course you have to take care to reset correctly this list for each http request. – Thomas Nov 06 '13 at 10:08
  • Hi Thomas! After some grep work, I actually think I'm on to something. Although it's not a platform-independent solution, it looks promising at allowing use of doHeaders() without duplicate elements. I was thinking about using Util.getPortalRequestContext().getExtraMarkupHeaders() to detect what has already been added. I'll have some progress on this later today, but it looks promising. – Benjamin Soddy Nov 06 '13 at 16:00
  • Yes, Util.getPortalRequestContext().getExtraMarkupHeaders() contains all the markup headers so you should easily achieve what you want to do. But as you said, it is not platform-agnostic ;) – Thomas Nov 06 '13 at 16:44

1 Answers1

2

A solution I've currently implemented is a RenderFilter which writes the entire JS and CSS resource pool to the head if the pool is not already written. This is somewhat sub optimal as detecting pending head elements is not something that I've found can be done in a platform-independent manner. However, deciding what to do with redundant head elements is completely up to platform delegation, as JSR-286 does not dictate what should be done in this case.

Through use of eXo's PortalRequestContext, it's possible to obtain the list of pending head elements. By adding a meta element to identify the resource pool, the RenderFilter can then decide if the pool needs to be written or if it already has been written.

Here is the basic detection logic:

boolean addHeaderElements = true;
if (Util.getPortalRequestContext() != null && Util.getPortalRequestContext().getExtraMarkupHeaders() != null)
{
    for (Element markupHeaderElement : Util.getPortalRequestContext().getExtraMarkupHeaders())
    {
        if (markupHeaderElement.getTagName().equalsIgnoreCase("meta") &&
            markupHeaderElement.getAttribute("name") != null &&
            markupHeaderElement.getAttribute("name").equalsIgnoreCase("project-name"))
        {
            addHeaderElements = false;
            break;
        }
    }
}

This could also be written to operate on a file basis, but my portlets' resource pool is commonly shared to the point that an all-or-nothing approach can be used.

Benjamin Soddy
  • 557
  • 1
  • 6
  • 22