This blog is mainly about Java...

Monday, March 30, 2009

Adding html cache on a JSF 1.2 / Seam 2.1.1 application

Adding cache in seam is very easy, and the 2.1.1 release has even made using caching easier.

It is useful to cache html typically when you have lots of hits to the database, otherwise you shouldn't use html cache.

I recently needed to read the current url, extract a part of the url, and check the database for a hit on that pattern. This was done on every request and is something that suits caching. The data I was retrieving was something that was changed very rarely thus it could also be cached infinetly. For this purpose, I have chosen the in-built treecache that ships with JBoss and Seam.

Getting the url from a JSF page can be done like this:
       
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
String uri = request.getRequestURI();
// zero so that trailing empty strings will be discarded
String[] values = uri.split("/", 0);
//Concatinate the string and create a query based on the concatinated string
Now in the xhtml page you will have to write:
<s:cache region="instructionPageFragments" key="instruction-#{instructionHandler.instructionUrl}">
#{instructionHandler.getInstruction()}
</s:cache>
Note that the key must be something unique. In this example the key value will get the current url as a key, and in our web application this is unique, thus works excellent as a key. The s:cache will use the key to retrieve the value from its HashMap.
s:cache uses pojoCache internally, but we want to configure it to use treecache and that is done in components.xml.
You do this by defining the namespace
xmlns:cache="http://jboss.com/products/seam/cache"
and then
<cache:jboss-cache-provider configuration="META-INF/treecache.xml">
Furthermore, we have defined a region on the s:cache called instructionPageFragments. This can be configured in treecache.xml where we can define timeToLive maxAgeSeconds and so on.

<region name="/instructionPageFragments" policyClass="org.jboss.cache.eviction.LRUPolicy">
<attribute name="maxNodes">0</attribute>
<attribute name="timeToLiveSeconds">0</attribute>
<attribute name="maxAgeSeconds">0</attribute>
</region>

A copy of our entire treecache.xml is here:
<?xml version="1.0" encoding="UTF-8"?>

<!-- ===================================================================== -->
<!-- -->
<!-- Sample TreeCache Service Configuration -->
<!-- -->
<!-- ===================================================================== -->

<server>

<classpath codebase="../lib" archives="jboss-cache.jar, jgroups.jar"/>


<!-- ==================================================================== -->
<!-- Defines TreeCache configuration -->
<!-- ==================================================================== -->

<mbean code="org.jboss.cache.TreeCache"
name="jboss.cache:service=TreeCache">

<depends>jboss:service=Naming</depends>
<depends>jboss:service=TransactionManager</depends>

<!--
Configure the TransactionManager
-->
<attribute name="TransactionManagerLookupClass">org.jboss.cache.JBossTransactionManagerLookup</attribute>

<!--
Isolation level : SERIALIZABLE
REPEATABLE_READ (default)
READ_COMMITTED
READ_UNCOMMITTED
NONE
-->
<attribute name="IsolationLevel">REPEATABLE_READ</attribute>

<!--
Valid modes are LOCAL
REPL_ASYNC
REPL_SYNC
INVALIDATION_ASYNC
INVALIDATION_SYNC
-->
<attribute name="CacheMode">LOCAL</attribute>

<!--
Just used for async repl: use a replication queue
-->
<attribute name="UseReplQueue">false</attribute>

<!--
Replication interval for replication queue (in ms)
-->
<attribute name="ReplQueueInterval">0</attribute>

<!--
Max number of elements which trigger replication
-->
<attribute name="ReplQueueMaxElements">0</attribute>

<!-- Name of cluster. Needs to be the same for all clusters, in order
to find each other
-->
<attribute name="ClusterName">TreeCache-Cluster</attribute>

<!-- JGroups protocol stack properties. Can also be a URL,
e.g. file:/home/bela/default.xml
<attribute name="ClusterProperties"></attribute>
-->

