1
2
3
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
306 backToWebflow(webflow.getName(), objectSetToRemove, container);
307
308 webflow.backToPage(url, objectSetToRemove, url2webflow, container);
309 } else {
310
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 }