Index: components/page-manager/src/test/org/apache/jetspeed/page/TestDatabasePageManager.java =================================================================== --- components/page-manager/src/test/org/apache/jetspeed/page/TestDatabasePageManager.java (revision 801357) +++ components/page-manager/src/test/org/apache/jetspeed/page/TestDatabasePageManager.java (working copy) @@ -152,6 +152,13 @@ { removedNodeCount++; } + + /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManagerEventListener#reapNodes(long) + */ + public void reapNodes(long interval) + { + } public void testCreates() throws Exception { Index: components/page-manager/src/java/org/apache/jetspeed/page/impl/DatabasePageManager.java =================================================================== --- components/page-manager/src/java/org/apache/jetspeed/page/impl/DatabasePageManager.java (revision 801357) +++ components/page-manager/src/java/org/apache/jetspeed/page/impl/DatabasePageManager.java (working copy) @@ -187,6 +187,14 @@ } /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManager#getNodeReapingInterval() + */ + public long getNodeReapingInterval() + { + return delegator.getNodeReapingInterval(); + } + + /* (non-Javadoc) * @see org.apache.jetspeed.page.PageManager#newPage(java.lang.String) */ public Page newPage(String path) Index: components/page-manager/src/java/org/apache/jetspeed/page/AbstractPageManager.java =================================================================== --- components/page-manager/src/java/org/apache/jetspeed/page/AbstractPageManager.java (revision 801357) +++ components/page-manager/src/java/org/apache/jetspeed/page/AbstractPageManager.java (working copy) @@ -57,10 +57,13 @@ { private final static Log log = LogFactory.getLog(AbstractPageManager.class); + private final static long DEFAULT_NODE_REAPING_INTERVAL = 300000; + private final static String FOLDER_NODE_TYPE = "folder"; private final static String PAGE_NODE_TYPE = "page"; private final static String FRAGMENT_NODE_TYPE = "fragment"; private final static String LINK_NODE_TYPE = "link"; + protected Class fragmentClass; protected Class pageClass; protected Class folderClass; @@ -91,11 +94,38 @@ private boolean constraintsEnabled; private List listeners = new LinkedList(); + + private long nodeReapingInterval = DEFAULT_NODE_REAPING_INTERVAL; public AbstractPageManager(boolean permissionsEnabled, boolean constraintsEnabled) - { + { this.permissionsEnabled = permissionsEnabled; this.constraintsEnabled = constraintsEnabled; + // start node reaping deamon thread + if (this.nodeReapingInterval > 0) + { + Thread nodeReapingThread = new Thread(new Runnable() + { + public void run() + { + while (true) + { + try + { + // wait for reap interval and invoke reaping + // notification on page manager event listeners + Thread.sleep(nodeReapingInterval); + notifyReapNodes(); + } + catch (InterruptedException ie) + { + } + } + } + }, "PageManagerNodeReapingThread"); + nodeReapingThread.setDaemon(true); + nodeReapingThread.start(); + } } public AbstractPageManager(boolean permissionsEnabled, boolean constraintsEnabled, Map modelClasses) @@ -127,26 +157,16 @@ this.fragmentPreferenceClass = (Class)modelClasses.get("FragmentPreferenceImpl"); } - /** - *
- * getPermissionsEnabled - *
- * + /* (non-Javadoc) * @see org.apache.jetspeed.page.PageManager#getPermissionsEnabled() - * @return */ public boolean getPermissionsEnabled() { return permissionsEnabled; } - /** - *- * getConstraintsEnabled - *
- * + /* (non-Javadoc) * @see org.apache.jetspeed.page.PageManager#getConstraintsEnabled() - * @return */ public boolean getConstraintsEnabled() { @@ -154,6 +174,14 @@ } /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManager#getNodeReapingInterval() + */ + public long getNodeReapingInterval() + { + return nodeReapingInterval; + } + + /* (non-Javadoc) * @see org.apache.jetspeed.page.PageManager#newPage(java.lang.String) */ public Page newPage(String path) @@ -792,6 +820,34 @@ } } + /** + * notifyReapNodes - notify page manager event listeners of + * reap nodes event + */ + public void notifyReapNodes() + { + // copy listeners list to reduce synchronization deadlock + List listenersList = null; + synchronized (listeners) + { + listenersList = new ArrayList(listeners); + } + // notify listeners + Iterator listenersIter = listenersList.iterator(); + while (listenersIter.hasNext()) + { + PageManagerEventListener listener = (PageManagerEventListener)listenersIter.next(); + try + { + listener.reapNodes(nodeReapingInterval); + } + catch (Exception e) + { + log.error("Failed to notify page manager event listener", e); + } + } + } + public Folder copyFolder(Folder source, String path) throws NodeException { Index: components/portal-site/src/java/org/apache/jetspeed/portalsite/impl/PortalSiteSessionContextImpl.java =================================================================== --- components/portal-site/src/java/org/apache/jetspeed/portalsite/impl/PortalSiteSessionContextImpl.java (revision 801357) +++ components/portal-site/src/java/org/apache/jetspeed/portalsite/impl/PortalSiteSessionContextImpl.java (working copy) @@ -129,6 +129,11 @@ private transient String pipeline = ""; /** + * locatorsLastUpdateCheck - time stamp of last locators update check. + */ + private transient long locatorsLastUpdateCheck; + + /** * PortalSiteSessionContextImpl - constructor * * @param pageManager PageManager component instance @@ -277,13 +282,13 @@ // clear all history entries for fallback // request path in advance to make fallback // page selection more predictable - Iterator folderIter = getFolderPageHistory().keySet().iterator(); - while (folderIter.hasNext()) + Iterator folderPathIter = getFolderPageHistory().keySet().iterator(); + while (folderPathIter.hasNext()) { - Folder folder = (Folder)folderIter.next(); - if (folder.getUrl().equals(fallbackRequestPath)) + String folderPath = (String)folderPathIter.next(); + if (folderPath.equals(fallbackRequestPath)) { - folderIter.remove(); + folderPathIter.remove(); break; } } @@ -538,21 +543,34 @@ { Page requestPage = null; - // attempt to lookup last visited page by folder proxy - // path, (proxies are hashed by their path), contains - // test must be performed since identical paths may - // occur in multiple site views + // attempt to lookup last visited page by folder path; + // page id test must be performed since identical paths + // may occur in multiple site views if (useHistory) { - requestPage = (Page)getFolderPageHistory().get(requestFolder); - if ((requestPage != null) && requestFolderPages.contains(requestPage)) + String requestPageId = (String)getFolderPageHistory().get(requestFolder.getPath()); + if (requestPageId != null) { + // find page by id in request folder pages + Iterator requestFolderPagesIter = requestFolderPages.iterator(); + while ((requestPage == null) && (requestFolderPagesIter.hasNext())) + { + Page requestFolderPage = (Page)requestFolderPagesIter.next(); + if (requestPageId.equals(requestFolderPage.getId())) + { + requestPage = requestFolderPage; + } + } + // log selected request page - if (log.isDebugEnabled()) + if (requestPage != null) { - log.debug("Selected folder historical page: path=" + view.getManagedPage(requestPage).getPath()); + if (log.isDebugEnabled()) + { + log.debug("Selected folder historical page: path=" + view.getManagedPage(requestPage).getPath()); + } + return requestPage; } - return requestPage; } } @@ -569,13 +587,12 @@ } try { - // save last visited non-hidden page for folder proxy - // path, (proxies are hashed by their path), and - // return default page + // save last visited non-hidden page for folder path + // and return default page requestPage = requestFolder.getPage(defaultPageName); if (!requestPage.isHidden()) { - getFolderPageHistory().put(requestFolder, requestPage); + getFolderPageHistory().put(requestFolder.getPath(), requestPage.getId()); } // log selected request page @@ -599,12 +616,11 @@ // default page not available, select first page // proxy in request folder; save last visited - // non-hidden page for folder proxy path, (proxies - // are hashed by their path), and return default page + // non-hidden page for folder path and return default page requestPage = (Page)requestFolderPages.iterator().next(); if (!requestPage.isHidden()) { - getFolderPageHistory().put(requestFolder, requestPage); + getFolderPageHistory().put(requestFolder.getPath(), requestPage.getId()); } // log selected request page @@ -619,13 +635,12 @@ { Page requestPage = (Page)requestNode; - // save last visited non-hidden page for folder proxy - // path, (proxies are hashed by their path), and - // return matched page + // save last visited non-hidden page for folder path + // and return matched page Folder requestFolder = (Folder)requestPage.getParent(); if (!requestPage.isHidden()) { - getFolderPageHistory().put(requestFolder, requestPage); + getFolderPageHistory().put(requestFolder.getPath(), requestPage.getId()); } // log selected request page @@ -729,6 +744,7 @@ userPrincipal = currentUserPrincipal; updated = true; } + locatorsLastUpdateCheck = System.currentTimeMillis(); } // log session context setup and update @@ -759,11 +775,11 @@ { debug.append("Updated stale"); } - debug.append(" context: user=" + userPrincipal + ", profileLocators=("); - if (profileLocators != null) + debug.append(" context: user=" + currentUserPrincipal + ", profileLocators=("); + if (requestProfileLocators != null) { boolean firstEntry = true; - Iterator entriesIter = profileLocators.entrySet().iterator(); + Iterator entriesIter = requestProfileLocators.entrySet().iterator(); while (entriesIter.hasNext()) { Map.Entry entry = (Map.Entry)entriesIter.next(); @@ -801,23 +817,20 @@ /** * clearSessionProfileLocators - clear cache session profile locators */ - private void clearSessionProfileLocators() + private synchronized void clearSessionProfileLocators() { // clear cached session profile locators, view, // folder page history, menu definition locators, // and stale flag - synchronized (this) + profileLocators = null; + userPrincipal = null; + siteView = null; + folderPageHistory = null; + if (menuDefinitionLocatorCache != null) { - profileLocators = null; - userPrincipal = null; - siteView = null; - folderPageHistory = null; - if (menuDefinitionLocatorCache != null) - { - menuDefinitionLocatorCache.clear(); - } - stale = false; + menuDefinitionLocatorCache.clear(); } + stale = false; } /** @@ -828,18 +841,27 @@ */ public SiteView getSiteView() { - if ((siteView == null) && (pageManager != null) && (profileLocators != null)) + // get or create site view + SiteView view = null; + boolean viewCreated = false; + synchronized (this) { - // create new site view - siteView = new SiteView(pageManager, profileLocators); - - // log site view creation - if (log.isDebugEnabled()) + if ((siteView == null) && (pageManager != null) && (profileLocators != null)) { - log.debug("Created site view: search paths=" + siteView.getSearchPathsString()); + // create new site view + siteView = new SiteView(pageManager, profileLocators); + viewCreated = true; } + view = siteView; } - return siteView; + + // log site view creation + if (viewCreated && log.isDebugEnabled()) + { + log.debug("Created site view: search paths=" + view.getSearchPathsString()); + } + + return view; } /** @@ -861,7 +883,7 @@ */ public boolean isValid() { - // existant transient page manager implies valid context + // existent transient page manager implies valid context return (pageManager != null); } @@ -1111,12 +1133,8 @@ } */ - /** - * newNode - invoked when the definition of a node is - * created by the page manager or when the - * node creation is otherwise detected - * - * @param node new managed node if known + /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManagerEventListener#newNode(org.apache.jetspeed.page.document.Node) */ public void newNode(Node node) { @@ -1124,12 +1142,8 @@ updatedNode(node); } - /** - * updatedNode - invoked when the definition of a node is - * updated by the page manager or when the - * node modification is otherwise detected - * - * @param node updated managed node if known + /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManagerEventListener#updatedNode(org.apache.jetspeed.page.document.Node) */ public void updatedNode(Node node) { @@ -1153,18 +1167,28 @@ } } - /** - * removedNode - invoked when the definition of a node is - * removed by the page manager or when the - * node removal is otherwise detected - * - * @param node removed managed node if known + /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManagerEventListener#removedNode(org.apache.jetspeed.page.document.Node) */ public void removedNode(Node node) { // equivalent to node updated event updatedNode(node); } + + /* (non-Javadoc) + * @see org.apache.jetspeed.page.PageManagerEventListener#reapNodes(long) + */ + public synchronized void reapNodes(long interval) + { + // reap page manager nodes for idle sessions to free + // system resources; the site view will be lazily + // recalculated if the session is resumed + if ((locatorsLastUpdateCheck > 0) && (System.currentTimeMillis()-locatorsLastUpdateCheck > interval)) + { + siteView = null; + } + } /** * sessionDidActivate - notification that the session has just @@ -1238,19 +1262,19 @@ */ public void valueUnbound(HttpSessionBindingEvent event) { - // unsubscribe this session context to page manager events synchronized (this) { + // unsubscribe this session context to page manager events if (subscribed && (pageManager != null)) { pageManager.removeListener(this); subscribed = false; } + + // clear session context state + clearSessionProfileLocators(); } - // clear session context state - clearSessionProfileLocators(); - // log binding event if (log.isDebugEnabled()) { @@ -1258,7 +1282,7 @@ } } - private Map getFolderPageHistory() + private synchronized Map getFolderPageHistory() { if (folderPageHistory == null) { Index: jetspeed-api/src/java/org/apache/jetspeed/page/PageManagerEventListener.java =================================================================== --- jetspeed-api/src/java/org/apache/jetspeed/page/PageManagerEventListener.java (revision 801357) +++ jetspeed-api/src/java/org/apache/jetspeed/page/PageManagerEventListener.java (working copy) @@ -53,4 +53,13 @@ * @param node removed managed node if known */ void removedNode(Node node); + + /** + * reapNodes - periodically invoked by page manager to + * indicate lifetime of node references should + * be checked to see if they should be reaped + * + * @param interval reap nodes interval + */ + void reapNodes(long interval); } Index: jetspeed-api/src/java/org/apache/jetspeed/page/PageManager.java =================================================================== --- jetspeed-api/src/java/org/apache/jetspeed/page/PageManager.java (revision 801357) +++ jetspeed-api/src/java/org/apache/jetspeed/page/PageManager.java (working copy) @@ -58,24 +58,27 @@ public String SERVICE_NAME = "PageManager"; /** - *- * getConstraintsEnabled - *
+ * Get constraints enabled. * * @return enabled indicator */ public boolean getConstraintsEnabled(); /** - *- * getPermissionsEnabled - *
+ * Get permissions enabled. * * @return enabled indicator */ public boolean getPermissionsEnabled(); /** + * Get node reaping interval. + * + * @return reaping interval + */ + public long getNodeReapingInterval(); + + /** * Creates a new empty Page instance * * @return a newly created Page object