<attribute name="ClusterConfig">
<config>
<!-- UDP: if you have a multihomed machine,
set the bind_addr attribute to the appropriate NIC IP address -->
<!-- UDP: On Windows machines, because of the media sense feature
being broken with multicast (even after disabling media sense)
set the loopback attribute to true -->
<UDP mcast_addr="228.1.2.3" mcast_port="48866"
ip_ttl="64" ip_mcast="true"
mcast_send_buf_size="150000" mcast_recv_buf_size="80000"
ucast_send_buf_size="150000" ucast_recv_buf_size="80000"
loopback="false"/>
<PING timeout="2000" num_initial_members="3"
up_thread="false" down_thread="false"/>
<MERGE2 min_interval="10000" max_interval="20000"/>
<!-- <FD shun="true" up_thread="true" down_thread="true" />-->
<FD_SOCK/>
<VERIFY_SUSPECT timeout="1500"
up_thread="false" down_thread="false"/>
<pbcast.NAKACK gc_lag="50" retransmit_timeout="600,1200,2400,4800"
max_xmit_size="8192" up_thread="false" down_thread="false"/>
<UNICAST timeout="600,1200,2400" window_size="100" min_threshold="10"
down_thread="false"/>
<pbcast.STABLE desired_avg_gossip="20000"
up_thread="false" down_thread="false"/>
<FRAG frag_size="8192"
down_thread="false" up_thread="false"/>
<pbcast.GMS join_timeout="5000" join_retry_timeout="2000"
shun="true" print_local_addr="true"/>
<pbcast.STATE_TRANSFER up_thread="true" down_thread="true"/>
</config>
</attribute>

<!--
Whether or not to fetch state on joining a cluster
NOTE this used to be called FetchStateOnStartup and has been renamed to be more descriptive.
-->
<attribute name="FetchInMemoryState">true</attribute>

<!--
The max amount of time (in milliseconds) we wait until the
initial state (ie. the contents of the cache) are retrieved from
existing members in a clustered environment
-->
<attribute name="InitialStateRetrievalTimeout">20000</attribute>

<!--
Number of milliseconds to wait until all responses for a
synchronous call have been received.
-->
<attribute name="SyncReplTimeout">20000</attribute>

<!-- Max number of milliseconds to wait for a lock acquisition -->
<attribute name="LockAcquisitionTimeout">15000</attribute>


<!-- Name of the eviction policy class. -->
<attribute name="EvictionPolicyClass"></attribute>

<!--
Indicate whether to use marshalling or not. Set this to true if you are running under a scoped
class loader, e.g., inside an application server. Default is "false".
-->
<attribute name="UseMarshalling">false</attribute>


<!-- Specific eviction policy configurations. -->
<attribute name="EvictionPolicyConfig">
<config>
<attribute name="wakeUpIntervalSeconds">120</attribute>

<!-- Cache wide default -->
<region name="/_default_"
policyClass="org.jboss.cache.eviction.LRUPolicy">
<!-- This is the maximum number of nodes allowed in this region. -->
<!-- 0 denotes no limit. -->
<attribute name="maxNodes">0</attribute>

<!-- The amount of time a node is not written to or read (in seconds) before the node is swept away.-->
<!-- 0 denotes no limit. -->
<attribute name="timeToLiveSeconds">0</attribute>

<!-- Lifespan of a node (in seconds) regardless of idle time before the node is swept away. -->
<!-- 0 denotes no limit. -->
<attribute name="maxAgeSeconds">0</attribute>
</region>

<!-- We dont have to define each region if we have default 0 values, but just incase we want to change it later -->

<!--
<region name="/instructionPageFragments" policyClass="org.jboss.cache.eviction.LRUPolicy">
<attribute name="maxNodes">0</attribute>
<attribute name="timeToLiveSeconds">0</attribute>
<attribute name="maxAgeSeconds">0</attribute>
</region>

<region name="/reviewInputPageFragments" policyClass="org.jboss.cache.eviction.LRUPolicy">
<attribute name="maxNodes">0</attribute>
<attribute name="timeToLiveSeconds">0</attribute>
<attribute name="maxAgeSeconds">0</attribute>
</region>

<region name="/actorPageFragments" policyClass="org.jboss.cache.eviction.LRUPolicy">
<attribute name="maxNodes">0</attribute>
<attribute name="timeToLiveSeconds">0</attribute>
<attribute name="maxAgeSeconds">0</attribute>
</region>
-->
</config>
</attribute>


</mbean>


<!-- Uncomment to get a graphical view of the TreeCache MBean above -->
<!-- <mbean code="org.jboss.cache.TreeCacheView" name="jboss.cache:service=TreeCacheView">-->
<!-- <depends>jboss.cache:service=TreeCache</depends>-->
<!-- <attribute name="CacheService">jboss.cache:service=TreeCache</attribute>-->
<!-- </mbean>-->


</server>

Since I want everything to be cached infinetly I have uncommented the regions out and put 0 (no limit) on the _default_ region

You can also programatically add and remove content to the cache.
@In 
private CacheProvider cacheProvider;

//Removing the cache with the region instructionPageFragments, and the key instruction-'current url'
cacheProvider.remove("instructionPageFragments", "instruction-" + url);
//Adding to the cache
cacheProvider.put("instructionPageFragments", "instruction-" + url, "This text will be cached!!");

Labels