View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.portalsite.impl;
18  
19  import java.io.Serializable;
20  import java.security.AccessController;
21  import java.security.Principal;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import javax.security.auth.Subject;
29  import javax.servlet.http.HttpSessionActivationListener;
30  import javax.servlet.http.HttpSessionBindingEvent;
31  import javax.servlet.http.HttpSessionBindingListener;
32  import javax.servlet.http.HttpSessionEvent;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.jetspeed.om.folder.Folder;
37  import org.apache.jetspeed.om.page.Page;
38  import org.apache.jetspeed.page.PageManager;
39  import org.apache.jetspeed.page.PageManagerEventListener;
40  import org.apache.jetspeed.page.document.Node;
41  import org.apache.jetspeed.page.document.NodeException;
42  import org.apache.jetspeed.page.document.NodeNotFoundException;
43  import org.apache.jetspeed.page.document.NodeSet;
44  import org.apache.jetspeed.portalsite.PortalSiteRequestContext;
45  import org.apache.jetspeed.portalsite.PortalSiteSessionContext;
46  import org.apache.jetspeed.portalsite.view.SiteView;
47  import org.apache.jetspeed.portalsite.view.SiteViewMenuDefinitionLocator;
48  import org.apache.jetspeed.profiler.ProfileLocator;
49  import org.apache.jetspeed.profiler.ProfileLocatorProperty;
50  import org.apache.jetspeed.security.JSSubject;
51  import org.apache.jetspeed.security.UserPrincipal;
52  
53  /***
54   * This class encapsulates managed session state for and
55   * interface to the portal-site component and subscribes
56   * to page manager and session events to flush stale state.
57   *
58   * Note that is object is Serializable since it is designed
59   * to be cached in the session. However, because this object
60   * is cached only for these two reasons:
61   *
62   * 1. a performance optimization to reuse SiteViews, and 
63   * 2. to hold optional folder page history,
64   *
65   * this object need not be relocatable between J2 instances.
66   * Consequently, all data members are marked transient and
67   * the isValid() method is used to test whether this object
68   * is a valid context for the session or if it was
69   * transferred from another server or the persistent session
70   * store and needs to be discarded.
71   * 
72   * @author <a href="mailto:rwatler@apache.org">Randy Watler</a>
73   * @version $Id: PortalSiteSessionContextImpl.java 553375 2007-07-05 05:37:00Z taylor $
74   */
75  public class PortalSiteSessionContextImpl implements PortalSiteSessionContext, PageManagerEventListener, HttpSessionActivationListener, HttpSessionBindingListener, Serializable
76  {
77      /***
78       * log - logging instance
79       */
80      private final static Log log = LogFactory.getLog(PortalSiteSessionContextImpl.class);
81  
82      /***
83       * pageManager - PageManager component
84       */
85      private transient PageManager pageManager;
86  
87      /***
88       * profileLocators - map of session profile locators by locator names
89       */
90      private transient Map profileLocators;
91  
92      /***
93       * userPrincipal - session user principal
94       */
95      private transient String userPrincipal;
96  
97      /***
98       * siteView - session site view
99       */
100     private transient SiteView siteView;
101 
102     /***
103      * folderPageHistory - map of last page visited by folder 
104      */
105     private transient Map folderPageHistory;
106 
107     /***
108      * menuDefinitionLocatorCache - cached menu definition locators for
109      *                              absolute menus valid for session
110      */
111     private transient Map menuDefinitionLocatorCache;
112 
113     /***
114      * subscribed - flag that indicates whether this context
115      *              is subscribed as event listeners
116      */
117     private transient boolean subscribed;
118 
119     /***
120      * stale - flag that indicates whether the state
121      *         managed by this context is stale
122      */
123     private transient boolean stale;
124 
125     /***
126      * store which pipeline we are serving
127      * 
128      */
129     private transient String pipeline = "";
130     
131     /***
132      * PortalSiteSessionContextImpl - constructor
133      *
134      * @param pageManager PageManager component instance
135      */
136     public PortalSiteSessionContextImpl(PageManager pageManager)
137     {
138         this.pageManager = pageManager;
139         this.pipeline = "";
140     }
141 
142     /***
143      * newRequestContext - create a new request context instance with fallback and history
144      *
145      * @param requestProfileLocators request profile locators
146      * @return new request context instance
147      */
148     public PortalSiteRequestContext newRequestContext(Map requestProfileLocators)
149     {
150         return new PortalSiteRequestContextImpl(this, requestProfileLocators, true, true);
151     }
152 
153     /***
154      * newRequestContext - create a new request context instance with history
155      *
156      * @param requestProfileLocators request profile locators
157      * @param requestFallback flag specifying whether to fallback to root folder
158      *                        if locators do not select a page or access is forbidden
159      * @return new request context instance
160      */
161     public PortalSiteRequestContext newRequestContext(Map requestProfileLocators, boolean requestFallback)
162     {
163         return new PortalSiteRequestContextImpl(this, requestProfileLocators, requestFallback, true);
164     }
165 
166     /***
167      * newRequestContext - create a new request context instance
168      *
169      * @param requestProfileLocators request profile locators
170      * @param requestFallback flag specifying whether to fallback to root folder
171      *                        if locators do not select a page or access is forbidden
172      * @param useHistory flag indicating whether to use visited page
173      *                   history to select default page per site folder
174      * @return new request context instance
175      */
176     public PortalSiteRequestContext newRequestContext(Map requestProfileLocators, boolean requestFallback, boolean useHistory)
177     {
178         return new PortalSiteRequestContextImpl(this, requestProfileLocators, requestFallback, useHistory);
179     }
180 
181     /***
182      * selectRequestPage - select page proxy for request given profile locators
183      *
184      * @param requestProfileLocators map of profile locators for request
185      * @param requestFallback flag specifying whether to fallback to root folder
186      *                        if locators do not select a page or access is forbidden
187      * @param useHistory flag indicating whether to use visited page
188      *                   history to select default page per site folder
189      * @return selected page proxy for request
190      * @throws NodeNotFoundException if not found
191      * @throws SecurityException if view access not granted
192      */
193     public Page selectRequestPage(Map requestProfileLocators, boolean requestFallback, boolean useHistory) throws NodeNotFoundException
194     {
195         // validate and update session profile locators if modified
196         if (updateSessionProfileLocators(requestProfileLocators))
197         {
198             // extract page request path from the locators
199             String requestPath = Folder.PATH_SEPARATOR;
200             ProfileLocator locator = (ProfileLocator)requestProfileLocators.get(ProfileLocator.PAGE_LOCATOR);
201             if (locator != null)
202             {
203                 // use 'page' locator to determine request page by executing
204                 // profile locator to determine path
205                 requestPath = getRequestPathFromLocator(locator);
206             }
207             else
208             {
209                 // 'page' locator unavailable, use first locator since
210                 // all locators should have identical request paths, (do
211                 // not execute profile locator though to determine path:
212                 // simply use the request path)
213                 locator = (ProfileLocator)requestProfileLocators.values().iterator().next();
214                 requestPath = locator.getRequestPath();
215             }
216             
217             // attempt to select request page or folder using
218             // profile locators and site view; if fallback
219             // enabled, fallback on missing node or access
220             // exceptions to the parent folders until the root
221             // folder access has been attempted
222             do
223             {
224                 // attempt to access requested path
225                 Exception fallbackException = null;
226                 try
227                 {
228                     return selectRequestPage(requestPath, useHistory);
229                 }
230                 catch (NodeNotFoundException nnfe)
231                 {
232                     if (!requestFallback || requestPath.equals(Folder.PATH_SEPARATOR))
233                     {
234                         throw nnfe;
235                     }
236                     fallbackException = nnfe;
237                 }
238                 catch (SecurityException se)
239                 {
240                     if (!requestFallback || requestPath.equals(Folder.PATH_SEPARATOR))
241                     {
242                         throw se;
243                     }
244                     fallbackException = se;
245                 }
246 
247                 // compute fallback request path
248                 if (requestFallback && !requestPath.equals(Folder.PATH_SEPARATOR))
249                 {
250                     // compute parent folder fallback request path
251                     String fallbackRequestPath = requestPath;
252                     while (fallbackRequestPath.endsWith(Folder.PATH_SEPARATOR))
253                     {
254                         fallbackRequestPath = fallbackRequestPath.substring(0, fallbackRequestPath.length()-1);
255                     }
256                     int folderIndex = fallbackRequestPath.lastIndexOf(Folder.PATH_SEPARATOR);
257                     if (folderIndex >= 2)
258                     {
259                         // fallback to parent folder
260                         fallbackRequestPath = fallbackRequestPath.substring(0, folderIndex);
261                     }
262                     else
263                     {
264                         // fallback to root folder
265                         fallbackRequestPath = Folder.PATH_SEPARATOR;
266                     }
267 
268                     // check fallback path and log fallback operation
269                     if (!fallbackRequestPath.equals(requestPath))
270                     {
271                         // log fallback
272                         if (log.isDebugEnabled())
273                         {
274                             log.debug("Missing/forbidden page selection fallback: request path=" + requestPath + ", attempting fallback request path=" + fallbackRequestPath, fallbackException);
275                         }
276                         
277                         // clear all history entries for fallback
278                         // request path in advance to make fallback
279                         // page selection more predictable
280                         Iterator folderIter = getFolderPageHistory().keySet().iterator();
281                         while (folderIter.hasNext())
282                         {
283                             Folder folder = (Folder)folderIter.next();
284                             if (folder.getUrl().equals(fallbackRequestPath))
285                             {
286                                 folderIter.remove();
287                                 break;
288                             }
289                         }
290 
291                         // retry requested page access
292                         requestPath = fallbackRequestPath;
293                     }
294                 }
295                 else
296                 {
297                     // fallback attempts complete: no page found for user
298                     break;
299                 }
300             }
301             while (true);
302         }
303 
304         // no request page available
305         throw new NodeNotFoundException("No request page available in site view.");
306     }
307 
308     /***
309      * getRequestPathFromLocator - execute profile locator to extract
310      *                             request path using locator rules; this
311      *                             is request specific and is not part of
312      *                             the site view
313      *
314      * @param locator profile locator to execute
315      * @return request path from profile locator
316      */
317     private String getRequestPathFromLocator(ProfileLocator locator)
318     {
319         // use profile iterator to process the initial full
320         // set of profile locator properties searching for
321         // the first non control/navigation, (i.e. page/path),
322         // property that will force the request path if
323         // non-null; otherwise default to locator request path
324         String requestPath = locator.getRequestPath();
325         Iterator locatorIter = locator.iterator();
326         if (locatorIter.hasNext())
327         {
328             ProfileLocatorProperty [] properties = (ProfileLocatorProperty []) locatorIter.next();
329             for (int i = 0; (i < properties.length); i++)
330             {
331                 if (!properties[i].isControl() && !properties[i].isNavigation())
332                 {
333                     // request page/path property; append to or replace
334                     // using locator specified path
335                     String path = properties[i].getValue();
336                     if (path != null)
337                     {
338                         // specified page/path to be appended to request path if
339                         // relative; otherwise specified page/path to replace
340                         // request path
341                         if (!path.startsWith(Folder.PATH_SEPARATOR))
342                         {
343                             // strip page from request path if required
344                             // and append page/path to base request path
345                             String basePath = requestPath;
346                             if (basePath == null)
347                             {
348                                 basePath = Folder.PATH_SEPARATOR;
349                             }
350                             else if (basePath.endsWith(Page.DOCUMENT_TYPE))
351                             {
352                                 basePath = basePath.substring(0, basePath.lastIndexOf(Folder.PATH_SEPARATOR)+1);
353                             }
354                             else if (!basePath.endsWith(Folder.PATH_SEPARATOR))
355                             {
356                                 basePath += Folder.PATH_SEPARATOR;
357                             }
358                             path = basePath + path;
359 
360                             // make sure path ends in page extension
361                             // if folder not explicitly specified
362                             if (!path.endsWith(Folder.PATH_SEPARATOR) && !path.endsWith(Page.DOCUMENT_TYPE))
363                             {
364                                 path += Page.DOCUMENT_TYPE;
365                             }
366                         }
367 
368                         // detect profile locator request path modification
369                         if (!path.equals(requestPath))
370                         {
371                             // if modified request path ends with default page,
372                             // strip default page from path to allow folder level
373                             // defaulting to take place: locator should not force
374                             // selection of default page when selection of the
375                             // folder is implied by use in locator page/path
376                             if (path.endsWith(Folder.PATH_SEPARATOR + Folder.FALLBACK_DEFAULT_PAGE))
377                             {
378                                 path = path.substring(0, path.length() - Folder.FALLBACK_DEFAULT_PAGE.length());
379                             }
380                             
381                             // log modified page request
382                             if (log.isDebugEnabled() && !path.equals(requestPath))
383                             {
384                                 log.debug("Request page modified by profile locator: request path=" + path);
385                             }
386                         }
387                         return path;
388                     }
389                 }
390             }
391         }
392 
393         // return locator request path
394         return requestPath;
395     }
396 
397     /***
398      * selectRequestPage - select page proxy for request for specified
399      *                     path given profile locators and site view
400      *                     associated with this context
401      *
402      * @param requestPath request path
403      * @param useHistory flag indicating whether to use visited page
404      *                   history to select default page per site folder
405      * @return selected page proxy for request
406      * @throws NodeNotFoundException if not found
407      * @throws SecurityException if view access not granted
408      */
409     private Page selectRequestPage(String requestPath, boolean useHistory) throws NodeNotFoundException
410     {
411         // save access exceptions
412         SecurityException accessException = null;
413 
414         // valid SiteView required from session profile locators
415         SiteView view = getSiteView();
416         if (view != null)
417         {
418             // default request to root folder if not specified
419             if (requestPath == null)
420             {
421                 requestPath = Folder.PATH_SEPARATOR;
422             }
423             
424             // log page request
425             if (log.isDebugEnabled())
426             {
427                 log.debug("Request page: request path=" + requestPath);
428             }
429 
430             // lookup request path in view for viewable page or folder
431             // nodes; note: directly requested pages/folders may be hidden
432             // or not viewable
433             Node requestNode = null;
434             try
435             {
436                 // try page or folder request url
437                 requestNode = view.getNodeProxy(requestPath, null, false, false);
438             }
439             catch (NodeNotFoundException nnfe)
440             {
441                 // if request path ends with default page, strip from
442                 // request url to retry for folder default
443                 if (requestPath.endsWith(Folder.PATH_SEPARATOR + Folder.FALLBACK_DEFAULT_PAGE))
444                 {
445                     // retry folder request url
446                     requestPath = requestPath.substring(0, requestPath.length() - Folder.FALLBACK_DEFAULT_PAGE.length());
447                     requestNode = view.getNodeProxy(requestPath, null, true, false);
448                 }
449                 else
450                 {
451                     // rethrow original exception
452                     throw nnfe;
453                 }
454             }
455             
456             // invoke default page logic to determine folder page
457             if (requestNode instanceof Folder)
458             {
459                 Folder requestFolder = (Folder)requestNode;
460                 
461                 // support subfolders specified as default pages;
462                 // find highest subfolder with a default page that
463                 // specifies a default folder, (not a default page).
464                 try
465                 {
466                     String defaultFolderName = requestFolder.getDefaultPage();
467                     if (defaultFolderName != null)
468                     {
469                         // do not follow broken default folders
470                         Folder defaultRequestFolder = requestFolder;
471                         // follow default folders to parent folders
472                         while ((defaultRequestFolder != null) && (defaultFolderName != null) &&
473                                defaultFolderName.equals(".."))
474                         {
475                             defaultRequestFolder = (Folder)defaultRequestFolder.getParent();
476                             if (defaultRequestFolder != null)
477                             {
478                                 defaultFolderName = defaultRequestFolder.getDefaultPage();
479                             }
480                             else
481                             {
482                                 defaultFolderName = null;
483                             }
484                         }
485                         // follow default folders to subfolders
486                         while ((defaultRequestFolder != null) && (defaultFolderName != null) &&
487                                !defaultFolderName.endsWith(Page.DOCUMENT_TYPE) && !defaultFolderName.equals(".."))
488                         {
489                             defaultRequestFolder = defaultRequestFolder.getFolder(defaultFolderName);
490                             defaultFolderName = defaultRequestFolder.getDefaultPage();
491                         }
492                         // use default request folder
493                         if (defaultRequestFolder != null)
494                         {
495                             requestFolder = defaultRequestFolder;
496                         }
497                     }
498                 }
499                 catch (NodeException ne)
500                 {
501                 }
502                 catch (NodeNotFoundException nnfe)
503                 {
504                 }
505                 catch (SecurityException se)
506                 {
507                     requestFolder = null;
508                     accessException = se;
509                 }
510 
511                 // only request folders with pages can be
512                 // selected by request; otherwise, fall back to
513                 // parent folders assuming that immediate parents
514                 // will have the most appropriate default page
515                 NodeSet requestFolderPages = null;
516                 if (requestFolder != null)
517                 {
518                     try
519                     {
520                         requestFolderPages = requestFolder.getPages();
521                         while (((requestFolderPages == null) || requestFolderPages.isEmpty()) && (requestFolder.getParent() != null))
522                         {
523                             requestFolder = (Folder)requestFolder.getParent();
524                             requestFolderPages = requestFolder.getPages();
525                         }
526                     }
527                     catch (NodeException ne)
528                     {
529                         requestFolderPages = null;
530                     }
531                     catch (SecurityException se)
532                     {
533                         requestFolderPages = null;
534                         accessException = se;
535                     }
536                 }
537                 if ((requestFolder != null) && (requestFolderPages != null) && !requestFolderPages.isEmpty())
538                 {
539                     Page requestPage = null;
540 
541                     // attempt to lookup last visited page by folder proxy
542                     // path, (proxies are hashed by their path), contains
543                     // test must be performed since identical paths may
544                     // occur in multiple site views
545                     if (useHistory)
546                     {
547                         requestPage = (Page)getFolderPageHistory().get(requestFolder);
548                         if ((requestPage != null) && requestFolderPages.contains(requestPage))
549                         {
550                             // log selected request page
551                             if (log.isDebugEnabled())
552                             {
553                                 log.debug("Selected folder historical page: path=" + view.getManagedPage(requestPage).getPath());
554                             }
555                             return requestPage;
556                         }
557                     }
558                     
559                     // get default page for folder proxy if more than one
560                     // page is available to choose from
561                     if (requestFolderPages.size() > 1)
562                     {
563                         String defaultPageName = requestFolder.getDefaultPage();
564                         if (defaultPageName == null)
565                         {
566                             // use fallback default if default page
567                             // not explicitly specified
568                             defaultPageName = Folder.FALLBACK_DEFAULT_PAGE;
569                         }
570                         try
571                         {
572                             // save last visited non-hidden page for folder proxy
573                             // path, (proxies are hashed by their path), and
574                             // return default page
575                             requestPage = requestFolder.getPage(defaultPageName);
576                             if (!requestPage.isHidden())
577                             {
578                                 getFolderPageHistory().put(requestFolder, requestPage);
579                             }
580                             
581                             // log selected request page
582                             if (log.isDebugEnabled())
583                             {
584                                 log.debug("Selected folder default page: path=" + view.getManagedPage(requestPage).getPath());
585                             }
586                             return requestPage;
587                         }
588                         catch (NodeException ne)
589                         {
590                         }
591                         catch (NodeNotFoundException nnfe)
592                         {
593                         }
594                         catch (SecurityException se)
595                         {
596                             accessException = se;
597                         }
598                     }
599                     
600                     // default page not available, select first page
601                     // proxy in request folder; save last visited
602                     // non-hidden page for folder proxy path, (proxies
603                     // are hashed by their path), and return default page
604                     requestPage = (Page)requestFolderPages.iterator().next();
605                     if (!requestPage.isHidden())
606                     {
607                         getFolderPageHistory().put(requestFolder, requestPage);
608                     }
609 
610                     // log selected request page
611                     if (log.isDebugEnabled())
612                     {
613                         log.debug("Selected first folder page, path=" + view.getManagedPage(requestPage).getPath());
614                     }
615                     return requestPage;
616                 }
617             }
618             else if (requestNode instanceof Page)
619             {
620                 Page requestPage = (Page)requestNode;
621                 
622                 // save last visited non-hidden page for folder proxy
623                 // path, (proxies are hashed by their path), and
624                 // return matched page
625                 Folder requestFolder = (Folder)requestPage.getParent();
626                 if (!requestPage.isHidden())
627                 {
628                 	getFolderPageHistory().put(requestFolder, requestPage);
629                 }
630 
631                 // log selected request page
632                 if (log.isDebugEnabled())
633                 {
634                     log.debug("Selected page, path=" + view.getManagedPage(requestPage).getPath());
635                 }
636                 return requestPage;
637             }
638         }
639             
640         // no page matched or accessible
641         if (accessException != null)
642         {
643             throw accessException;
644         }
645         throw new NodeNotFoundException("No page matched " + requestPath + " request in site view.");
646     }
647     
648     /***
649      * getRequestRootFolder - select root folder proxy for given profile locators
650      *
651      * @param requestProfileLocators map of profile locators for request
652      * @return root folder proxy for request
653      * @throws NodeNotFoundException if not found
654      * @throws SecurityException if view access not granted
655      */
656     public Folder getRequestRootFolder(Map requestProfileLocators) throws NodeNotFoundException
657     {
658         // validate and update session profile locators if modified
659         if (updateSessionProfileLocators(requestProfileLocators))
660         {
661             // valid site view required from session profile locators
662             SiteView view = getSiteView();
663             if (view != null)
664             {
665                 // return root folder proxy from session site view
666                 return view.getRootFolderProxy();
667             }
668         }
669 
670         // no root folder available
671         throw new NodeNotFoundException("No root folder available in site view.");
672     }
673 
674     /***
675      * updateSessionProfileLocators - detect modification of and update cached
676      *                                session profile locators
677      *
678      * @param requestProfileLocators map of profile locators for request
679      * @return profile locators validation flag
680      */
681     private boolean updateSessionProfileLocators(Map requestProfileLocators)
682     {
683         // request profile locators are required
684         if ((requestProfileLocators != null) && !requestProfileLocators.isEmpty())
685         {
686             // get current user principal; ignore derivative
687             // changes in role and group principals
688             String currentUserPrincipal = null;
689             Subject subject = JSSubject.getSubject(AccessController.getContext());
690             if (subject != null)
691             {
692                 Iterator principals = subject.getPrincipals().iterator();
693                 while (principals.hasNext())
694                 {
695                     Principal principal = (Principal) principals.next();
696                     if (principal instanceof UserPrincipal)
697                     {
698                         if (currentUserPrincipal == null)
699                         {
700                             currentUserPrincipal = principal.getName();
701                         }
702                         else
703                         {
704                             currentUserPrincipal += "|" + principal.getName();
705                         }
706                     }
707                 }
708             }
709 
710             // detect stale session, modification of user
711             // principal, or changed profile locators for
712             // this session context
713             boolean userUpdate = false;
714             boolean locatorsUpdate = false;
715             boolean updated = false;
716             synchronized (this)
717             {
718                 userUpdate = (((userPrincipal == null) && (currentUserPrincipal != null)) ||
719                               ((userPrincipal != null) && !userPrincipal.equals(currentUserPrincipal)));
720                 locatorsUpdate = ((profileLocators == null) ||
721                                   !locatorsEquals(profileLocators, requestProfileLocators));
722                 if (stale || userUpdate || locatorsUpdate)
723                 {
724                     // reset cached session profile locators, view,
725                     // folder page history, menu definition locators,
726                     // and stale flag
727                     clearSessionProfileLocators();
728                     profileLocators = requestProfileLocators;
729                     userPrincipal = currentUserPrincipal;
730                     updated = true;
731                 }
732             }
733 
734             // log session context setup and update
735             if (updated && log.isDebugEnabled())
736             {
737                 StringBuffer debug = new StringBuffer();
738                 if (userUpdate)
739                 {
740                     debug.append("Updated user");
741                     if (locatorsUpdate)
742                     {
743                         debug.append("/locators");
744                     }
745                     if (stale)
746                     {
747                         debug.append("/stale");
748                     }
749                 }
750                 else if (locatorsUpdate)
751                 {
752                     debug.append("Updated locators");
753                     if (stale)
754                     {
755                         debug.append("/stale");
756                     }
757                 }
758                 else
759                 {
760                     debug.append("Updated stale");
761                 }
762                 debug.append(" context: user=" + userPrincipal + ", profileLocators=(");
763                 if (profileLocators != null)
764                 {
765                     boolean firstEntry = true;
766                     Iterator entriesIter = profileLocators.entrySet().iterator();
767                     while (entriesIter.hasNext())
768                     {
769                         Map.Entry entry = (Map.Entry)entriesIter.next();
770                         String locatorName = (String)entry.getKey();
771                         ProfileLocator locator = (ProfileLocator)entry.getValue();
772                         if (!firstEntry)
773                         {
774                             debug.append(",");
775                         }
776                         else
777                         {
778                             firstEntry = false;
779                         }
780                         debug.append(locatorName);
781                         debug.append("=");
782                         debug.append(locator.toString());
783                     }
784                 }
785                 else
786                 {
787                     debug.append("null");
788                 }
789                 debug.append(")");
790                 log.debug(debug);
791             }
792 
793             // return valid
794             return true;
795         }
796 
797         // return invalid
798         return false;
799     }
800 
801     /***
802      * clearSessionProfileLocators - clear cache session profile locators
803      */
804     private void clearSessionProfileLocators()
805     {
806         // clear cached session profile locators, view,
807         // folder page history, menu definition locators,
808         // and stale flag
809         synchronized (this)
810         {
811             profileLocators = null;
812             userPrincipal = null;
813             siteView = null;
814             folderPageHistory = null;
815             if (menuDefinitionLocatorCache != null)
816             {
817                 menuDefinitionLocatorCache.clear();
818             }
819             stale = false;
820         }
821     }
822 
823     /***
824      * getSiteView - lookup and/or create site view for
825      *               profile locators of this context
826      *
827      * @return site view instance
828      */
829     public SiteView getSiteView()
830     {
831         if ((siteView == null) && (pageManager != null) && (profileLocators != null))
832         {
833             // create new site view
834             siteView = new SiteView(pageManager, profileLocators);
835 
836             // log site view creation
837             if (log.isDebugEnabled())
838             {
839                 log.debug("Created site view: search paths=" + siteView.getSearchPathsString());
840             }
841         }
842         return siteView;
843     }
844 
845     /***
846      * getPageManager - return PageManager component instance
847      *
848      * @return PageManager instance
849      */
850     public PageManager getPageManager()
851     {
852         return pageManager;
853     }
854 
855     /***
856      * isValid - return flag indicating whether this context instance
857      *           is valid or if it is stale after being persisted and
858      *           reloaded as session state
859      *
860      * @return valid context status
861      */
862     public boolean isValid()
863     {
864         // existant transient page manager implies valid context 
865         return (pageManager != null);
866     }
867 
868     /***
869      * getProfileLocators - get session profile locators
870      */
871     public Map getProfileLocators()
872     {
873         return profileLocators;
874     }
875 
876     /***
877      * getStandardMenuNames - get set of available standard menu names
878      *  
879      * @return menu names set
880      */
881     public Set getStandardMenuNames()
882     {
883         // return standard menu names defined for site view
884         SiteView view = getSiteView();
885         if (view != null)
886         {
887             return view.getStandardMenuNames();
888         }
889         return null;
890     }
891 
892     /***
893      * getMenuDefinitionLocators - get list of node proxy menu definition
894      *                             locators from site view
895      *
896      * @param node site view node proxy
897      * @return definition locator list
898      */
899     public List getMenuDefinitionLocators(Node node)
900     {
901         // return menu definition locators for node in site view
902         SiteView view = getSiteView();
903         if (view != null)
904         {
905             return view.getMenuDefinitionLocators(node);
906         }
907         return null;
908     }
909 
910     /***
911      * getMenuDefinitionLocator - get named node proxy menu definition
912      *                            locator from site view
913      *
914      * @param node site view node proxy
915      * @param name menu definition name
916      * @return menu definition locator
917      */
918     public SiteViewMenuDefinitionLocator getMenuDefinitionLocator(Node node, String name)
919     {
920         // return named menu definition locator for node in site view
921         SiteView view = getSiteView();
922         if (view != null)
923         {
924             return view.getMenuDefinitionLocator(node, name);
925         }
926         return null;
927     }
928 
929     /***
930      * getManagedPage - get concrete page instance from page proxy
931      *  
932      * @param page page proxy
933      * @return managed page
934      */
935     public Page getManagedPage(Page page)
936     {
937         // return managed page in site view
938         SiteView view = getSiteView();
939         if (view != null)
940         {
941             return view.getManagedPage(page);            
942         }
943         return null;
944     }
945 
946     /***
947      * getMenuDefinitionLocatorCache - get menu definition locators cache
948      *                                 for absolute menus
949      *
950      * @return menu definition locators cache
951      */
952     public Map getMenuDefinitionLocatorCache()
953     {
954         return menuDefinitionLocatorCache;
955     }
956 
957     /***
958      * setMenuDefinitionLocatorCache - set menu definition locators cache
959      *                                 for absolute menus
960      *
961      * @return menu definition locators cache
962      */
963     public void setMenuDefinitionLocatorCache(Map cache)
964     {
965         menuDefinitionLocatorCache = cache;
966     }
967 
968     /***
969      * locatorsEquals - test profile locator maps for equivalence
970      *                  ignoring request specifics
971      *
972      * @param locators0 request profile locator map
973      * @param locators1 request profile locator map
974      * @return boolean flag indicating equivalence
975      */
976     private static boolean locatorsEquals(Map locators0, Map locators1)
977     {
978         // trivial comparison
979         if (locators0 == locators1)
980         {
981             return true;
982         }
983 
984         // compare locator map sizes
985         if (locators0.size() != locators1.size())
986         {
987             return false;
988         }
989 
990         // compare locator map entries
991         Iterator entriesIter = locators0.entrySet().iterator();
992         if (entriesIter.hasNext())
993         {
994             Map.Entry entry = (Map.Entry)entriesIter.next();
995             ProfileLocator locator0 = (ProfileLocator)entry.getValue();
996             ProfileLocator locator1 = (ProfileLocator)locators1.get(entry.getKey());
997             if (locator1 == null)
998             {
999                 return false;
1000             }
1001 
1002             // compare locators using the most specific,
1003             // (i.e. first), locator properties array
1004             // returned by the locator iterator
1005             ProfileLocatorProperty [] properties0 = (ProfileLocatorProperty [])locator0.iterator().next();
1006             ProfileLocatorProperty [] properties1 = (ProfileLocatorProperty [])locator1.iterator().next();
1007             if ((properties0 != null) || (properties1 != null))
1008             {
1009                 if ((properties0 == null) || (properties1 == null) || (properties0.length != properties1.length))
1010                 {
1011                     return false;
1012                 }
1013                 
1014                 // compare ordered locator properties
1015                 for (int i = 0, limit = properties0.length; (i < limit); i++)
1016                 {
1017                     // compare property names, control flags, navigation flags,
1018                     // and values. note: properties values are compared only for
1019                     // control or navigation properties; otherwise they are
1020                     // assumed to contain variable request paths that should
1021                     // be treated as equivalent
1022                     if (!properties0[i].getName().equals(properties1[i].getName()) ||
1023                         (properties0[i].isControl() && !properties1[i].isControl()) ||
1024                         (properties0[i].isNavigation() && !properties1[i].isNavigation()) ||
1025                         ((properties0[i].isControl() || properties0[i].isNavigation()) && 
1026                          (((properties0[i].getValue() == null) && (properties1[i].getValue() != null)) ||
1027                           ((properties0[i].getValue() != null) && !properties0[i].getValue().equals(properties1[i].getValue())))))
1028                     {
1029                         return false;
1030                     }
1031                 }
1032             }
1033         }
1034         return true;
1035     }
1036 
1037     /***
1038      * locatorRequestPath - extract request specific path from profile locator
1039      *                      using request path from locator
1040      *
1041      * @param locator request profile locator
1042      * @return request path
1043      
1044     private static String locatorRequestPath(ProfileLocator locator)
1045     {
1046         // use request path in locator as default
1047         return locatorRequestPath(locator, locator.getRequestPath());
1048     }
1049     */
1050 
1051     /***
1052      * locatorRequestPath - extract request specific path from profile locator
1053      *
1054      * @param locator request profile locator
1055      * @param requestPath request path
1056      * @return request path
1057      
1058     private static String locatorRequestPath(ProfileLocator locator, String requestPath)
1059     {
1060         // search locator using the most specific,
1061         // (i.e. first), locator properties array
1062         // returned by the locator iterator and return
1063         // first valued property that is not a control
1064         // or navigation type
1065         ProfileLocatorProperty [] properties = (ProfileLocatorProperty [])locator.iterator().next();
1066         for (int i = 0, limit = properties.length; (i < limit); i++)
1067         {
1068             if (!properties[i].isControl() && !properties[i].isNavigation() && (properties[i].getValue() != null))
1069             {
1070                 // use specified locator path
1071                 String locatorPath = properties[i].getValue();
1072 
1073                 // return specified locatorPath if absolute
1074                 if (locatorPath.startsWith(Folder.PATH_SEPARATOR))
1075                 {
1076                     return locatorPath;
1077                 }
1078 
1079                 // page names and relative paths are assumed relative to
1080                 // request path and that any locator paths with no url
1081                 // separator should have the page extension appended
1082                 // get default page if page path null
1083                 if ((locatorPath.indexOf(Folder.PATH_SEPARATOR) == -1) && !locatorPath.endsWith(Page.DOCUMENT_TYPE))
1084                 {
1085                     locatorPath += Page.DOCUMENT_TYPE;
1086                 }
1087             
1088                 // append locator path to request path, replacing
1089                 // requested pages and preserving requested folders
1090                 boolean rootFolderRequest = requestPath.equals(Folder.PATH_SEPARATOR);
1091                 boolean folderRequest = (!requestPath.endsWith(Page.DOCUMENT_TYPE));
1092                 int lastSeparatorIndex = requestPath.lastIndexOf(Folder.PATH_SEPARATOR_CHAR);
1093                 if ((lastSeparatorIndex > 0) && (!folderRequest || requestPath.endsWith(Folder.PATH_SEPARATOR)))
1094                 {
1095                     // append locator to request path base path
1096                     return requestPath.substring(0, lastSeparatorIndex) + Folder.PATH_SEPARATOR + locatorPath;
1097                 }
1098                 else if (!rootFolderRequest && folderRequest)
1099                 {
1100                     // append locator to request path root folder
1101                     return requestPath + Folder.PATH_SEPARATOR + locatorPath;
1102                 }
1103                 else
1104                 {
1105                     // use root folder locator
1106                     return Folder.PATH_SEPARATOR + locatorPath;
1107                 }
1108             }
1109         }
1110         return requestPath;
1111     }
1112     */
1113 
1114     /***
1115      * newNode - invoked when the definition of a node is
1116      *           created by the page manager or when the
1117      *           node creation is otherwise detected
1118      *
1119      * @param node new managed node if known
1120      */
1121     public void newNode(Node node)
1122     {
1123         // equivalent to node updated event
1124         updatedNode(node);
1125     }
1126 
1127     /***
1128      * updatedNode - invoked when the definition of a node is
1129      *               updated by the page manager or when the
1130      *               node modification is otherwise detected
1131      *
1132      * @param node updated managed node if known
1133      */
1134     public void updatedNode(Node node)
1135     {
1136         // set stale flag to force session context state reset
1137         synchronized (this)
1138         {
1139             stale = true;
1140         }
1141 
1142         // log updated node event
1143         if (log.isDebugEnabled())
1144         {
1145             if (node != null)
1146             {
1147                 log.debug("Page manager update event, (node=" + node.getPath() + "): set session context state stale");
1148             }
1149             else
1150             {
1151                 log.debug("Page manager update event: set session context state stale");
1152             }
1153         }
1154     }
1155 
1156     /***
1157      * removedNode - invoked when the definition of a node is
1158      *               removed by the page manager or when the
1159      *               node removal is otherwise detected
1160      *
1161      * @param node removed managed node if known
1162      */
1163     public void removedNode(Node node)
1164     {
1165         // equivalent to node updated event
1166         updatedNode(node);
1167     }
1168 
1169     /***
1170      * sessionDidActivate - notification that the session has just
1171      *                      been activated
1172      *
1173      * @param event session activation event
1174      */
1175     public void sessionDidActivate(HttpSessionEvent event)
1176     {
1177         // set stale flag to force session context state reset
1178         synchronized (this)
1179         {
1180             stale = true;
1181         }
1182 
1183         // log activation event
1184         if (log.isDebugEnabled())
1185         {
1186             log.debug("Session activation event: set session context state stale");
1187         }
1188     }
1189     
1190     /***
1191      * sessionWillPassivate - notification that the session is about
1192      *                        to be passivated
1193      *
1194      * @param event session activation event
1195      */
1196     public void sessionWillPassivate(HttpSessionEvent event)
1197     {
1198         // clear session context state
1199         clearSessionProfileLocators();
1200 
1201         // log activation event
1202         if (log.isDebugEnabled())
1203         {
1204             log.debug("Session deactivation event: clear session context state");
1205         }
1206     }
1207 
1208     /***
1209      * valueBound - notifies this context that it is being bound to
1210      *              a session and identifies the session
1211      *
1212      * @param event session binding event
1213      */
1214     public void valueBound(HttpSessionBindingEvent event)
1215     {
1216         // subscribe this session context to page manager events
1217         synchronized (this)
1218         {
1219             if (!subscribed && (pageManager != null))
1220             {
1221                 pageManager.addListener(this);
1222                 subscribed = true;
1223             }
1224         }
1225 
1226         // log binding event
1227         if (log.isDebugEnabled())
1228         {
1229             log.debug("Session bound event: setup page manager listener");
1230         }
1231     }
1232     
1233     /***
1234      * valueUnbound - notifies this context that it is being unbound
1235      *                from a session and identifies the session
1236      *
1237      * @param event session binding event
1238      */
1239     public void valueUnbound(HttpSessionBindingEvent event)
1240     {
1241         // unsubscribe this session context to page manager events
1242         synchronized (this)
1243         {
1244             if (subscribed && (pageManager != null))
1245             {
1246                 pageManager.removeListener(this);
1247                 subscribed = false;
1248             }
1249         }
1250 
1251         // clear session context state
1252         clearSessionProfileLocators();
1253 
1254         // log binding event
1255         if (log.isDebugEnabled())
1256         {
1257             log.debug("Session unbound event: clear page manager listener and session context state");
1258         }
1259     }
1260 
1261 	private Map getFolderPageHistory()
1262     {
1263 		if (folderPageHistory == null)
1264         {
1265 			folderPageHistory = new HashMap();
1266 		}
1267 		return folderPageHistory;
1268 	}
1269     
1270     public void setPipeline(String pipeline)
1271     {
1272         this.pipeline = pipeline;
1273     }
1274     
1275     public String getPipeline()
1276     {
1277         return this.pipeline;
1278     }
1279 }