The class below uses Infinispan.
I also wanted to code it using a TreeCache API, but eventually dropped that as I found the possibility to index & query the cache using Lucene.
Note that I wrote this just to have a look at the Infinispan API, and I did not test it.
If you test it, please let me know.
TODO: Remember the last version & ajaxVersion for each page, not to have to sort it in Lucene.
@Override
protected ISessionStore newSessionStore() {
return new SecondLevelCacheSessionStore(this, new JBossCachePageStore());
}
package cz.dw.test.jbosscache;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.wicket.Page;
import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore.IPageStore;
import org.apache.wicket.protocol.http.pagestore.AbstractPageStore;
import org.infinispan.Cache;
import org.infinispan.config.Configuration;
import org.infinispan.manager.CacheManager;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.QueryFactory;
import org.infinispan.query.QueryIterator;
import org.infinispan.query.backend.QueryHelper;
import org.infinispan.tree.TreeCache;
import org.infinispan.tree.TreeCacheFactory;
/**
* JBoss Cache / Infinispan implementation of IPageStore.
*
* @author Ondrej Zizka
*/
public class JBossCachePageStore extends AbstractPageStore implements IPageStore {
private CacheManager manager;
private Cache cache;
private QueryFactory qf;
private TreeCacheFactory tcFactory;
private TreeCache treeCache;
// -- Const -- //
public JBossCachePageStore(CacheManager manager, Cache defaultCache) throws IOException
{
Configuration cfg = new Configuration();
cfg.setIndexingEnabled(true);
this.manager = new DefaultCacheManager( cfg );
this.cache = manager.getCache();
// The QueryHelper must be instantiated before putting objects into the cache.
QueryHelper qh = new QueryHelper(this.cache, new Properties(), PageKey.class);
// When I want to query objects in the cache, I will create a QueryFactory.
this.qf = new QueryFactory(this.cache, qh);
// Tree
this.tcFactory = new TreeCacheFactory();
this.treeCache = this.tcFactory.createTreeCache(cache);
}
// Page key - takes only the metainfo from the given page.
private class PageKey extends SerializedPage
{
private String sessionId;
public PageKey(String sessionId, Page page) {
super(page);
this.sessionId = sessionId;
}
public PageKey(String sessionId, int pageId, String pageMapName, int versionNumber, int ajaxVer, byte[] data) {
super(pageId, pageMapName, versionNumber, ajaxVer, null);
this.sessionId = sessionId;
}
public PageKey(String sessionId, SerializedPage page) {
super(page.getPageId(), page.getPageMapName(), page.getVersionNumber(), page.getAjaxVersionNumber(), null);
this.sessionId = sessionId;
}
}
/**
* Destroy the store.
*/
@Override
public void destroy() {
this.cache.stop();
this.cache.clear();
this.cache = null;
this.manager = null;
}
/**
* Stores the page to a persistent layer. The page should be stored under the id and the version number.
*/
@Override
public void storePage( String sessionId, Page page ) {
PageKey key = new PageKey( sessionId, page );
List<SerializedPage> pages = this.serializePage(page);
for (SerializedPage serializedPage : pages) {
PageKey key2 = new PageKey( sessionId, serializedPage );
this.cache.put( key2, serializedPage );
}
}
/**
Restores a page version from the persistent layer.
Note that the versionNumber and ajaxVersionNumber parameters may be -1.
* If ajaxVersionNumber is -1 and versionNumber is specified,
the page store must return the page with highest ajax version.
* If both versionNumber and ajaxVersioNumber are -1, the pagestore
must return last touched (saved) page version with given id.
*/
@Override
public <T> Page getPage( String sessionId, String pagemap, int id, int versionNumber, int ajaxVer ) {
if( versionNumber != -1 && ajaxVer != -1 ){
PageKey pk = new PageKey(sessionId, id, pagemap, versionNumber, ajaxVer, null);
SerializedPage page = (SerializedPage) this.cache.get( pk );
return this.deserializePage(page.getData(), versionNumber);
}
CacheQuery cq = createQuery(sessionId, pagemap, id, versionNumber, ajaxVer, true);
QueryIterator lazyIterator = cq.lazyIterator(1);
SerializedPage serPage = (SerializedPage) lazyIterator.next();
Page page = this.deserializePage(serPage.getData(), serPage.getVersionNumber());
return page;
}
/**
* Create a query according to the given data.
* @returns a query object.
*/
private CacheQuery createQuery( String sessionId, String pagemap, int id,
int versionNumber, int ajaxVer, boolean doSort ){
// private final int pageId;
// private final String pageMapName;
// private final int versionNumber;
// private final int ajaxVersionNumber;
BooleanQuery query = new BooleanQuery();
query.add( new TermQuery( new Term("sessionId", sessionId) ) , Occur.MUST);
query.add( new TermQuery( new Term("pageId", ""+id) ) , Occur.MUST);
query.add( new TermQuery( new Term("pageMapName", pagemap) ) , Occur.MUST);
Sort sort = null;
SortField sortAjax = new SortField("ajaxVersionNumber", SortField.INT, true);
if( versionNumber != -1 ){
// Sort by ajax only, filter by version number.
query.add( new TermQuery( new Term("versionNumber", pagemap) ) , Occur.MUST);
if(doSort) sort = new Sort( sortAjax );
} else {
// Sort by both.
if(doSort) sort = new Sort( new SortField[]{ new SortField("versionNumber", SortField.INT, true), sortAjax } );
}
CacheQuery cq = qf.getQuery( query );
if( sort != null ) cq.setSort( sort );
return cq;
}
/**
* This method is called when the page is accessed.
* A IPageStore implementation can block until a save of that page version is done.
* So that a specific page version is always restore able.
*/
@Override
public void pageAccessed( String sessionId, Page page ) {
// We won't block.
}
@Override
public void removePage( String sessionId, String pagemap, int id ) {
CacheQuery cq = createQuery( sessionId, pagemap, id, -1, -1, false );
List serPages = cq.list();
for( Object serPageObj : serPages) {
this.cache.remove( (SerializedPage) serPageObj );
}
}
/**
* The pagestore should cleanup all the pages for that sessionid.
*/
@Override
public void unbind(String sessionId) {
try {
CacheQuery cq = qf.getBasicQuery("sessionId", sessionId);
List serPages = cq.list();
for (Object serPageObj : serPages) {
this.cache.remove((SerializedPage) serPageObj);
}
}
catch (ParseException ex) {
Logger.getLogger(JBossCachePageStore.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Returns whether the PageStore contains given page.
*/
@Override
public boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVer) {
CacheQuery cq = createQuery( sessionId, pageMapName, pageId, pageVer, -1, false);
return cq.getResultSize() > 0;
}
}// class
<repositories>
<repository><id>jboss</id><url>https://repository.jboss.org/nexus/content/groups/public</url></repository>
</repositories>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
<version>4.0.0.FINAL</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-query</artifactId>
<version>4.0.0.FINAL</version>
</dependency>
For possible Infinispan cache store implementations, see the JBoss Maven repo
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-cachestore-jdbm</artifactId> <version>4.0.0.FINAL</version> </dependency>
Not used (yet?):
<dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-tree</artifactId> <version>4.0.0.FINAL</version> </dependency>