View Javadoc

1   /*
2    * WebFlow Navigation Manager: webflow definiton, server side navigation history and automatic session cleaning.
3    * Distributed under LGPL license at web site http://wfnm.sourceforge.net .
4    */
5   package net.sf.wfnm;
6   
7   import org.apache.commons.logging.Log;
8   import org.apache.commons.logging.LogFactory;
9   
10  import java.util.HashMap;
11  import java.util.HashSet;
12  import java.util.Iterator;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.Stack;
16  
17  
18  /***
19   * The navigation manager default implementation.
20   *
21   * @author <a href="mailto:malbari@users.sourceforge.net">Maurizio Albari</a>
22   * @version 1.0.6
23   */
24  public class NavigationManagerImpl implements NavigationManager, ObjectSetOwner {
25      /*** 
26       * The logger.
27       */
28      private static Log log = LogFactory.getLog(NavigationManagerImpl.class);
29  
30      /*** 
31       * The default webflow name.
32       */
33      protected static final String DEFAULT_WEBFLOW_NAME = "default";
34  
35      /*** 
36       * A map that bind the object key with the owner of the object.
37       */
38      protected Map object2owner = new HashMap();
39  
40      /*** 
41       * A map that bind an url with a webflow witch contains a page with that url.
42       */
43      protected Map url2webflow = new HashMap();
44  
45      /*** 
46       * The synchronization lock.
47       */
48      protected transient Object lock;
49  
50      /*** 
51       * The no ownership set.
52       */
53      protected Set noOwnershipSet;
54  
55      /*** 
56       * The stack of webflow.
57       */
58      protected Stack webflowStack = new Stack();
59  
60      /*** 
61       * The page changed listener
62       */
63      private PageChangedListener pageChangedListener;
64  
65      /*** 
66       * The webflow changed listener
67       */
68      private WebflowChangedListener webflowChangedListener;
69  
70      /***
71       * Creates a new NavigationManager object.
72       */
73      protected NavigationManagerImpl() {
74          super();
75      }
76  
77      /***
78       * Gets the url of the current page.
79       *
80       * @return the url of the current page (null=no visited page)
81       */
82      public String getCurrentPage() {
83          String url = null;
84          Webflow webflow = getTopWebflow();
85  
86          if (webflow != null) {
87              Page page = webflow.getTopPage();
88  
89              if (page != null) {
90                  url = page.getUrl();
91              } else {
92                  log.debug("Bug detected, webflow opened with no visited pages");
93              }
94          }
95  
96          return url;
97      }
98  
99      /***
100      * Gets the current webflow name.
101      *
102      * @return the current webflow name
103      */
104     public String getCurrentWebflow() {
105         String currentWebflow = null;
106         Webflow webflow = getTopWebflow();
107 
108         if (webflow != null) {
109             currentWebflow = webflow.getName();
110         }
111 
112         return currentWebflow;
113     }
114 
115     /***
116      * Sets the synchronization lock.
117      *
118      * @param lock the synchronization lock
119      */
120     public void setLock(Object lock) {
121         this.lock = lock;
122     }
123 
124     /***
125      * Gets the synchronization lock.
126      *
127      * @return the synchronization lock.
128      */
129     public Object getLock() {
130         return lock;
131     }
132 
133     /***
134      * Sets the object owner.
135      *
136      * @param objectKey the object key
137      * @param objectOwnership the object ownership
138      */
139     public void setObjectOwnership(String objectKey, int objectOwnership) {
140         ObjectSetOwner objectSetOwner = (ObjectSetOwner) object2owner.get(objectKey);
141 
142         if (objectSetOwner == null) {
143             objectSetOwner = getObjectOwnership(objectOwnership);
144 
145             if (objectSetOwner != null) {
146                 object2owner.put(objectKey, objectSetOwner);
147                 objectSetOwner.getOwnedObjectSet().add(objectKey);
148             } else {
149                 log.warn("Unable to set the ownership of object with key '" + objectKey + "' to " +
150                     OWNERSHIP_DESC[objectOwnership]);
151             }
152         }
153     }
154 
155     /***
156      * Gets the object set owned (with no ownership).
157      *
158      * @return the object set owned (with no ownership)
159      */
160     public Set getOwnedObjectSet() {
161         if (noOwnershipSet == null) {
162             noOwnershipSet = new HashSet();
163         }
164 
165         return noOwnershipSet;
166     }
167 
168     /***
169      * Sets the page changed listener.
170      *
171      * @param pageChangedListener the page changed listener
172      */
173     public void setPageChangedListener(PageChangedListener pageChangedListener) {
174         this.pageChangedListener = pageChangedListener;
175     }
176 
177     /***
178      * Returns true if the page has been visited.
179      *
180      * @param url the url of the page
181      *
182      * @return true if the page has been visited
183      */
184     public boolean isPageVisited(String url) {
185         return (url2webflow.get(url) != null);
186     }
187 
188     /***
189      * Gets the url of the previous page.
190      *
191      * @return the url of the previous page (null=no previous page)
192      */
193     public String getPreviousPage() {
194         String url = null;
195         Webflow webflow = getTopWebflow();
196 
197         if (webflow != null) {
198             Page page = webflow.getPreviousPage();
199 
200             if (page != null) {
201                 url = page.getUrl();
202             } else {
203                 Webflow previousWebflow = getPreviousWebflowOrTop();
204 
205                 if (previousWebflow != null) {
206                     Page previousPage = previousWebflow.getTopPage();
207 
208                     if (previousPage != null) {
209                         url = previousPage.getUrl();
210                     }
211                 }
212             }
213         }
214 
215         return url;
216     }
217 
218     /***
219      * Gets the url of the previous webflow top page.
220      *
221      * @return the url of the previous webflow top page (null=no previous webflow top page)
222      */
223     public String getPreviousWebflow() {
224         String url = null;
225         Webflow webflow = getPreviousWebflowOrTop();
226 
227         if (webflow != null) {
228             Page page = webflow.getTopPage();
229 
230             if (page != null) {
231                 url = page.getUrl();
232             }
233         }
234 
235         return url;
236     }
237 
238     /***
239      * Gets the url of a webflow top page.
240      *
241      * @param webflowName the webflow name
242      *
243      * @return the url of a webflow top page (null=no visited pages in the specified webflow)
244      */
245     public String getPreviousWebflow(String webflowName) {
246         String url = null;
247         int webflowIndex = findWebflowIndex(webflowName);
248 
249         if (webflowIndex >= 0) {
250             Webflow webflow = (Webflow) webflowStack.elementAt(webflowIndex);
251             Page page = webflow.getTopPage();
252 
253             if (page != null) {
254                 url = page.getUrl();
255             }
256         }
257 
258         return url;
259     }
260 
261     /***
262      * Sets the webflow changed listener.
263      *
264      * @param webflowChangedListener the webflow changed listener
265      */
266     public void setWebflowChangedListener(WebflowChangedListener webflowChangedListener) {
267         this.webflowChangedListener = webflowChangedListener;
268     }
269 
270     /***
271      * Returns the webflow stack.
272      *
273      * @return the webflow stack
274      */
275     public Stack getWebflowStack() {
276         return webflowStack;
277     }
278 
279     /***
280      * Returns true if the webflow has been visited.
281      *
282      * @param webflowName the webflow name
283      *
284      * @return true if the webflow has been visited
285      */
286     public boolean isWebflowVisited(String webflowName) {
287         int webflowIndex = findWebflowIndex(webflowName);
288 
289         return (webflowIndex >= 0);
290     }
291 
292     /***
293      * Notify that the navigation has reached a page.
294      *
295      * @param container the attribute container
296      * @param url the url of the page
297      * @param addedObjectSet the added object set
298      * @param removedObjectSet the removed object set
299      */
300     public void notifyPage(AttributeContainer container, String url, Set addedObjectSet, Set removedObjectSet) {
301         Set objectSetToRemove = new HashSet();
302         Webflow webflow = (Webflow) url2webflow.get(url);
303 
304         if (webflow != null) {
305             // the page has been already visited
306             backToWebflow(webflow.getName(), objectSetToRemove, container);
307 
308             webflow.backToPage(url, objectSetToRemove, url2webflow, container);
309         } else {
310             // the page has not yet visited
311             webflow = getTopWebflow();
312 
313             if (webflow == null) {
314                 log.info("No webflow opened, opening a default webflow called '" + DEFAULT_WEBFLOW_NAME + "'");
315                 webflow = new Webflow(DEFAULT_WEBFLOW_NAME, WEBFLOW_OWNERSHIP, pageChangedListener);
316                 webflowStack.push(webflow);
317 
318                 if (webflowChangedListener != null) {
319                     webflowChangedListener.webflowOpened(DEFAULT_WEBFLOW_NAME, container);
320                 }
321             }
322 
323             webflow.addPage(new Page(url), container);
324             url2webflow.put(url, webflow);
325         }
326 
327         addObjectSet(addedObjectSet);
328         removeObjectSet(removedObjectSet);
329 
330         removeObjectSet(objectSetToRemove);
331         removeObjectSetFromContainer(container, objectSetToRemove);
332     }
333 
334     /***
335      * Notify that the navigation has reached a page of a specific webflow.
336      *
337      * @param container the attribute container
338      * @param url the url of the page
339      * @param webflowName the webflow name
340      * @param defaultWebflowOwnership the default webflow ownership
341      * @param addedObjectSet the added object set
342      * @param removedObjectSet the removed object set
343      */
344     public void notifyPage(AttributeContainer container, String url, String webflowName, int defaultWebflowOwnership,
345         Set addedObjectSet, Set removedObjectSet) {
346         Webflow webflow = (Webflow) url2webflow.get(url);
347 
348         if (webflow != null) {
349             if (!webflow.getName().equals(webflowName)) {
350                 log.warn("Ignoring webflow entry '" + webflowName + "', the url '" + url +
351                     "' has been already visited in webflow '" + webflow.getName() + "'");
352             }
353 
354             notifyPage(container, url, addedObjectSet, removedObjectSet);
355         } else {
356             int webflowIndex = findWebflowIndex(webflowName);
357 
358             if (webflowIndex >= 0) {
359                 notifyPage(container, url, addedObjectSet, removedObjectSet);
360             } else {
361                 webflow = new Webflow(webflowName, defaultWebflowOwnership, pageChangedListener);
362                 webflowStack.push(webflow);
363 
364                 if (webflowChangedListener != null) {
365                     webflowChangedListener.webflowOpened(webflowName, container);
366                 }
367 
368                 webflow.addPage(new Page(url), container);
369                 url2webflow.put(url, webflow);
370 
371                 addObjectSet(addedObjectSet);
372                 removeObjectSet(removedObjectSet);
373             }
374         }
375     }
376 
377     /***
378      * Reset the navigation manager.
379      *
380      * @param container the attribute container
381      */
382     public void resetFramework(AttributeContainer container) {
383         Set objectSetToRemove = new HashSet();
384 
385         while (webflowStack.size() > 0) {
386             Webflow webflow = getTopWebflow();
387             webflow.closeWebflow(objectSetToRemove, url2webflow, container);
388             webflowStack.pop();
389 
390             if (webflowChangedListener != null) {
391                 webflowChangedListener.webflowClosed(webflow.getName(), container);
392             }
393         }
394 
395         removeObjectSet(objectSetToRemove);
396         removeObjectSetFromContainer(container, objectSetToRemove);
397     }
398 
399     /***
400      * Gets the object set owner.
401      *
402      * @param ownership the ownership
403      *
404      * @return the object set owner
405      */
406     protected ObjectSetOwner getObjectOwnership(int ownership) {
407         ObjectSetOwner objectSetOwner = null;
408 
409         switch (ownership) {
410         case PAGE_OWNERSHIP:
411             objectSetOwner = getTopPage();
412 
413             break;
414 
415         case WEBFLOW_OWNERSHIP:
416             objectSetOwner = getTopWebflow();
417 
418             break;
419 
420         case PREVIOUS_OWNERSHIP:
421             objectSetOwner = getPreviousWebflowOrTop();
422 
423             break;
424 
425         case WORKING_OWNERSHIP:
426             objectSetOwner = getWebflowByIndex(1);
427 
428             break;
429 
430         case GLOBAL_OWNERSHIP:
431             objectSetOwner = getWebflowByIndex(0);
432 
433             break;
434 
435         case NO_OWNERSHIP:
436             objectSetOwner = this;
437 
438             break;
439 
440         default:
441             break;
442         }
443 
444         if (objectSetOwner == null) {
445             log.debug("The object owner set is null");
446         }
447 
448         return objectSetOwner;
449     }
450 
451     /***
452      * Gets the previous webflow, or the top webflow if there is only one webflow
453      *
454      * @return the previous webflow (null=no previous webflow)
455      */
456     protected Webflow getPreviousWebflowOrTop() {
457         Webflow previousWebflow = null;
458 
459         if (webflowStack.size() >= 2) {
460             previousWebflow = (Webflow) webflowStack.elementAt(webflowStack.size() - 2);
461         } else {
462             previousWebflow = getTopWebflow();
463         }
464 
465         return previousWebflow;
466     }
467 
468     /***
469      * Returns the top page.
470      *
471      * @return the top page (null = no pages in this webflow or no webflows)
472      */
473     protected Page getTopPage() {
474         Page topPage = null;
475         Webflow webflow = getTopWebflow();
476 
477         if (webflow != null) {
478             topPage = webflow.getTopPage();
479         }
480 
481         return topPage;
482     }
483 
484     /***
485      * Gets the top webflow (null=no webflow opened).
486      *
487      * @return the top webflow
488      */
489     protected Webflow getTopWebflow() {
490         Webflow webflow = null;
491 
492         if (webflowStack.size() > 0) {
493             webflow = (Webflow) webflowStack.elementAt(webflowStack.size() - 1);
494         }
495 
496         return webflow;
497     }
498 
499     /***
500      * Gets a webflow given its index. If it does not exists returns the webflow with the minimal  index that exists or
501      * null if it does not exists.
502      *
503      * @param index the webflow index
504      *
505      * @return the webflow (null=no webflow)
506      */
507     protected Webflow getWebflowByIndex(int index) {
508         Webflow webflow = null;
509 
510         if (webflowStack.size() > index) {
511             webflow = (Webflow) webflowStack.elementAt(index);
512         } else if (webflowStack.size() > 0) {
513             webflow = (Webflow) webflowStack.elementAt(webflowStack.size() - 1);
514         }
515 
516         return webflow;
517     }
518 
519     /***
520      * Add an object to the navigation manager.
521      *
522      * @param objectKeySet the object key
523      */
524     protected void addObjectSet(Set objectKeySet) {
525         if (objectKeySet != null) {
526             for (Iterator i = objectKeySet.iterator(); i.hasNext();) {
527                 String objectKey = (String) i.next();
528                 ObjectSetOwner objectSetOwner = (ObjectSetOwner) object2owner.get(objectKey);
529 
530                 if (objectSetOwner == null) {
531                     Webflow webflow = getTopWebflow();
532 
533                     if (webflow != null) {
534                         objectSetOwner = getObjectOwnership(webflow.getDefaultOwnership());
535 
536                         if (objectSetOwner != null) {
537                             object2owner.put(objectKey, objectSetOwner);
538                             objectSetOwner.getOwnedObjectSet().add(objectKey);
539                         }
540                     }
541                 }
542             }
543         }
544     }
545 
546     /***
547      * Back to a specified webflow (if different from the current).
548      *
549      * @param webflowName the webflow name
550      * @param objectSetToRemove the object set to remove for goinf to the specified webflow
551      * @param container the attribute container
552      */
553     protected void backToWebflow(String webflowName, Set objectSetToRemove, AttributeContainer container) {
554         Webflow webflow = getTopWebflow();
555 
556         while ((webflow != null) && !webflow.getName().equals(webflowName)) {
557             webflow.closeWebflow(objectSetToRemove, url2webflow, container);
558             webflowStack.pop();
559 
560             if (webflowChangedListener != null) {
561                 webflowChangedListener.webflowClosed(webflow.getName(), container);
562             }
563 
564             webflow = getTopWebflow();
565         }
566     }
567 
568     /***
569      * Find the index of a named webflow.
570      *
571      * @param webflowName the webflow to find
572      *
573      * @return the index of the webflow inside the webflowStack (-1=webflow does not exists)
574      */
575     protected int findWebflowIndex(String webflowName) {
576         int webflowIndex = -1;
577 
578         for (int i = webflowStack.size() - 1; (i >= 0) && (webflowIndex < 0); i--) {
579             Webflow webflow = (Webflow) webflowStack.elementAt(i);
580 
581             if (webflow.getName().equals(webflowName)) {
582                 webflowIndex = i;
583             }
584         }
585 
586         return webflowIndex;
587     }
588 
589     /***
590      * Removes an object form the navigation manager.
591      *
592      * @param objectKeySet the object key set
593      */
594     protected void removeObjectSet(Set objectKeySet) {
595         if (objectKeySet != null) {
596             for (Iterator i = objectKeySet.iterator(); i.hasNext();) {
597                 String objectKey = (String) i.next();
598                 ObjectSetOwner objectSetOwner = (ObjectSetOwner) object2owner.get(objectKey);
599 
600                 if (objectSetOwner != null) {
601                     object2owner.remove(objectKey);
602                     objectSetOwner.getOwnedObjectSet().remove(objectKey);
603                 }
604             }
605         }
606     }
607 
608     /***
609      * Removes from a container a set of object.
610      *
611      * @param container the container
612      * @param objectSet the set of object to be removed
613      */
614     protected static void removeObjectSetFromContainer(AttributeContainer container, Set objectSet) {
615         if (Config.getInstance().isEnabled() && Config.getInstance().isCleanerEnabled() && (objectSet != null)) {
616             for (Iterator i = objectSet.iterator(); i.hasNext();) {
617                 String objectKey = (String) i.next();
618                 container.removeAttributeValue(objectKey);
619             }
620 
621             if (log.isInfoEnabled()) {
622                 log.info("Removed from container object set " + objectSet.toString());
623             }
624         }
625     }
626 }