Jesi Tutorial
Jesi Tutorial
By MC
Changelog
1) Introduction
This guide will give the reader a quick overview of how to include Java Edge-Side Includes (JESI)
caching tags in their templates. These tags are translated into ESI markup by the JSP compiler. JESI
tags also take care of setting the appropriate HTTP headers required by the ESI webcache server.
2) Recommended reading
http://www.oracle.com/webapps/online-
help/jdeveloper/10.1.3/state/content/vtTopicFile.developing_with_ojsp%7Cojsp_rjesitaglib~html/nav
Id.4/navSetId._/
http://www.oracle.com/webapps/online-
help/jdeveloper/10.1.2/state/content/locale.en/vtTopicFile.developing_with_ojsp%7Cjesitags%7Eht
ml/navId.4/navSetId._/
http://www.esi.org/jesit_tag_lib_1-0.html
• jesi:template
o supported attributes:
control
cache
expiration
maxRemovalDelay
aclGroup
• jesi:fragment
o supported attributes:
control
cache
expiration
maxRemovalDelay
user
group
aclGroup
absolute
© Jahia Ltd, all rights reserved.
Page 3 of 37
JESI QuickStart
alwaysInvalidate
• jesi:codeblock
o supported attributes:
execute
• jesi:control
o supported attributes:
control
cache
expiration
maxRemovalDelay
4) Example 1
</td>
</tr>
</table>
</jesi:fragment>
</jesi:template >
Simple.jsp
Let us look at the code step-by-step. We first import JESI taglib before declaring this page as an ESI
template using the jesi:template :
Note that this must be the first tag on the page. Scripts or HTML not enclosed within the
jesi:template tag will not be displayed, so be sure to carefully place this tag.
The cache="yes" attribute means that this Template (also known as page skeleton) will be cached by
the ESI webcache for the default period of time of 24 hours.
<jesi:fragment cache="yes">
The <jesi:fragment cache="yes"> tag notifies the start of an ESI Fragment. It will also be cached
for the default time period. It is equivalent to <jesi:fragment>.
The above example declares two fragments: one surrounding the main content of the page and
another the footer. Both these fragments must be enclosed within a parent jesi:template tag.
If we wanted to cache these fragments for more (or less) time, we could use the expiration attribute,
specified in seconds, such that:
<jesi:fragment expiration="15">
would mean this fragment is only valid in the ESI cache 15 seconds. Subsequent requests for this
fragment will result in a request to the Jahia server for a fresh version.
<jesi:fragment expiration="150">
this example, on the other hand, will be refreshed every 150 seconds.
To make sure the ESI server always fetches a new version of the Fragment for every request to that
page:
5) Template Tag
The jesi:template tag generates a skeleton for a each page. This skeleton contains everything (e.g.
html and java scripts) declared outside any enclosed jesi:fragment tags and include links to these
fragments.
In practice, the generated skeleton should comprise mostly of include links to fragments and little
content. The majority of the content should be enclosed in jesi:fragments tags. This is why we refer
to it as a skeleton since it establishes the general structure of the page.
The default behaviour of jesi:template tag is to cache each generated skeleton on a per-user basis (see
later for a shared alternative using aclGroup). That is, a new instance of the skeleton is stored in the
ESI webcache for each logged in Jahia user.
Therefore
<jesi:template cache="yes">
is equivalent to
If you know in advance that a page will be the same for groups of users, it is possible to share a
single skeleton instance between each group member:
where getGroupName() will return a unique String describing the group to which the current user
belongs to.
It is also possible to use for example the current user’s properties to group users together. So, if say,
we had a template identical for each user except for its background colour. And the background is
defined in the current user’s profile properties. Then we could simply put the colour name as a group
identifier as such:
All users with the same background colour preference would share the same skeleton.
The group attribute in the examples above is dynamically generated for each user. It is also possible
to include a static group name, as such:
In a similar manner to fragment aclGroup sharing (see later), the jesi:template tag also supports an
additional attribute called aclGroup. Instead of using the username as in the 'user' attribute as defined
in the default behaviour, or generating an arbitrary string for the 'group' attribute, you can use the
aclGroup attribute to share templates between those who belong to the same Access Control List
(ACL) groups.
This is done by basically automatically generating a unique key based on the current user's ACL
group membership. This key is later appended to the fragment URL so that all users with the same
group membership rights will point to the same fragment.
General Comments
The template developer should try to maximize template sharing between users. The reason for this
are twofold:
• When a skeleton is detected as invalid/stale, only one instance needs to be fetch by the ESI
webcache for all users sharing this skeleton, reducing response times and the workload on the
Jahia server necessary to regenerate each skeleton.
• Only one instance of a skeleton is stored on the ESI webcache for each group, reducing
memory requirements on the webcache.
Please refer to the “Final Remarks/Template Sharing” section for caveats on template sharing.
6) Fragment Tag
The jesi:fragment tag declares an area of content which must be cached separately from the skeleton.
Fragments can be shared across different users of the same page; they currently cannot be shared
across different pages (see Final Remarks).
The default caching behaviour is the same as the jesi:template tag, that is per-user caching. Therefore
is equivalent to
<jesi:fragment
cache="yes"
user="<%=Jahia.getThreadParamBean().getUser().getUserKey()%>"
>
You can circumvent this behaviour and specify your own user caching key by setting this attribute.
For example, by setting it to another username if you know in advance they will
be displaying the same fragment.
This fragment declaration, defining the “homepage_footer” static group, can be used to share the
footer fragment on the homepage between all users.
However, if the background colour of the footer depended on a preference setting in the current user's
properties, this solution wouldn't work since the same footer would be displayed to all users
independent of their background colour preferences. To share fragments with the same background
colour, one would simply need to modify the group to :
<jesi:fragment
cache="yes"
group="<%="homepage_footer" + getUserBackgroundColourPreference(currentUser) %>"
>
where getUserBackgroundColourPreference() returns a unique string for the colour selected by the
current user.
The functionality of the 'group' and 'user' attributes are roughly identical
since the 'user' attribute could be used to achieve the same effect by
setting
<jesi:fragment
cache="yes"
user="<%="homepage_footer"
+getUserBackgroundColourPreference(currentUser)%>">
Note that if the 'user' attribute is also set, it will take precedence and the 'group' attribute which will
be ignored.
aclGroup sharing
The jesi:fragment tag also supports an additional attribute called aclGroup. Instead of using the
username as in the 'user' attribute as defined in the default behaviour, or generating an arbitrary string
for the 'group' attribute, you can use the aclGroup attribute to share fragments between those who
belong to the same ACL groups.
This is done by basically automatically generating a unique key based on the current user's ACL
group membership. This key is later appended to the fragment URL so that all users with the same
ACL group memberships will point to the same fragment.
with the same background colour settings in their profiles and with the same group
memberships will see the same list of containers with the same background colour by
setting the tag to:
<jesi:fragment
cache="yes"
aclGroup="<%="homepage_footer"+getUserBackgroundColourPreference(currentUser)%>"
>
Note that if the 'user' or 'group' attribute is also set, it will take precedence and the 'aclGroup'
attribute will be ignored.
If neither 'user', 'group' nor 'aclGroup' attributes are defined, the default behaviour of the
jesi:fragment tag is to cache each fragment by user.
If any content enclosed in this fragment exhibits a special ACL entry for
the current user's name, then a specific group for that single user is
created. This is because this user might have specific rights added or
removed on the content referenced in this fragment.
Absolute fragments
The group/user/aclGroup attributes alone make it possible to share fragments between users of the
same page, but not between users on different pages. The absolute attribute addresses this issue.
In Jahia, a page is defined by its unique /pid/xxx value. So all users accessing a page with say /pid/1
can share fragments. However, fragments cannot be shared between pages /pid/1 and /pid/2.
Using the 'absolute' attribute, fragments can be made available to all pages independent of their pid or
JSP template. The 'absolute' attribute must be used in conjunction with a group/user/aclGroup
attribute to allow cross-page user or group sharing of fragments.
This is a powerful way of sharing content and maximizing cache usage. However it must be used
with caution because the content generated in an absolute fragment must be identical on all pages
sharing this content, otherwise we have no idea what will be displayed. In order to do this, every JSP
template must have the exact same code between absolute fragment tags for each absolute fragment.
This is due to the fact that the ESI server can query any page for an absolute fragment and it will
share it with any other page that displays this absolute fragment.
then we could safely paste it in all JSP template and share it between all users and instantiated Jahia
pages (i.e. pid pages).
In this example, the fontsize variable varies with the current page's pid number and therefore is
specific to each page. Thus, the first page to generate this absolute fragment and store it in the ESI
cache, will be the exactly the same one displayed by every other page displaying this absolute
fragment, independent of their current pid. This of course isn’t workable. One couldn’t use absolute
fragments for this example.
It is therefore important that the content of an absolute fragment generated by any page
be identical, no matter what page is queried for that fragment.
Usage Example 1:
This footer fragment will be shared by all pages, regardless of the original JSP template used.
Usage Example 2:
<jesi:fragment
absolute="true" group="<%="footer"+currentUserPreferences.getFooterBackgroundColour()%>">
This fragment will be shared users with the same footer background colour preference, regardless of
the page or the original JSP template used.
Usage Example 3:
This fragment will be shared by all users belonging to the same user groups, regardless of the page or
the original JSP template used.
Usage Example 4:
This fragment will be shared by a same user across all pages and regardless of the original JSP
template used.
alwaysInvalidate fragments
The alwaysInvalidate attribute was introduced to force invalidations of fragments before its
expiration time or even when no content inside it was changed. It is currently used exclusively
around the page Mode tabs (i.e. Live/Preview/Compare/Edit) in the admin_menu.inc JSP file. We do
this to ensure that it will be invalidated from the ESI cache in all modes if anything is changed on the
page. This is to guarantee that the tabs are updated to reflect the current status of the page.
• 'true' or 'yes': this fragment will be invalided if any other fragment (or template) is
invalidated on the same page.
• ['false'] or 'no': (default) will only be invalidated when explicitly necessary.
If ‘true’, then a fragment is always invalidated if something is changed anywhere else on the page, in
every mode (e.g. live/preview/compare/edit modes). This is used for example on the edit tabs so that
the fragment containing the tabs is invalidated in every mode if any other fragments is invalidated.
This is due to the fact that the Live mode is only invalidated when content is validated through the
workflow; if this was the case then the tabs in Live mode would not always be coherent.
<jesi:fragment alwaysInvalidate="true" aclGroup="true">
This attribute is useful to use on fragments that need to be invalidated if anything is changed on the
page. You can use it in conjunction with aclGroup/group/user attributes. It also works with 'absolute'
attribute but review carefully if that is the behaviour you want.
<jesi:template>
<jesi:fragment group="pagetitle">
Display page title
</jesi:fragment >
<% if (user.isLogged()) { %>
<jesi:fragment >
Display user info
</jesi:fragment >
<jesi:fragment group="pageHeader">
Display page header menu
</jesi:fragment >
<% } else { %>
<jesi:fragment group="pageHeader">
Display page header menu
</jesi:fragment >
<% } %>
<jesi:fragment group="mainContent">
Display main content
</jesi:fragment >
<jesi:template>
There are two problems here: the non-constant number of total fragments
(depending on if user if logged or not) and the varying order in which
they appear. So the ‘user info’ fragment #2 for logged users will also be
the pageHeader fragment #2 for guests. A better way to write this would
be:
<jesi:template>
<jesi:fragment group="pagetitle">
Display page title
</jesi:fragment >
<jesi:fragment >
<% if (user.isLogged()) { %>
Display user info
<% } else {
//do nothing
} %>
</jesi:fragment >
<jesi:fragment group="pageHeader">
Display page header menu
</jesi:fragment >
<jesi:fragment group="mainContent">
Display main content
</jesi:fragment >
<jesi:template>
7) Codeblock Tag
You can optionally use codeblock tags within the template tag, outside of any fragments, to mark
conditional execution of blocks of code.
To avoid needlessly repeating the execution of expensive template code, strategically place the code
within JESI codeblock tags. Configure each codeblock tag according to when you want the code
within it to be executed (whenever the template is requested, whenever a fragment is requested, or
always).
• template: The enclosed code will only be executed when the current request is requesting the
template. This is equivalent to not putting a codeblock tag around the code.
• fragment: The enclosed code will only be executed when a fragment is being fetched, but
won’t be executed when the template is requested.
• always: The enclosed code is always executed (for both fragment and template requests).
To maximally boost performance, we highly recommend using this tag profusely (yet thoughtfully).
8) Control Tag
The JESI control tag controls caching characteristics for JSP pages. You can use a JESI control tag
in the top-level page or any included page, but it is not mandatory.
For example, we use the control tag in the sitemap.jsp in the corporate_templates to prevent the entire
page from being cached:
9) Example 2
<jesi:codeblock execute="always">
<%@ include file="commonDeclarations.inc"%>
</jesi:codeblock>
<jesi:codeblock execute="template">
<%@ include file="topMenuBar.inc"%>
</jesi:codeblock>
<jesi:fragment cache="yes">
<jsp:include page="include/box.jsp" flush="true">
<jsp:param name="id" value="main_1"/>
<jsp:param name="displayDetails" value="true"/>
</jsp:include>
</jesi:fragment>
<jesi:fragment expiration="0">
The current time is <%=(new Date())%>
</jesi:fragment>
</td>
<td><img src="<jahia:contextURL/>/images/pix.gif" width="25" height="12"
alt=""/></td>
<td valign="top">
<jesi:fragment cache="yes">
<jsp:include page="include/box.jsp" flush="true">
<jsp:param name="id" value="right"/>
<jsp:param name="width" value="160"/>
</jsp:include>
</jesi:fragment>
</td>
</tr>
</table>
<jesi:codeblock execute="always">
<%
String organization =
org.apache.commons.lang.StringUtils.trimToEmpty(user.getProperty("organization"));
%>
</jesi:codeblock>
<%
int id = 1;
int newsListPid = -1;
JahiaContainerList newsLinkContainerList1 =
jData.containers().getContainerList( "newsListContainer" + id);
if ( newsLinkContainerList1 != null ) {
Enumeration newsLinkEnumeration = newsLinkContainerList1.getContainers();
while (newsLinkEnumeration.hasMoreElements()) {
JahiaContainer newsLinkContainer =
(JahiaContainer)newsLinkEnumeration.nextElement();
if ( newsLinkContainer != null ){
JahiaPage newsListLink = (JahiaPage)
newsLinkContainer.getFieldObject( "newsListLink" + id);
if ( newsListLink != null ) {
newsListPid = newsListLink.getID();
%><a href="<%=newsListLink.getUrl(jData.params())%>"
class="news"><%=newsListLink.getTitle()%></a><%
}
}
}
}
%>
</jesi:fragment>
</jesi:template >
Example2.jsp
The JSP first declares a skeleton cached on a per-user basis which automatically self-expires every
100 minutes:
<jesi:template expiration="6000">
The codeblock tag is then used to make sure that common variable declarations, javascript includes
and necessary JSP includes are executed every time both a fragment or the skeleton is requested:
<jesi:codeblock execute="always">
<%@ include file="commonDeclarations.inc"%>
</jesi:codeblock>
Next we want to render the top menu bar. For better performance, it is preferable to include this code
inside another codeblock tag; this time with the execute="template" attribute. This ensures that we
only execute this code when re-generating the skeleton and not whilst fetching each fragment. This is
because when we fetch a fragment, the generated topMenu bar (along with all the other skeleton
content) is simply ignored. So it’s an unnecessary burden to generate it to fetch a single targeted
fragment’s output.
<jesi:codeblock execute="template">
<%@ include file="topMenuBar.inc"%>
</jesi:codeblock>
<jesi:fragment expiration="6000">
<td width="159" valign="top"><%@ include
file="include/left_menu.inc"%></td>
</jesi:fragment>
We then make use of the aclGroup caching mechanism in the following snippet:
We didn’t use a per-group based caching since, although the page title is identical for everyone, not
everyone has the same editing rights on it.
The next snippet of interest is the one displaying the current time:
<jesi:fragment expiration="0">
The current time is <%=(new Date())%>
</jesi:fragment>
We want to make sure this is never cached so each page refresh will cause the display of the correct
time.
The more interesting fragment declaration is the one displaying the news entries:
<jesi:codeblock execute="always">
<%
String organization =
org.apache.commons.lang.StringUtils.trimToEmpty(user.getProperty("organization"));
%>
</jesi:codeblock>
We could’ve used a aclGroup="yes" in the fragment declaration to share this fragment across all
users with the same ACL group permissions. However, notice that each displayed news entry is
preceded with the user’s current organization name. The name of the organization is retrieved from
the current user’s properties. So we use instead aclGroup="<%=organization%>" to share this
© Jahia Ltd, all rights reserved.
Page 18 of 37
JESI QuickStart
fragment across all users with the same ACL group permissions and with the same organization
name in their profile.
Note that the organization String declaration is always executed using a codeblock tag because it is
both used by the skeleton to generate the caching attributes and by the fragment itself.
Finally, the footer is shared between all users using a static group:
In order to make the invalidation of fragments and templates on the remote ESI cache server, Jahia
needs to know what content is displayed where and if it has been updated. Once Jahia knows, say, a
Container A has been added to a ContainerList B, it will look for all pages displaying ContainerList
B and invalidate those on the ESI server. Otherwise these pages will display the outdated version of
ContainerList B. The fragments (or templates) referencing, calling or displaying ContainerList B will
be invalidated via a SOAP xml invalidation message from Jahia to the remote ESI server.
To detect which content objects (e.g. Pages, Fields, Containers or ContainerLists) your JSPs
references, calls or displays, Jahia uses Aspect-oriented programming (AOP) to detect at execution-
time the method calls to content objects made from within the JSPs. Jahia is then able to infer and
store which content objects were called from and to which fragment or template they belong.
This is normally transparent to the template developer, except in the case when custom classes are
called from within your JSPs. If the code executed in these classes does not reference any Jahia API
calls, then this section does not apply. However, if your classes make use of Jahia content objects,
then they need to be declared to the AspectWerkz AOP mechanism. This will allow Jahia to track
which content objects your code made use of and to locate in which template or fragment it did so.
For example, say we have developed a Java class to retrieve the size of a ContainerList:
package org.reader.mycode;
import org.jahia.data.JahiaData;
import org.jahia.data.containers.JahiaContainerList;
import org.jahia.exceptions.JahiaException;
/**
* User: Reader
* Date: Jul 31, 2006
* Time: 11:09:39 AM
* Copyright (C) Jahia Inc.
*/
public class MyClass {
public MyClass () {
}
JahiaContainerList absoluteContainerList =
jData.containers().getAbsoluteContainerList(listName, pageId);
if (absoluteContainerList!=null)
return absoluteContainerList.getFullSize();
else
return 0;
}
}
<%--
My JSP
--%>
<%@ page import="java.lang.*"%>
<%@ page import="java.net.*"%>
<%@ page import="javax.servlet.*"%>
<%@ page import="org.reader.mycode.*"%>
<%@ taglib uri="ajaxLib" prefix="ajax" %>
<%@ taglib uri="jahiaHtmlLib" prefix="jahiaHtml" %>
<%@ taglib uri="contentLib" prefix="content" %>
<%@ taglib uri="JahiaLib" prefix="jahia" %>
<%@ taglib uri="contentLib" prefix="content" %>
<%@ taglib uri="/WEB-INF/tld/jesi-tags" prefix="jesi" %>
<jesi:template>
<%
JahiaData jData = (JahiaData) request.getAttribute(
"org.jahia.data.JahiaData" );
ParamBean jParams = jData.params();
int listSize = MyClass.getContainerListSize(10);
%>
<html>
<body>
<jesi:fragment>
</body>
</html>
</jesi:template >
If AOP doesn’t know about MyClass, then if ContainerList id=10 is modified, the corresponding
page will not be invalidated.
<include package="org.reader.mycode"/>
OR within(org.reader.mycode.MyClass+.*(..))
…
…
This means that AOP capturing of content object Jahia API calls will occur whilst executing the any
MyClass methods, including MyClass subclasses. The MyClass+ means all subclasses of MyClass
and *(..) means any method with any number of parameters. You can narrow the scope of course.
For more details on the syntax of aop.xml, please refer to:
http://aspectwerkz.codehaus.org/xml_definition.html.
By default, AOP is configured by default to run on the Apache Tomcat platform. If you intend to use
Jahia on another platform such as WebSphere or WebLogic, you need to make a few minor changes
to the \YOUR_SERVER\webapps\jahia\WEB-INF\aol.xml file.
<include package="org.apache.jsp.jsp.jahia.templates"/>
within(org.apache.jsp.jsp.jahia.templates..*)
with :
within(org.apache.jasper.runtime.HttpJspBase+)
which has the same effect since all compiled JSP classes are derived from the same HttpJspBase.
Again, refer to http://aspectwerkz.codehaus.org/xml_definition.html for more syntax details.
A different servlet container, such as WebSphere, will probably have a different template package
name or JSP base class. This can be done by inspecting the servlet code generated from the
compilation of JSP files (to be found in the temporary directory where your servlet container
compiles JSP files). You’ll want to pay special attention to either the package statement or the
extends/implements statements.
When you successfully configure your platform to work with AOP, feel
free to contribute your updated AOP file to Jahia so that we can pre-
package pre-configured aop.xml files for various platforms.
In parallel to automatic invalidations carried out by Jahia when content is modified, template
developers can initiate invalidations manually to force the remote ESI server(s) to clear specific
cache entries for skeletons (i.e. templates) and/or fragments. This is useful if the changes are
generated by your JSP code logic, and not by Jahia content changes such as in forms.
The following code will invalidate the current page and all its fragments in all modes (e.g. Live,
Preview and Edit modes), in all languages and over all users:
All fragments of any type (e.g group, aclGroup or user) are invalidated, including any absolute
fragments referenced by this page.
A more selective method allows one to include or exclude non-absolute and absolute fragments:
Note that if absolute fragments are included then other pages will have to be re-aggregated on the ESI
server if they also reference these absolute fragments.
Sometimes, you’ll want to invalidate only a specific fragment on a page. This is done using the
following method:
You’ll need to retrieve the fragment id, which is the position number it appears on the page, from the
current PageContext. The PageContext object is updated at the very end of the fragment execution,
so it must be executed just after the final </jesi:fragment> closing tag. If this is not the case in your
code, you might be retrieving the fragment id of the previous fragment.
The fragment will then be synchronously removed from the ESI cache, which means that once the
SOAPInvalidateFrags method is called, the fragment is not in the ESI cache anymore. Note that
your invalidation will only take effect for the next request to the page. So you cannot invalidate a
fragment and regenerate it, all in the same request. Only on the next request will the regenerated
fragment be displayed.
There is little point in executing this method inside the actual fragment which you want to invalidate,
since if the fragment is being executed then its updated version is being sent to the ESI server.
Usually, you’ll want to call the SOAPInvalidateFrags method from within the enclosing template or
from within another fragment. So if a template or a fragment is being regenerated, you can force the
invalidation of another fragment within the page or the template itself on the current or other pages. It
is used for example in the corporate_templates_v2 in \common\top.inc for the ESI flush button.
<jesi:fragment aclGroup="true">
Lots of fragment content goes here...
</jesi:fragment>
<%
Integer fragmentID = (Integer) pageContext.getAttribute
("org.jahia.taglibs.esi.fragmentID");
ServicesRegistry.getInstance().getEsiSOAPInvalidatorService().SOAPInvalidateFrags
(jParams, jParams.getPageID(), fragKeys);
%>
fragmentID=<%=fragmentID%>
The invalidation method will only be called when the template is being regenerated. To limit or
expand the times when your invalidation code is executed, use the jesi:codeblock tag. For example,
the following code snippet will be executed only when a fragment is being generated:
<jesi:codeblock execute="fragment">
<%
Integer fragmentID = (Integer) pageContext.getAttribute
("org.jahia.taglibs.esi.fragmentID");
ServicesRegistry.getInstance().getEsiSOAPInvalidatorService().SOAPInvalidateFrags
(jParams, jParams.getPageID(),fragmentID.intValue());
%>
</jesi:codeblock>
To invalidate a specific absolute fragment, you need to retrieve its unique identifying key first, as
such:
<%
String absoluteFragmentKey = (String) pageContext.getAttribute
("org.jahia.taglibs.esi.absoluteFragmentKey");
ServicesRegistry.getInstance().getEsiSOAPInvalidatorService().SOAPInvalidateAbsol
uteFrags(jParams, absoluteFragmentKey);
%>
absoluteFragmentKey=<%=absoluteFragmentKey%>
Again, make sure the getAttribute method is called just after the </jesi:fragment> closing tag to be
sure to get the key for the targeted fragment.
To a certain extend, you can invalidate all the templates/fragments generated by a given user. By
generated, it is meant fragments that were generated in response to a request from a given user. So a
shared fragment X (e.g. group, aclGroup or absolute) might be displayed on user A’s page, but it
might’ve been generated by user B. So if we invalidate all entries for user A, fragment X will not be
invalidated even if it is displayed by user A.
Therefore this method is effective for invalidations of user-specific fragments and for non-
aclGrouped templates. If the template are non-aclGrouped (meaning that they are generated for each
user) then all these templates will be invalidated with this command. All aclGroup=true templates
used by a user will not necessarily be invalidated, except if the current user has generated them.
It is also possible to empty the entire ESI cache with a single command:
The emptyOnlyHtml parameter controls the invalidation of only Jahia pages and fragments, or of
everything including cached Javascript, image, css and other non-Jahia-page objects.
• Combo method
The following method allows the invalidation of multiple pages (and all their fragments) as well as
all the entries generated by a set of users, all in one go.
SOAPInvalidatePagesAndAllFragsAndAllUserEntries(ProcessingContext pc,
Set pids,
Set users,
boolean includeAbsoluteFrags,
boolean includeNonAbsoluteFrags)
Once a content object is changed, Jahia will automatically invalidate all the templates/fragments
which display it. If however, you want to force this process from your JSP templates, you need to fire
an event that contains a reference to this content object and Jahia will do the rest.
JahiaEvent objectUpdatedEvent =
new JahiaEvent(this, Jahia.getThreadParamBean(), myContentObject);
ServicesRegistry.getInstance ().getJahiaEventService ()
.fireContentObjectUpdated(objectUpdatedEvent);
The invalidation will be executed on the next request made to the Jahia server.
It will also invalidate content in the Preview, Compare and Edit mode.
To invalidate in the Live mode also, the following command is used instead:
(new EsiInvalidationEventListener())
.invalidateContentObjectInAllModes(Jahia.getThreadParamBean() , myContentObject);
Another way to manually clear the ESI cache and without any coding required, is to use Jahia’s
Administration Center or the ESI server’s Administration Center. Note that if you modify one or
more files in your template directory, you do not need to initiate a manual ESI cache flush. This is
done automatically by Jahia when it detects any file changes in the template directory.
If you are using Portlets in your JSPs, here is what you need to know about using them with ESI
caching.
Usage:
Portlets need to be enclosed within a jesi:fragment tag. The main difference is that you’ll want to set
your expiration delay to ‘0’. Normal rules apply to the remaining the fragments on the page, as well
as the enclosing jesi:template. This means that the fragment containing the portlet will always be
regenerated, but the rest of the page can be cached.
Taking the simple.jsp as an example and assuming that the available boxes are all portlets, then
simply inserting a 0 expiration will do the trick:
If you have more than one portlet per page, then you can put them all in the same 0 expiration
fragment or in separate 0 expiration fragments. Since each portlet fragment is generated during the
same request as the other portlet fragments, information sharing between portlets is therefore
supported.
Portlet fragments can still be cached by ESI, using a non-zero expiration value, but with certain
limitations.
The cache key on the ESI server for fragments is based on the request URL for the page (please read
Key Generation Overview section first). In the case of a page containing a portlet, URLs can be of the
form :
http://localhost:8081/jahia/Jahia/site/mySite/pid/3/jesi_appid/227_2/_ns/YWphaGlhX3BvcnRsZXRf
dGVzdDo6dGVzdHBvcnRsZXQ6Oi01OGFhOTdkZDoxMGY0ZGRjYjM1NjotN2ZmZnxm
where the part after /jesi_appid/ can be different for each request. So we abstract away this variable
portlet part in our cache key to
http://localhost:8081/jahia/Jahia/site/mySite/pid/3/jesi_appid/
This entails that there will only be one cache entry no matter what the variable portlet URL part is.
The consequence is that means that the latest version of the portlet will be served, no matter what the
current variable portlet URL part is. For some portlets, this is not a problem. For others, it might
mean that if a user clicks inside the portlet but still gets the cached version (at least until it expired or
is manually invalidated by yourself).
If there are multiple portlet fragments on a page and some of them are cached, be sure that there is no
information sharing between them. This is because portlets will be generated on an individual basis
per request, and not all in the same request. If your portlets are independent, then this isn’t an issue. If
they are, this might lead to
unexpected behaviours.
User, group and aclGroup based fragment sharing also applies to portlet fragments. So each user can
have his own version of the portlet, or each user in the same group, or with the same rights. This does
not absolve you from following the restrictions due to the truncated portlet URL in the cache key.
In practice, template sharing is limited since a group shared skeleton cannot contain fragments
individual to each user. All fragments in a shared skeleton are the same. Let us look at an example:
The template is shared between all users using the static group identifier “salesperson”. However,
Fragment 1 uses a user property to define sharing. The first time this template is executed the result
will be correct for the requesting user, in that Fragment 1 will use his/her preferred background
colour defined in his/her profile. The skeleton is then stored in the ESI webcache.
However, all subsequent users requesting this page will also get this cached skeleton. The problem is
that the include link for Fragment 1 is hardcoded. It points to the fragment group defined by the first
user’s background colour preference. As a result all users, no matter what preferred background
colour is declared in their profile, will get the same instance of Fragment 1.
The same goes for Fragment 2. Here the default jesi:fragment behaviour –per user caching– is
invoked since no attribute is define. All users will see the Fragment 2 initiated by the first user
requesting this fragment.
Only Fragment 3 is correctly defined here. It is shared on a static basis via the “salesProjections”
group. There is no danger of its content being shared incorrectly.
Fragment 4 is incorrect since the rights of the first user to generate this group template will be used to
generate the include link to Fragment 4. Another user, with different rights, should point to another
aclGroup fragment. However this will not be the case here. Therefore aclGroup fragments should not
be used with group sharing.
For these reasons, it is preferable to minimize group template (i.e. skeleton) sharing whilst
maximizing fragment sharing. By minimizing the content stored in each template and using
jesi:codeblock tags, the workload necessary to generate skeletons on a per-user basis (instead of on a
per-group basis) can be kept to a minimum.
aclGroup template sharing is more flexible than group sharing since it always for aclGroup and per-
user caching:
Fragment 1 is still incorrect since different users might have a different group value, even if they
have the same rights.
Fragment 2 is now correct. It will only be displayed by the same user. This is because, during a
request, the current username is dynamically swapped in the include link to the user fragment of the
aclGroup shared template skeleton.
Fragment 4 is correct. It will only be displayed by a user with the same rights. This is because, during
a request, the current user’s rights are dynamically swapped in the include link to the aclGroup
fragment of the aclGroup shared template skeleton.
Fragment 5 is correct.
Fragment 6 is incorrect since the prefix aclGroup key might be different for different users (as in
Fragment 1).
<jesi:fragment cache="yes">
…
<jesi:fragment cache="yes">
…
</jesi:fragment>
…
</jesi:fragment>
• We intend to add fine grained control of the behaviours to adopt for any errors that
may occur during fragment generation. For example, choose between displaying a
default error message in place of an inaccessible fragment, using the stale yet already
cached version of a fragment or displaying custom fallback content.
• We intend to add the jesi:include tag to be used in replacement of the jsp:include tag.
This will allow absolute fragments to be shared between pages and not just users of
the same page.
In order to get a better grasp of template and fragment sharing, a schematic overview of the key
generation process is given here. These keys are used as entry points into hashmaps on the ESI server
to store fragments and templates.
The following simplified example is for illustrative purposes, the exact inner workings of Jahia’s ESI
might be slightly different (see source code for details).
• Template/Skeleton
<jesi:template>
Header
<jesi:fragment >
Frag1 content
</jesi:fragment>
Footer
</jesi:template>
For a request to http://myEsiServer:8081/jahia/Jahia/pid/1 by user John and if the page isn’t yet
cached, the ESI server must first fetch the skeleton (i.e. esi template) for the page to determine which
fragments are required. Jahia will roughly generate the following html for this skeleton request from
the ESI server:
<html>
Header
<esi:fragment src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&user=John">
Footer
</html>
method=GET_user=John_Url=http://myEsiServer:8081/jahia/Jahia/pid/1
where method is the HTTP method utilized to fetch the page, user is the JahiaUser who requested the
page and Url is the request URL.
Notice that the skeleton is stored on a per-user basis, this is because the <jesi:template> tag by default
caches skeletons (i.e. esi templates) on a per user basis. See later for an example of aclGroup
templates.
• user Fragment
The ESI server then parses the HTML, detects any fragment links and, in this case, will fetch
fragment 1. __esi_fragment=1&user=John identifies fragment one which is specific to user John.
Jahia returns the content of fragment 1 to the ESI server for the request
http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&user=John, in this case the string
“Frag1 content”. The hashmap key for this fragment is then:
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&user=
John
• group Fragment
<jesi:fragment group="salesProjections">
Frag1 content
</jesi:fragment>
Its resulting include link in the skeleton would look like this :
<esi:fragment src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&group=salesProjections">
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&group
=salesProjections
• aclGroup Fragment
<jesi:fragment aclGroup="true">
Frag1 content
</jesi:fragment>
If John was member of group1 and group2, it’s resulting include link in the skeleton would look like
this :
<esi:fragment
src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGroup=group1_group2">
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGr
oup=group1_group2
<jesi:fragment aclGroup="myMarker">
Frag1 content
</jesi:fragment>
Its resulting include link in the skeleton would look like this :
<esi:fragment
src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGroup=myMarker_group1_group2">
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGr
oup=myMarker_group1_group2
Finally, expanding on the example above, if user John had specific rights over any content displayed
inside fragment 1, then the system would’ve automatically reverted to a non-shared user-specific
version of the fragment, such that the resulting include link in the skeleton would look like this :
<esi:fragment
src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGroup=myMarker_John">
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGr
oup=myMarker_John
• absolute Fragment
It’s resulting include link in the skeleton would look like this :
<esi:fragment
src="http://myJahiaServer:8080/jahia/Jahia/pid/1?__esi_fragment=1&aclGroup=myMarker_group1_group2">
method=GET_Url=http://myJahiaServer:8080/jahia/Jahia/pid/ABSOLUTE_MARKER?__esi_fr
agment=ABSOLUTE_MARKER&aclGroup=myMarker_group1_group2
Notice how the pid and frag number are removed. This is because if this fragment is shared across
multiple pages, we want its hashmap key to be independent of these.
• aclGroup Template/Skeleton
For the user John, the skeleton is stored in the ESI server’s cache at hashmap key:
method=GET_aclGroup=group1_group2_Url=http://myEsiServer:8081/jahia/Jahia/pid/1
This means that it can be shared by any user with the same rights. The resulting include links to
fragments are consequently more complex, but the overall mechanism remains the same.
© Jahia Ltd, all rights reserved.
Page 34 of 37
JESI QuickStart
For reference, here is the current listing of the JESI TLD declaration file available in
\tomcat\webapps\jahia\WEB-INF\tld\jesi-tags.tld :
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>jesi</short-name>
<tag>
<name>fragment</name>
<tag-class>org.jahia.taglibs.esi.JesiFragmentTag</tag-class>
<body-content>JSP</body-content>
<description>Creates a separate cacheable object from a page fragment.</description>
<attribute>
<name>control</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>cache</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>expiration</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>maxRemovalDelay</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>user</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>group</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<!-- generates one fragment for all users in same groups, so anything between this
fragment cannot reference
user properties or any external databases as they will be ignored -->
<name>aclGroup</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<name>absolute</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<name>alwaysInvalidate</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>codeblock</name>
<tag-class>org.jahia.taglibs.esi.JesiCodeblockTag</tag-class>
<body-content>JSP</body-content>
<description>Allows conditional execution of its body in template or fragment
request</description>
<attribute>
<name>execute</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>template</name>
<tag-class>org.jahia.taglibs.esi.JesiTemplateTag</tag-class>
<body-content>JSP</body-content>
<description>Controls ESI caching characteristics of non-fragment content of the
page.</description>
<attribute>
<name>control</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>cache</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>expiration</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>maxRemovalDelay</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>user</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>group</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>control</name>
<tag-class>org.jahia.taglibs.esi.JesiControlTag</tag-class>
<body-content>EMPTY</body-content>
<description>Controls ESI caching characteristics of the page.</description>
<attribute>
<name>control</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>cache</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>expiration</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>maxRemovalDelay</name>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
jesi-tags.tld