View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.model;
20  
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import javax.faces.context.ExternalContext;
25  import javax.faces.context.FacesContext;
26  
27  /**
28    *  <p>
29    *         There are two common scenarios for processes,
30    *         "Plus One" and "Max Visited" which are explained below.
31    *           <ul>
32    *             <li>"Plus One" - from the current step the user can
33    *             navigate to any previous page and the next page.
34    *             If the user is on the 5th step of a process
35    *             and goes back to step 2, then the user can only
36    *             navigate from step 2 to step 1 and step 3.
37    *             </li>
38    *             <li>"Max Visited" - the user can navigate to the max
39    *             visited page. If the user is currently on the max
40    *             visited page then the user can also navigate to
41    *             the next page. If the user is on the 5th step of a
42    *             process and goes back to step 2, then the user can
43    *             navigate from step 2 to steps 1, 2, 3, 4, and 5.
44    *             </li>
45    *           </ul>
46    *
47    *           </p>
48    *           <p>
49    *           A node in a process should be readOnly
50    *           if that step of the process is not reachable from the current
51    *           step.
52    *           </p>
53    *           <p>
54    *           A node in a process should be immediate if the values
55    *           in the current step don't need to be validated.
56    *           </p>
57    *           <p>
58   */
59  public class ProcessUtils
60  {
61  
62  //**********************************
63  // Plus One Process methods
64  //**********************************
65   /**
66     *
67    *           <p>
68    *           Determines immediate for a "plus one" process.
69    *           Immediate will be true for any previous step, and false otherwise.
70    *           For example if the user is on step 5
71    *           and goes back to step 2, the user will have to come back
72    *           to step 5 again, so the fields on page 5 don't need to
73    *           be validated when going back to steps 1,2,3,4, but should be
74    *           validated when going to step 6.
75    *           </p>
76    *
77    *
78     * @param model the model instance to use.
79     * When the model is passed in a call to model.getPath should return the
80     * path for the "current" node.
81     * @param defaultReturnValue if the current and focus nodes aren't
82     * siblings in the tree, this value will be returned.
83     * @return whether or not the current node should be immediate.
84     */
85    public static boolean isImmediate(
86      MenuModel model,
87      boolean   defaultReturnValue
88      )
89    {
90      Object focusPath = model.getFocusRowKey();
91      Object currPath = model.getRowKey();
92      boolean returnDefault = _hasDifferentAncestors(model,
93                                                     focusPath,
94                                                     currPath);
95  
96      if (returnDefault)
97      {
98        model.setRowKey(currPath);
99        return defaultReturnValue;
100     }
101 
102     model.setRowKey(focusPath);
103     int focusIndex = model.getRowIndex();
104     model.setRowKey(currPath);
105     int currIndex = model.getRowIndex();
106 
107     if (currIndex < focusIndex)
108       return true;
109 
110     return false;
111   }
112 
113 
114   /**
115   *           <p>
116   *           Determines readOnly for a "plus one" process.
117   *           ReadOnly will be true for any step past the next available step
118   *         </p>
119    * @param model the model instance to use.
120    * When the model is passed in a call to model.getPath should return the
121    * path for the "current" node.
122    * @param defaultReturnValue if the current and focus nodes aren't
123    * siblings in the tree, this value will be returned.
124    * @return whether or not the current node should be immediate.
125    */
126   public static boolean isReadOnly(
127     MenuModel model,
128     boolean   defaultReturnValue
129     )
130   {
131     Object focusPath = model.getFocusRowKey();
132     Object currPath = model.getRowKey();
133     boolean returnDefault = _hasDifferentAncestors(model,
134                                                    focusPath,
135                                                    currPath);
136 
137     if (returnDefault)
138     {
139       model.setRowKey(currPath);
140       return defaultReturnValue;
141     }
142 
143 
144     if (focusPath.equals(currPath))
145     {
146       model.setRowKey(currPath);
147       return true;
148     }
149 
150     model.setRowKey(focusPath);
151     int focusIndex = model.getRowIndex();
152     model.setRowKey(currPath);
153     int currIndex = model.getRowIndex();
154 
155     if (currIndex > (focusIndex + 1))
156       return true;
157 
158     return false;
159   }
160 
161   private static boolean _hasDifferentAncestors(
162     MenuModel model,
163     Object    key1,
164     Object    key2
165   )
166   {
167       // if either key is null return true
168     if ( key1 == null || key2 == null)
169       return true;
170 
171     int depth = model.getDepth(key2);
172     if (  depth != model.getDepth(key1))
173     {
174       return true;
175     }
176     if (depth > 0)
177     {
178 
179       model.setRowKey(key1);
180       Object key1ParentPath = model.getContainerRowKey();
181 
182       model.setRowKey(key2);
183       Object key2ParentPath = model.getContainerRowKey();
184 
185       if (!key2ParentPath.equals(key1ParentPath))
186       {
187         return true;
188       }
189     }
190 
191     return false;
192   }
193 
194 
195 
196 //**********************************
197 // Max process methods
198 //**********************************
199  /**
200   *  <p>
201   *   Determines immediate for a "max visited" process.
202   *   When the current step and the max step are the same
203   *   immediate will be true for any previous step, and false otherwise.
204   *   If the current step is before the max step,
205   *   immediate will be false.
206   *  </p>
207    * @param model the model instance to use.
208    * When the model is passed in a call to model.getRowKey should return the
209    * rowKey for the "current" node.
210    * @param maxVisitedRowKey the rowKey to use to determine the max node
211    * @param defaultReturnValue if the current, max, and focus nodes aren't
212    * siblings in the tree, this value will be returned.
213    * @return whether or not the current node should be immediate.
214    */
215   public static boolean isImmediate(
216     MenuModel model,
217     Object    maxVisitedRowKey,
218     boolean   defaultReturnValue
219     )
220   {
221     Object focusPath = model.getFocusRowKey();
222     Object currPath = model.getRowKey();
223 
224     boolean returnDefault = _hasDifferentAncestors(model,
225                                                    focusPath,
226                                                    currPath,
227                                                    maxVisitedRowKey);
228 
229     if (returnDefault)
230     {
231       model.setRowKey(currPath);
232       return defaultReturnValue;
233     }
234 
235 
236     model.setRowKey(focusPath);
237     int focusIndex = model.getRowIndex();
238     model.setRowKey(maxVisitedRowKey);
239     int maxIndex = model.getRowIndex();
240     model.setRowKey(currPath);
241     int currIndex = model.getRowIndex();
242 
243     if (maxIndex == focusIndex &&
244         currIndex < focusIndex)
245       return true;
246 
247     return false;
248   }
249 
250  /**
251   *  <p>
252   *   Determines readOnly for a "max visited" process.
253   *   When the current step and the max step are the same,
254   *   readOnly will be true for any step past the next available step.
255   *   If the current step is before the max step,
256   *   then readOnly will be true for any step past the max step.
257   *  </p>
258    * @param model the model instance to use.
259    * When the model is passed in a call to model.getRowKey should return the
260    * rowKey for the "current" node.
261    * @param maxVisitedRowKey the rowKey to use to determine the max node
262    * @param defaultReturnValue if the current, max, and focus nodes aren't
263    * siblings in the tree, this value will be returned.
264    * @return whether or not the current node should be immediate.
265    */
266   public static boolean isReadOnly(
267     MenuModel model,
268     Object    maxVisitedRowKey,
269     boolean   defaultReturnValue
270     )
271   {
272     Object focusPath = model.getFocusRowKey();
273     Object currPath = model.getRowKey();
274 
275     boolean returnDefault = _hasDifferentAncestors(model,
276                                                    focusPath,
277                                                    currPath,
278                                                    maxVisitedRowKey);
279 
280     if (returnDefault)
281     {
282       model.setRowKey(currPath);
283       return defaultReturnValue;
284     }
285 
286     if (focusPath.equals(currPath))
287     {
288       model.setRowKey(currPath);
289       return true;
290     }
291 
292     model.setRowKey(focusPath);
293     int focusIndex = model.getRowIndex();
294 
295     model.setRowKey(maxVisitedRowKey);
296     int maxIndex = model.getRowIndex();
297 
298     if (maxIndex == focusIndex)
299       maxIndex = maxIndex + 1;
300 
301     model.setRowKey(currPath);
302     int currIndex = model.getRowIndex();
303 
304 
305     if (currIndex > maxIndex)
306       return true;
307 
308     return false;
309   }
310   
311   
312   // Plus One case
313 
314   /**
315    * For the Plus One case, a stop is considered visited if, 
316    * - it's before the current or,
317    * - is the current stop itself
318    * 
319    * @param model the menuModel instance to use. When the model is passed in, 
320    *  a call to model.getRowKey should return the rowKey for the "current" node.
321    * @param defaultReturnValue if the current and focus nodes aren't
322    *  siblings in the tree, this value will be returned.
323    * @return whether or not the current node has been visited.
324    */
325   public static boolean isVisited(
326     MenuModel model,
327     boolean   defaultReturnValue
328 )
329   {
330     Object focusPath = model.getFocusRowKey();
331     Object currPath = model.getRowKey();
332   
333     boolean returnDefault = _hasDifferentAncestors (model, 
334                                                     focusPath,
335                                                     currPath);
336                                                     
337     if (returnDefault)
338     {
339       model.setRowKey(currPath);
340       return defaultReturnValue;
341     }
342                            
343     // current node is active
344     if (focusPath.equals(currPath))
345     {
346       model.setRowKey(currPath);
347       return true;
348     }
349     
350     // nodes before the current node are visited
351     model.setRowKey(focusPath);
352     int focusIndex = model.getRowIndex();
353     model.setRowKey(currPath);
354     int currIndex = model.getRowIndex();
355 
356     if (currIndex <= focusIndex)
357       return true;
358 
359     return false;
360   }
361 
362   /**
363    * For the Max Visited case, a stop is considered visited if
364    *   - a stop is before the active stop
365    *   - or is the active stop
366    *   
367    * @param model the menuModel instance to use. When the model is passed in, a 
368    *  call to model.getRowKey should return the rowKey for the "current" node.
369    * @param maxVisitedRowKey the rowKey to use to determine the max visited node
370    * @param defaultReturnValue if the current, maxVisited and focus nodes aren't
371    *  siblings in the tree, this value will be returned.
372    * @return whether or not the current node has been visited.
373    */
374   public static boolean isVisited (
375     MenuModel model,
376     Object maxVisitedRowKey,
377     boolean defaultReturnValue)
378   {
379     Object focusPath = model.getFocusRowKey();
380     Object currPath = model.getRowKey();
381 
382     boolean returnDefault = _hasDifferentAncestors(model,
383                                                    focusPath,
384                                                    currPath,
385                                                    maxVisitedRowKey);
386 
387     if (returnDefault)
388     {
389       model.setRowKey(currPath);
390       return defaultReturnValue;
391     }
392 
393     // on active node
394     if (focusPath.equals(currPath))
395     {
396       model.setRowKey(currPath);
397       return true;
398     }
399 
400     model.setRowKey(maxVisitedRowKey);
401     int maxIndex = model.getRowIndex();
402 
403     model.setRowKey(currPath);
404     int currIndex = model.getRowIndex();
405 
406     if (currIndex <= maxIndex)
407       return true;
408 
409     return false;
410   }
411 
412   /**
413    * Get the rowKey of the max visited node in the respective process.
414    * If null set maxVisitedRowKey to be the focus rowKey.
415    * If set but the focus rowKey is after maxVisitedRowKey,
416    * set maxVisitedRowKey to the focus rowKey.
417    *
418    * @param model  the model instance to use.
419    * When the model is passed in a call to model.getRowKey should return the
420    * key for the "current" node.
421    * @param maxPathKey this key is used to store the maxVisitedRowKey in both the
422    * session and request map.
423    * @return the rowKey to the "max visited" node.
424    */
425   @SuppressWarnings("unchecked")
426   public static Object getMaxVisitedRowKey(
427     MenuModel model,
428     String    maxPathKey
429   )
430   {
431     assert(maxPathKey != null);
432     
433     ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
434     Map<String, Object> requestMap = externalContext.getRequestMap();
435 
436     Object maxRowKey = requestMap.get(maxPathKey);
437     boolean mustCompareWithFocusRow = true;
438 
439     //TODO - if I change this to use pageFlowScope it doesn't work.
440     // figure out why.
441     Map<String,Object> sessionMap  = externalContext.getSessionMap();
442     Map<Object,Object> maxPathMap = (Map<Object,Object>)sessionMap.get(maxPathKey);
443     if (maxPathMap == null)
444       maxPathMap = new HashMap<Object,Object>();
445 
446     Object focusRowKey = model.getFocusRowKey();
447     Object parentRowKey = model.getContainerRowKey(focusRowKey);
448 
449     if (parentRowKey == null)
450       parentRowKey = _MAX_PATH_TOP;
451 
452     if (maxRowKey == null)
453     {
454       maxRowKey = maxPathMap.get(parentRowKey);
455       if (maxRowKey == null)
456       {
457         maxRowKey = focusRowKey;
458         maxPathMap.put(parentRowKey, maxRowKey);
459         sessionMap.put(maxPathKey, maxPathMap);
460         
461         // for fast access store maxRowKey in the requestMap
462         requestMap.put(maxPathKey, maxRowKey);
463         mustCompareWithFocusRow = false;
464       }
465     }
466     
467     if (mustCompareWithFocusRow)
468     {
469       // figure out if focusRowKey > maxPath, if so set a new maxPath,
470       // the result is set on the request so we do this once per request.
471       Object currPath = model.getRowKey();
472       boolean hasDifferentAncestors = _hasDifferentAncestors(model,
473                                                    focusRowKey,
474                                                    maxRowKey);
475 
476       //TODO - should I default this to focusPath?
477       if (hasDifferentAncestors)
478       {
479         model.setRowKey(currPath);
480         return null;
481       }
482 
483       model.setRowKey(focusRowKey);
484       int focusRowIndex = model.getRowIndex();
485       model.setRowKey(maxRowKey);
486       int maxRowIndex = model.getRowIndex();
487 
488       if (focusRowIndex > maxRowIndex)
489       {
490         maxPathMap.put(parentRowKey, focusRowKey);
491         sessionMap.put(maxPathKey, maxPathMap);
492         requestMap.put(maxPathKey, focusRowKey);
493       }
494       else
495       {
496         requestMap.put(maxPathKey, maxRowKey);
497       }
498 
499       model.setRowKey(currPath);
500     }
501 
502     return maxRowKey;
503   }
504 
505   @SuppressWarnings("unchecked")
506   public static void clearMaxPath(
507     String maxPathKey
508   )
509   {
510     if (maxPathKey != null)
511     {
512       Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
513       sessionMap.put(maxPathKey, null);
514     }
515   }
516 
517 
518   private static boolean _hasDifferentAncestors(
519     MenuModel model,
520     Object    key1,
521     Object    key2,
522     Object    key3
523   )
524   {
525     boolean diff1 = _hasDifferentAncestors(model, key1, key2);
526 
527     if (diff1)
528       return true;
529 
530     boolean diff2 = _hasDifferentAncestors(model, key1, key3);
531 
532     if ( diff2)
533       return true;
534 
535     return false;
536 
537   }
538 
539 
540 
541   private static final Object _MAX_PATH_TOP = new Object();
542 
543 }
544