package com.arclight.zoomf.baseline; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import javax.servlet.http.HttpSession; import org.apache.wicket.IClusterable; import org.apache.wicket.Page; import org.apache.wicket.protocol.http.WebRequest; import org.apache.wicket.protocol.http.WebRequestCycle; import org.apache.wicket.protocol.http.pagestore.AbstractPageStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of AbstractPageStore, similar to DiskPageStore but saves to httpsession for terracotta session replication. * * Differences from DiskPageStore: * -> Does not need sync and async methods as is to memory * -> Does not need to store session id as pagemap is in session so can only get this users session. * * @author richard wilkinson (richardjohnwilkinson@gmail.com) * */ public class TerracottaPageStore extends AbstractPageStore { public static final Logger logger = LoggerFactory.getLogger(TerracottaPageStore.class); public static final String TERRACOTTA_SESSION_KEY = "TERRACOTTA_SESSION_KEY"; private static int MAX_PAGE_VERSIONS; private static int MAX_PAGES; /** * Stores all versions of a specific page */ protected static class PageEntries implements IClusterable { private LinkedHashSet pages = new LinkedHashSet(); public boolean containsPage(int pageVersion) { for(SerializedPage sPage : pages) { if(sPage.getVersionNumber() == pageVersion) { return true; } } return false; } public SerializedPage getPage(int versionNumber, int ajaxVersionNumber) { SerializedPage page = null; synchronized (pages) { logger.debug("getPage versionNumber: " + versionNumber + " ajax: " + ajaxVersionNumber); if (versionNumber != -1 && ajaxVersionNumber != -1) { // just find the exact page version for(SerializedPage sPage : pages) { if(sPage.getVersionNumber() == versionNumber && sPage.getAjaxVersionNumber() == ajaxVersionNumber) { page = sPage; break; } } } else if (versionNumber == -1) { // we need to find last recently stored page window - that is page // at the end of the list Iterator iter = pages.iterator(); while(iter.hasNext()) { page = iter.next(); } } else if (ajaxVersionNumber == -1) { int lastAjaxVersion = -1; // we need to find index with highest ajax version for(SerializedPage sPage : pages) { if(sPage.getVersionNumber() == versionNumber && sPage.getAjaxVersionNumber() > lastAjaxVersion) { lastAjaxVersion = sPage.getAjaxVersionNumber(); page = sPage; } } } if(page == null) { logger.debug("Page cannot be found"); } } return page; } public void storePage(SerializedPage page) { logger.debug("Storing page: " + page.toString()); synchronized (pages) { logger.debug("Removing page: " + page.toString()); pages.remove(page); if(pages.size() == MAX_PAGE_VERSIONS) { logger.debug("Removing oldest page"); Iterator iter = pages.iterator(); iter.next(); iter.remove(); } if (page.getData() != null) { pages.add(page); } } } public CharSequence verboseStatusDump() { StringBuilder output = new StringBuilder(); Iterator iter = pages.iterator(); while(iter.hasNext()) { SerializedPage entry = iter.next(); output.append("\t\t").append("version: ").append(entry.getVersionNumber()).append("\t ajax: ") .append(entry.getAjaxVersionNumber()).append("\n"); } return output; } } /** * Stores all pages in a particular page map */ protected static class PageMapEntry implements IClusterable { private LinkedHashMap pages = new LinkedHashMap(5, 0.75f, true); public boolean containsPage(int pageId) { return pages.containsKey(pageId); } public boolean containsPage(int pageId, int pageVersion) { boolean contains = false; if(containsPage(pageId)) { contains = ((PageEntries) pages.get(pageId)).containsPage(pageVersion); } return contains; } public SerializedPage getPage(int pageId, int versionNumber, int ajaxVersionNumber) { SerializedPage page = null; synchronized (pages) { if(containsPage(pageId)) { page = ((PageEntries) pages.get(pageId)).getPage(versionNumber, ajaxVersionNumber); } } return page; } public void remove(int pageId) { synchronized (pages) { pages.remove(pageId); } } public void storePage(SerializedPage page) { synchronized (pages) { if(pages.size() == MAX_PAGES) { //remove oldest Iterator iter = pages.entrySet().iterator(); iter.next(); iter.remove(); } if(pages.containsKey(page.getPageId())) { ((PageEntries) pages.get(page.getPageId())).storePage(page); } else { PageEntries pageEntries = new PageEntries(); pageEntries.storePage(page); pages.put(page.getPageId(), pageEntries); } } } public CharSequence verboseStatusDump() { StringBuilder output = new StringBuilder(); Iterator> iter = pages.entrySet().iterator(); while(iter.hasNext()) { Map.Entry entry = iter.next(); output.append("\t").append("Page id: ").append(entry.getKey()).append("\n"); output.append(entry.getValue().verboseStatusDump()); } return output; } } /** * Stores all page maps (and so all pages) */ protected static class PageStore implements IClusterable { private Map pageStore = new HashMap(); public boolean containsPageMap(String pageMapName) { return pageStore.containsKey(pageMapName); } public void destroy() { pageStore.clear(); } public PageMapEntry getPageMapEntry(String pageMapName, boolean create) { PageMapEntry pme = null; if(create) { if(!containsPageMap(pageMapName)) { pme = new PageMapEntry(); pageStore.put(pageMapName, pme); } else { pme = pageStore.get(pageMapName); } } else { pme = pageStore.get(pageMapName); } return pme; } public int numPageMaps() { return pageStore.size(); } public CharSequence verboseStatusDump() { StringBuilder output = new StringBuilder(); Iterator> iter = pageStore.entrySet().iterator(); while(iter.hasNext()) { Map.Entry entry = iter.next(); output.append("\tPage map name: ").append(entry.getKey()).append("\n"); output.append(entry.getValue().verboseStatusDump()); } return output; } } /** * Make a new page store, specifying max max pages and max page version * @param maxPages - number of pages to keep in each page map * @param maxPageVersions - number of versions of each page to store */ public TerracottaPageStore(int maxPages, int maxPageVersions) { logger.info("Set up Terracotta page store, maxPageVersions = " + maxPageVersions + " maxPages = " + maxPages); MAX_PAGE_VERSIONS = maxPageVersions; MAX_PAGES = maxPages; } public boolean containsPage(String sessionId, String pageMapName, int pageId, int pageVersion) { if(getPageStore().containsPageMap(pageMapName)) return getPageStore().getPageMapEntry(pageMapName, false).containsPage(pageId,pageVersion); return false; } public void destroy() { getPageStore().destroy(); } public Page getPage(String sessionId, String pagemap, int pageId, int versionNumber, int ajaxVersionNumber) { logger.info("GetPage: " + sessionId + ", " + pagemap + ", " + pageId + ", " + versionNumber + ", " + ajaxVersionNumber); Page page = null; PageMapEntry pageMap = getPageStore().getPageMapEntry(pagemap, false); if(pageMap != null) { SerializedPage sPage = pageMap.getPage(pageId, versionNumber, ajaxVersionNumber); if(sPage != null) { page = deserializePage(sPage.getData(), versionNumber); } } return page; } public void pageAccessed(String sessionId, Page page) { //Do nothing } public void removePage(String sessionId, String pagemap, int id) { if(getPageStore().containsPageMap(pagemap)) { logger.debug("Removing page with Id: " + id); getPageStore().getPageMapEntry(pagemap, false).remove(id); } } @SuppressWarnings("unchecked") public void storePage(String sessionId, Page page) { List pages = serializePage(page); logger.debug("Storing page: " + page.toString()); for(SerializedPage aPage : pages) { getPageStore().getPageMapEntry(aPage.getPageMapName(), true).storePage(aPage); } if(logger.isDebugEnabled()) { verboseStatusDump(); } } public void unbind(String sessionId) { getPageStore().destroy(); } private void verboseStatusDump() { StringBuilder output = new StringBuilder(); output.append("User session: ").append(((WebRequest)(WebRequestCycle .get() .getRequest())) .getHttpServletRequest() .getSession().getId()).append("\n"); output.append("Page store contains ").append(getPageStore().numPageMaps()).append(" page map(s)\n"); output.append(getPageStore().verboseStatusDump()).append("\n"); logger.debug(output.toString()); } private PageStore getPageStore() { HttpSession session = ((WebRequest)(WebRequestCycle .get() .getRequest())) .getHttpServletRequest() .getSession(); if(session.getAttribute(TERRACOTTA_SESSION_KEY) == null) { session.setAttribute(TERRACOTTA_SESSION_KEY, new PageStore()); } return (PageStore) session.getAttribute(TERRACOTTA_SESSION_KEY); } }