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.io.InputStream;
22  import java.io.Serializable;
23  
24  import java.net.URL;
25  
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.concurrent.atomic.AtomicInteger;
33  
34  import javax.el.ELContext;
35  import javax.el.ELResolver;
36  import javax.el.PropertyNotFoundException;
37  
38  import javax.faces.context.ExternalContext;
39  import javax.faces.context.FacesContext;
40  
41  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
42  import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
43  import org.apache.myfaces.trinidad.util.ContainerUtils;
44  import org.apache.myfaces.trinidad.util.TransientHolder;
45  
46  
47  /**
48   * Creates a Menu Model from a TreeModel where nodes in the treeModel
49   * contain viewId information.
50   * <p>
51   * Each node must have either a bean getter method or a Map property
52   * that returns a viewId. There are several restrictions on the data:
53   * <ul>
54   * o The nodes in the tree must either be all beans or all maps,
55   * but not a mix of beans and maps.
56   * o The viewId of a node can be null, but if set it must be unique.
57   * o The tree cannot be mutable.
58   * </ul>
59   * <p>
60   * The getFocusRowKey method
61   * <ul>
62   * o gets the current viewId by calling
63   * FacesContext.getCurrentInstance().getViewRoot().getViewId()
64   * o compares the current viewId with the viewId's in the viewIdFocusPathMap
65   * that was built by traversing the tree when the model was created.
66   * o returns the focus path to the node with the current viewId or null if the
67   * current viewId can't be found.
68   * o in the case where a viewId has multiple focus paths, the currently
69   * selected node is used as a key into the nodeFocusPathMap to return the
70   * correct focus path.
71   * </ul>
72   * <p>
73   * The Model is created by specifying it in the faces-config.xml file
74   * as follows
75   * <pre>
76   *   &lt;managed-bean&gt;
77   *    &lt;managed-bean-name&gt;hr_menu&lt;/managed-bean-name&gt;
78   *    &lt;managed-bean-class&gt;
79   *      org.apache.myfaces.trinidad.model.XMLMenuModel
80   *    &lt;/managed-bean-class&gt;
81   *    &lt;managed-bean-scope&gt;request&lt;/managed-bean-scope&gt;
82   *    &lt;managed-property&gt;
83   *      &lt;property-name&gt;source&lt;/property-name&gt;
84   *      &lt;property-class&gt;java.lang.String&lt;/property-class&gt;
85   *      &lt;value&gt;/WEB-INF/hr-menu.xml&lt;/value&gt;
86   *    &lt;/managed-property&gt;
87   *  &lt;/managed-bean&gt;
88   * </pre>
89   *
90   * Objects of this class are not thread safe and should be used
91   * only in request scope.
92   *
93   */
94  
95  /*
96   * Three hashmaps are also created in order to be able to resolve cases where
97   * multiple menu items cause navigation to the same viewId.  All 3 of these maps
98   * are created after the metadata is parsed and the tree is built, in the
99   * MenuContentHandlerImpl.
100  *
101  * o The first hashMap is called the viewIdFocusPathMap and is built by
102  * traversing the tree when the model is created.  Each node's focusViewId is
103  * obtained and used as the key to an entry in the viewIdHashMap.  An ArrayList
104  * is used as the entry's value and each item in the ArrayList is a node's
105  * rowkey from the tree. This allows us to have duplicate rowkeys for a single
106  * focusViewId which translates to a menu that contains multiple items pointing
107  * to the same page. In general, each entry will have an ArrayList of rowkeys
108  * with only 1 rowkey, AKA focus path.
109  * o The second hashMap is called the nodeFocusPathMap and is built at the
110  * same time the viewIdHashMap is built. Each entry's key is the actual node and
111  * the value is the row key.  Since the model keeps track of the currently
112  * selected menu node, this hashmap can be used to resolve viewId's with
113  * multiple focus paths.  Since we have the currently selected node, we just
114  * use this hashMap to get its focus path.
115  * o The third hashMap is called idNodeMap and is built at the same time as the
116  * previous maps.  This map is populated by having each entry contain the node's
117  * id as the key and the actual node as the value.  In order to keep track of
118  * the currently selected node in the case of a GET, the node's id is appended
119  * to the request URL as a parameter.  The currently selected node's id is
120  * picked up and this map is used to get the actual node that is currently
121  * selected.
122  *
123  * Keeping track of the currently selected menu item/node.
124  *
125  * If an itemNode in the metadata uses its "action" attribute, a POST is done
126  * and the node's "doAction" method is called when the menu item is clicked. At
127  * that time, the model is notified through its setCurrentlyPostedNode() method,
128  * where the current node is set and the request method is set to POST.
129  *
130  * If an itemNode in the metadata uses its "destination" attribute, a GET is
131  * done.  Nothing is called on the model when the menu item is clicked.  However
132  * at the time the page is rendered the "getDestination" method for all nodes
133  * using the "destination" attribute is called.  At this point
134  * we append the node's id to the value of the destination attribute URL, as
135  * a parameter, and return it. So when getFocusRowKey() is called, we get the
136  * request the node's parameter matching the currently selected node's id.
137  * Using the node id, we find the matching node in the idNodeMap and voila, we
138  * have the currently selected node!
139  */
140 public class XMLMenuModel extends BaseMenuModel
141                           
142 {
143   public XMLMenuModel()
144   {
145     super();
146     _modelId = Integer.valueOf(System.identityHashCode(this)).toString();
147   }
148   
149   /**
150    * This needs to be overriden by classes extending XmlMenuModel and using APIs for the nodes
151    * of XmlMenuModel. Default value returned is true for backward compatibilty. The models using
152    * the external APIs for their nodes must return false.
153    * @return boolean
154    */
155   protected boolean isCompatibilityMode()
156   {
157     return true;
158   }
159 
160   /**
161    * setSource - specifies the XML metadata and creates
162    * the XML Menu Model.
163    *
164    * @param menuMetadataUri - String URI to the XML metadata.
165    */
166   public void setSource(String menuMetadataUri)
167   {
168     if (menuMetadataUri == null || "".equals(menuMetadataUri))
169       return;
170 
171     _mdSource = menuMetadataUri;
172     _createModel();
173   }
174 
175   /**
176    * Makes the TreeModel part of the menu model.  Also creates the
177    * _viewIdFocusPathMap, _nodeFocusPathMap, and idNodeMaps.
178    *
179    * @param data The Tree Model instance
180    */
181   @Override
182   public void setWrappedData(Object data)
183   {
184     super.setWrappedData(data);
185 
186     // The only thing the child menu models are needed for are their
187     // menuLists, which get incorporated into the Root Model's tree.
188     // There is no need to create the hashmaps or anything
189     // on the child menu models.  A lot of overhead (performance and
190     // memory) would be wasted.
191     if (_isRoot)
192     {
193       _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(_mdSource);
194       _nodeFocusPathMap   = _contentHandler.getNodeFocusPathMap(_mdSource);
195       _idNodeMap          = _contentHandler.getIdNodeMap(_mdSource);
196     }
197   }
198 
199   /**
200    * Returns the rowKey to the current viewId, or in the case of where the
201    * model has nodes with duplicate viewId's and one is encountered, we
202    * return the rowKey of the currently selected node.
203    * <p>
204    *
205    * The getFocusRowKey method
206    * <ul>
207    * <li>gets the current viewId by calling
208    * FacesContext.getCurrentInstance().getViewRoot().getViewId()
209    * <li>compares the current viewId with the viewId's in the viewIdFocusPathMap
210    * that was built by traversing the tree when the model was created.
211    * <li>returns the focus path to the node with the current viewId or null if
212    * the current viewId can't be found.
213    * <li>in the case where a viewId has multiple focus paths, the currently
214    * selected node is used as a key into the nodeFocusPathMap to return the
215    * correct focus path.
216    * </ul>
217    *
218    * @return  the rowKey to the node with the current viewId or null if the
219    * current viewId can't be found.
220    */
221   @SuppressWarnings("unchecked")
222   @Override
223   public Object getFocusRowKey()
224   {
225     Object focusPath        = null;
226     String currentViewId    = _getCurrentViewId();
227     FacesContext context    = FacesContext.getCurrentInstance();
228 
229     // getFocusRowKey() is called multiple times during the Process Validations
230     // Phase and again during the Render Response Phase during each Request.
231     // During each phase, as described below, the same viewId is passed in. To
232     // prevent unnecessary looking up of the focus path each time, the previous
233     // focus path is returned after the first call in each phase, as described
234     // below.
235     //
236     // ** Process Validations Phase:
237     // During the Process Validations Phase, the prevViewId is initially
238     // null (gets set to this at the start of each Request). The first time
239     // getFocusRowKey is called, the currentViewId is that of the node we are
240     // navigating "from" during the Request.  This is stored in prevViewId.
241     // Because the currentViewId is not equal to the prevViewId,
242     // the node is looked up, its focus path stored in prevFocusPath, and the
243     // focus path is returned. On subsequent calls during the Process
244     // Validations Phase, the currentViewId is always that of the "from" node,
245     // the currentViewId is equal to the prevViewId, and so we simply
246     // return prevFocusPath.
247     //
248     // ** Render Response Phase:
249     // During the Render Response Phase, the prevViewId is initially
250     // that of the "from" node. The first time getFocusRowKey is called
251     // the currentViewId is that of the node we are navigating "to" during
252     // the Request.  This is stored in prevViewId.
253     // Because the currentViewId is not equal to the prevViewId,
254     // the node is looked up, its focus path stored in prevFocusPath, and the
255     // focus path is returned. On subsequent calls during the Render
256     // Response Phase, the currentViewId is always that of the "to" node,
257     // the currentViewId is equal to the prevViewId, and so we simply
258     // return prevFocusPath.
259     //
260     // IMPORTANT: Code that returns the correct focus path for duplicate nodes
261     // in the node tree actually depends on this optimization.
262     //
263     if ((_prevViewId != null) && _prevViewId.equals(currentViewId))
264       return _prevFocusPath;
265 
266     // Initializations
267     _prevViewId    = currentViewId;
268 
269     // How did we get to this page?
270     // 1) Clicked on a menu item with its action attribute set.  This does
271     //    a POST.
272     // 2) Clicked on a menu item with its destination attribute set.  This
273     //    does a GET.
274     // 3) Navigation to a viewId within our model but done from outside the
275     //    model.  Examples, button, text link, etc.
276     //
277 
278     // Case 1: POST method.  Current Node has already been set and so has the
279     // request method.  The doAction() method of the clicked node calls
280     // the setCurrentlyPostedNode() method of this model, which sets both. So
281     // we have nothing to do in this case.
282 
283     if (_getRequestMethod() != _METHOD_POST)
284     {
285       // Case 2: GET method.  We have hung the selected node's id off the
286       // request's URL, which enables us to get the selected node and also
287       // to know that the request method is GET.
288       Map<String, String> paramMap =
289         context.getExternalContext().getRequestParameterMap();
290       String UrlNodeId = paramMap.get(_NODE_ID_PROPERTY);
291 
292       if (UrlNodeId != null)
293       {
294         _setCurrentlySelectedNode(_getNodeFromURLParams(UrlNodeId));
295         _setRequestMethod(_METHOD_GET);
296       }
297     }
298 
299     // Case 3: Navigation to a page within the model from an outside
300     // method, e.g. button, link text, etc.  In this case we set the
301     // currently selected node to null.  This tells us to get the 0th
302     // element of the ArrayList returned from the viewId hashMap.  This
303     // should be a focus path match to the node whose "defaultFocusPath"
304     // attribute was set to 'true'.
305     if (_getRequestMethod() == _METHOD_NONE)
306     {
307       _setCurrentlySelectedNode(null);
308     }
309 
310     // Get the matching focus path ArrayList for the currentViewId.
311     // This is an ArrayList because our map allows nodes with the same
312     // viewId, that is, different focus paths to the same viewId.
313     ArrayList<Object> fpArrayList =
314      (ArrayList<Object>) _viewIdFocusPathMap.get(currentViewId);
315 
316     if (fpArrayList != null)
317     {
318       if (_prevRequestNode != null)
319       {
320         // _prevRequestNode will only be non-null when the previous
321         // request's node (AKA the "from" node) was a duplicate.
322         // We need to return the correct focus path, so we must
323         // use the node.  If we don't do this, the wrong focus path
324         // could be returned from the _viewIdFocusPathMap.
325         focusPath = _nodeFocusPathMap.get(_prevRequestNode);
326 
327         // Reset it to null
328         _prevRequestNode = null;
329       }
330       else
331       {
332         // Get the currently selected node
333         Object currentNode = _getCurrentlySelectedNode();
334 
335         if (fpArrayList.size() == 1 || currentNode == null)
336         {
337           // For fpArrayLists with multiple focusPaths,
338           // the 0th entry in the fpArrayList carries the
339           // focusPath of the node with its defaultFocusPath
340           // attribute set to "true", if there is one.  If
341           // not, the 0th element is the default.
342           focusPath = fpArrayList.get(0);
343 
344           // Not a duplicate node so set this to null
345           _prevRequestNode = null;
346         }
347         else
348         {
349           // This will be a duplicate node, meaning it navigates to the
350           // the same page as at least one other node in the tree.
351           focusPath = _nodeFocusPathMap.get(currentNode);
352 
353           // Save this node for the next request.  Otherwise, the
354           // next Request will go into previous part of this conditional
355           // and return (possibly) the wrong focus path during the
356           // Process Validations Phase.
357           _prevRequestNode = currentNode;
358         }
359       }
360     }
361 
362     // Save all pertinent information
363     _prevFocusPath = focusPath;
364 
365     // Reset this to _METHOD_NONE so we will know when
366     // Navigation to a viewId within our model has been
367     // done from outside the model, e.g. link, button.
368     // If this is not done, the current request method
369     // will be from the previous navigation and could
370     // be incorrrect.  We always reset it to _METHOD_NONE
371     // so that the correct navigation method (see comment at top
372     // of getFocusRowKey() ) is determined each time.
373     _setRequestMethod(_METHOD_NONE);
374 
375     return focusPath;
376   }
377 
378 
379   /**
380    * Gets the URI to the XML menu metadata.
381    *
382    * @return String URI to the XML menu metadata.
383    */
384   public String getSource()
385   {
386     return _mdSource;
387   }
388 
389   /**
390    * Sets the boolean value that determines whether or not to create
391    * nodes whose rendered attribute value is false.  The default
392    * value is false.
393    *
394    * This is set through a managed property of the XMLMenuModel
395    * managed bean -- typically in the faces-config.xml file for
396    * a faces application.
397    */
398   public void setCreateHiddenNodes(boolean createHiddenNodes)
399   {
400     _createHiddenNodes = createHiddenNodes;
401   }
402 
403   /**
404    * Gets the boolean value that determines whether or not to create
405    * nodes whose rendered attribute value is false.  The default
406    * value is false.
407    *
408    * This is called by the contentHandler when parsing the XML metadata
409    * for each node.
410    *
411    * @return the boolean value that determines whether or not to create
412    * nodes whose rendered attribute value is false.
413    */
414   public boolean getCreateHiddenNodes()
415   {
416     return _createHiddenNodes;
417   }
418 
419 
420   /**
421    * Maps the focusPath returned when the viewId is newViewId
422    * to the focusPath returned when the viewId is aliasedViewId.
423    * This allows view id's not in the treeModel to be mapped
424    * to a focusPath.
425    *
426    * @param newViewId the view id to add a focus path for.
427    * @param aliasedViewId the view id to use to get the focusPath to use
428    *        for newViewId.
429    */
430   @SuppressWarnings("unchecked")
431   public void addViewId(String newViewId, String aliasedViewId)
432   {
433     List<Object> focusPath =
434       _viewIdFocusPathMap.get(aliasedViewId);
435 
436     if (focusPath != null)
437     {
438       _viewIdFocusPathMap.put(newViewId, focusPath);
439     }
440   }
441 
442   /**
443    * Sets the currently selected node and the request method.
444    * This is called by a selected node's doAction method.  This
445    * menu node must have had its "action" attribute set, thus the
446    * method is POST.
447    *
448    * @param currentNode  The currently selected node in the menu
449    */
450   public void setCurrentlyPostedNode(Object currentNode)
451   {
452     _setCurrentlySelectedNode(currentNode);
453     _setRequestMethod(_METHOD_POST);
454 
455 
456     // Do this in the case where a menu item is selected
457     // that has the same viewId as the previous menu item
458     // that is selected.  If not, the test at the beginning
459     // of getFocusRowKey() (currentViewId == _prevViewId)
460     // is true and just returns, even though we have selected
461     // a new node and the focus path should change.
462     _prevViewId = null;
463   }
464 
465   /**
466    * Get a the MenuNode corresponding to the key "id" from the
467    * node id hashmap.
468    *
469    * @param id - String node id key for the hashmap entry.
470    * @return The Node Object that corresponds to id.
471    */
472   public Object getNode (String id)
473   {
474     XMLMenuModel rootModel = _getRootModel();
475     Map<String, Object> idNodeMap = rootModel._getIdNodeMap();
476 
477     if (idNodeMap == null)
478       return null;
479 
480     // This needs to be public because the nodes call into this map
481     return idNodeMap.get(id);
482   }
483 
484   /**
485    * Gets the list of custom properties from the node
486    * and returns the value of propName.  Node must be an itemNode.
487    * If it is not an itemNode, the node will not have any custom
488    * properties and null will be returned.
489    *
490    * @param node Object used to get its list of custom properties
491    * @param propName String name of the property whose value is desired
492    *
493    * @return Object value of propName for Object node.
494    */
495   @SuppressWarnings("unchecked")
496   public Object getCustomProperty(Object node, String propName)
497   {
498     if (node == null)
499       return null;
500 
501     FacesContext context = FacesContext.getCurrentInstance();
502     ELContext elContext  = context.getELContext();
503     ELResolver resolver  = elContext.getELResolver();
504     String value         = null;
505 
506     try
507     {
508       Map<String, String> propMap =
509         (Map<String, String>) resolver.getValue(elContext,
510                                                 node, _CUSTOM_ATTR_LIST);
511 
512       // Need to check to see if propMap is null.  If there are
513       // no custom properties for this itemNode, there will be
514       // no propMap.  See MenuContentHandler._createItemNode().
515       if (propMap == null)
516         return null;
517 
518       value = propMap.get(propName);
519     }
520     catch (PropertyNotFoundException ex)
521     {
522       // if the node is not an itemNode, the node
523       // has no custom properties, so we simply
524       // return null
525       return null;
526     }
527 
528     // If it is an EL expression, we must evaluate it
529     // and return its value
530     if (   value != null
531         && ContainerUtils.isValueReference(value)
532        )
533      {
534        Object elValue = null;
535 
536        try
537        {
538          elValue = context.getApplication().evaluateExpressionGet(context,
539                                                                   value,
540                                                                   Object.class);
541        }
542        catch (Exception ex)
543        {
544          _LOG.warning("INVALID_EL_EXPRESSION", value);
545          _LOG.warning(ex);
546          return null;
547        }
548        return elValue;
549      }
550 
551     return value;
552   }
553 
554   /**
555    * getStream - Opens an InputStream to the provided URI.
556    *
557    * @param uri - String uri to a data source.
558    * @return InputStream to the data source.
559    */
560   public InputStream getStream(String uri)
561   {
562     // This is public so that extended menu models can override
563     // and provide their own InputStream metadata source.
564     // And it is called by the MenuContentHandlerImpl.
565     try
566     {
567       // Open the metadata
568       FacesContext context = FacesContext.getCurrentInstance();
569       URL url = context.getExternalContext().getResource(uri);
570       return url.openStream();
571     }
572     catch (Exception ex)
573     {
574       _LOG.severe("OPEN_URI_EXCEPTION", uri);
575       _LOG.severe(ex);
576       return null;
577     }
578   }
579 
580   /**
581    * Get the Model's viewIdFocusPathMap
582    *
583    * @return the Model's viewIdFocusPathMap
584    */
585   public Map<String, List<Object>> getViewIdFocusPathMap()
586   {
587     if (!_isRoot || _contentHandler == null)
588       return null;
589 
590     if (_viewIdFocusPathMap == null)
591       _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(_mdSource);
592 
593     return _viewIdFocusPathMap;
594   }
595 
596   /**
597  * Returns the map of content handlers
598  * which hold the state of one XML tree.
599  * @param scopeMap
600  * @return
601  */
602   protected Map<Object, List<MenuContentHandler> > getContentHandlerMap()
603   {
604     FacesContext facesContext = FacesContext.getCurrentInstance();
605     ExternalContext externalContext = facesContext.getExternalContext();
606     Map<String, Object> scopeMap =
607         externalContext.getApplicationMap();
608    Object lock  = externalContext.getContext();
609    
610    // cannot use double checked lock here as
611    // we cannot mark the reference as volatile
612    // therefore any reads should happen inside
613    // a synchronized block.
614    synchronized (lock)
615    {
616      TransientHolder<Map<Object, List<MenuContentHandler> >> holder =  
617        (TransientHolder<Map<Object, List<MenuContentHandler> >>) scopeMap.get(_CACHED_MODELS_KEY);
618       Map<Object, List<MenuContentHandler>> contentHandlerMap = (holder != null) ? holder.getValue() : null;
619       if (contentHandlerMap == null)
620       {
621         contentHandlerMap =
622             new ConcurrentHashMap<Object, List<MenuContentHandler>>();
623         scopeMap.put(_CACHED_MODELS_KEY, TransientHolder.newTransientHolder( contentHandlerMap) );
624         scopeMap.put(_CACHED_MODELS_ID_CNTR_KEY,new AtomicInteger(-1));
625       }
626       return contentHandlerMap;
627     }
628     
629   }
630   
631   protected int getContentHandlerId()
632   {
633     FacesContext facesContext = FacesContext.getCurrentInstance();
634     ExternalContext externalContext = facesContext.getExternalContext();
635     Map<String, Object> scopeMap =
636         externalContext.getApplicationMap();
637     AtomicInteger counter = (AtomicInteger) scopeMap.get(_CACHED_MODELS_ID_CNTR_KEY);
638     return counter.getAndIncrement();
639   }
640   protected Object getCacheKey()
641   {
642     return _mdSource;
643   }
644   
645   /* ====================================================================
646    * Private Methods
647    * ==================================================================== */
648 
649   private  Map<String, Object> _getIdNodeMap()
650   {
651     return (_isRoot) ? _idNodeMap : null;
652   }
653 
654   /**
655    * Get a the MenuNode corresponding to the key "id" from the
656    * node id hashmap.
657    *
658    * @param id - String node id key for the hashmap entry.
659    * @return The MenuNode that corresponds to id.
660    */
661   private Object _getNodeFromURLParams (String urlNodeId)
662   {
663     // This needs to be public because the nodes call into this map
664     return _idNodeMap.get(urlNodeId);
665   }
666 
667   /**
668     * Creates a menu model based on the menu metadata Uri.
669     * This is accomplished by:
670     * <ol>
671     * <li> Get the MenuContentHandlerImpl through the Services API.
672     * <li> Set the root model and current model on the content handler, which,
673     * in turn, sets the models on each of the nodes.
674     * <li> Parse the metadata.  This calls into the MenuContentHandler's
675     * startElement and endElement methods, where a List of nodes and a TreeModel
676     * are created, along with the 3 hashMaps needed by the Model.</li>
677     * <li> Use the TreeModel to create the XMLMenuModel.</li>
678     * </ol>
679     */
680   private void _createModel()
681   {
682     try
683     {
684       // this block of code handles the injection of the 
685       // correct content handler for this xml menu model.
686       _isRoot = _isThisRootModel();
687       
688       boolean newHandlerCreated = false;
689       List<MenuContentHandler> listOfHandlers = getContentHandlerMap().get(getCacheKey());
690       Map<Integer,XMLMenuModel> requestModelMap = _getRootModelMap();
691       if(listOfHandlers != null)
692       {
693         if(requestModelMap == null)
694           _contentHandler = listOfHandlers.get(0);
695         else
696         {
697           
698           for (MenuContentHandler handler : listOfHandlers)
699           {
700             int id = handler.getId();
701             if (!requestModelMap.containsKey(id))
702             {
703               _contentHandler = handler;
704               break;
705             }
706           }
707         }
708       }
709       
710       if(_contentHandler == null)
711       {
712         List<MenuContentHandler> services =
713           ClassLoaderUtils.getServices(_MENUCONTENTHANDLER_SERVICE);
714 
715         if (services.isEmpty())
716         {
717           throw new IllegalStateException(_LOG.getMessage(
718             "NO_MENUCONTENTHANDLER_REGISTERED"));
719         }
720         
721         if(isCompatibilityMode())
722         {
723           _contentHandler = services.get(0);
724         }
725         else
726         {
727           _contentHandler = services.get(1);
728         }
729         
730         if (_contentHandler == null)
731         {
732           throw new NullPointerException();
733         }
734         newHandlerCreated = true;
735       }
736       
737       if(_isRoot)
738       {
739         if(listOfHandlers == null)
740         {
741           listOfHandlers =  Collections.synchronizedList(new ArrayList<MenuContentHandler>());
742           getContentHandlerMap().put(getCacheKey(), listOfHandlers);
743         }
744         if(newHandlerCreated)
745         {
746           listOfHandlers.add(_contentHandler);
747           _contentHandler.setRootHandler(true);
748           _contentHandler.setId(getContentHandlerId());
749         }
750         
751       }
752 
753       // Set the root, top-level menu model's URI on the contentHandler.
754       // In this model, the menu content handler and nodes need to have
755       // access to the model's data structures and to notify the model
756       // of the currently selected node (in the case of a POST).
757       _populateRootModelMap();
758       _setRootModelKey(_contentHandler);
759 
760       // Set the local model (model created by a sharedNode) on the
761       // contentHandler so that nodes can get back to their local model
762       // if necessary.
763       
764       // Nodes never get back to their local models,which is good
765       // because if they did they would not have found them as the 
766       // hash code of newly created XMLMenuModels would be different
767       // and hence the modelIds of the menu nodes would be stale
768       // as they are longer lived than the menu models
769       
770       // On the other hand the content handler does refer to it's
771       // local model during parsing at which time it is guaranteed
772       // to be referring to just one model due to synchronization
773       // of the parsing activity.
774       
775       // Therefore we only set the model id if this is a newly
776       // created content handler,as we know it will start parsing
777       // shortly.This is also necessary so that the model id of
778       // the content handler does not change during the time it
779       // is initializing it's state by parsing.Thus we can do
780       // without synchronization here as the protection is needed only during
781       // parsing.Any other request thread for the same meta-data URI will
782       // find the content handler already published on the concurrent hash map
783       // of content handlers.
784      
785       if(newHandlerCreated)
786         _setModelId(_contentHandler);
787 
788       TreeModel treeModel = _contentHandler.getTreeModel(_mdSource);
789       setWrappedData(treeModel);
790     }
791     catch (Exception ex)
792     {
793       _LOG.severe("ERR_CREATE_MENU_MODEL", _mdSource);
794       _LOG.severe(ex);
795       return;
796     }
797   }
798 
799   
800   /**
801    * _setRootModelKey - sets the top-level, menu model's Key on the
802    * menu content handler. This is so nodes will only operate
803    * on the top-level, root model.
804    *
805    */
806   @SuppressWarnings("unchecked")
807   private void _setRootModelKey(MenuContentHandler contentHandler)
808   {
809     contentHandler.setRootModelKey(_ROOT_MODEL_KEY);
810   }
811   
812 
813   /*
814    * sets the model into the requestMap
815    */
816   private void _populateRootModelMap()
817   {
818     if (_isRoot)
819     {
820       Map<String, Object> requestMap = _getRequestMap();
821       Map<Integer, XMLMenuModel> modelMap =
822           (Map<Integer, XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY);
823       if(modelMap == null)
824       {
825         modelMap =  new HashMap<Integer,XMLMenuModel>();
826         requestMap.put(_ROOT_MODEL_KEY, modelMap);
827       }
828       modelMap.put(_contentHandler.getId(), this);
829     }
830   }
831   
832   private boolean _isThisRootModel()
833   {
834    Map<String, Object> requestMap = _getRequestMap();
835    return !requestMap.containsKey(SHARED_MODEL_INDICATOR_KEY);
836   }
837   
838   private Map<String, Object> _getRequestMap()
839   {
840     FacesContext facesContext = FacesContext.getCurrentInstance();
841     Map<String, Object> requestMap =
842       facesContext.getExternalContext().getRequestMap();
843     return requestMap;
844   }
845   
846 
847   /**
848    * Returns the root menu model.
849    *
850    * @return XMLMenuModel the root menu model.
851    */
852   @SuppressWarnings("unchecked")
853   private XMLMenuModel _getRootModel()
854   {
855     Map<Integer, XMLMenuModel> map = _getRootModelMap();
856     return map.get(_contentHandler.getId());
857   }
858   
859   private Map<Integer,XMLMenuModel> _getRootModelMap()
860   {
861     FacesContext facesContext = FacesContext.getCurrentInstance();
862     Map<String, Object> requestMap =
863       facesContext.getExternalContext().getRequestMap();
864     Map<Integer,XMLMenuModel> map = (Map<Integer,XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY);
865     return map;
866   }
867 
868 
869   /**
870    * _getModelId - gets the local, menu model's Sys Id.
871    *
872    * @return String the model's System Id.
873    */
874   private String _getModelId()
875   {
876     return _modelId;
877   }
878 
879   /**
880    * _setModelId - sets the local, menu model's id on the
881    * menu content handler.
882    */
883   @SuppressWarnings("unchecked")
884   private void _setModelId(MenuContentHandler contentHandler)
885   {
886     String modelId = _getModelId();
887 
888     // Put the local model on the Request Map so that it
889     // Can be picked up by the nodes to call back into the
890     // local model
891     FacesContext facesContext = FacesContext.getCurrentInstance();
892     Map<String, Object> requestMap =
893       facesContext.getExternalContext().getRequestMap();
894 
895     requestMap.put(modelId, this);
896 
897     // Set the key (modelId) to the local model on the content
898     // handler so that it can then be set on each of the nodes
899     contentHandler.setModelId(modelId);
900   }
901 
902   /**
903    * Returns the current viewId.
904    *
905    * @return  the current viewId or null if the current viewId can't be found
906    */
907 
908   private String _getCurrentViewId()
909   {
910     String currentViewId =
911         FacesContext.getCurrentInstance().getViewRoot().getViewId();
912 
913     return currentViewId;
914   }
915 
916   /**
917    * Gets the currently selected node in the menu
918    */
919   private Object _getCurrentlySelectedNode()
920   {
921     return _currentNode;
922   }
923 
924   /**
925    * Sets the currently selected node.
926    *
927    * @param currentNode.  The currently selected node in the menu.
928    */
929   private void _setCurrentlySelectedNode(Object currentNode)
930   {
931     _currentNode = currentNode;
932   }
933 
934   /**
935    * Sets the request method
936    *
937    * @param method
938    */
939   private void _setRequestMethod(String method)
940   {
941     _requestMethod = method;
942   }
943 
944   /**
945    * Get the request method
946    */
947   private String _getRequestMethod()
948   {
949     return _requestMethod;
950   }
951 
952   /* ================================================================
953    * Public inner interface for the menu content handler
954    * implementation
955    * ================================================================ */
956 
957   /*
958    * Interface corresponding to the MenuContentHandlerImpl
959    * in org.apache.myfaces.trinidadinternal.menu.   This is used to achieve
960    * separation between the api (trinidad) and the implementation
961    * (trinidadinternal). It is only used by the XMLMenuModel, thus it is
962    * an internal interface.
963    */
964   public interface MenuContentHandler
965   {
966     /**
967       * Get the TreeModel built while parsing metadata.
968       *
969       * @param uri String mapkey to a (possibly) treeModel cached on
970       *        the MenuContentHandlerImpl.
971       * @return TreeModel.
972       */
973     public TreeModel getTreeModel(String uri);
974 
975     /**
976       * Sets the root model's request map key on the ContentHandler so
977       * that the nodes can get back to their root model
978       * through the request map.
979       */
980     public void setRootModelKey(String key);
981 
982     /**
983       * Sets the local, sharedNode model's Model id on the ContentHandler so that
984       * the local model can be gotten to, if necessary.
985       */
986     public void setModelId(String modelId);
987 
988     /**
989      * Get the Model's idNodeMap
990      *
991      * @return the Model's idNodeMap
992      */
993     public Map<String, Object> getIdNodeMap(Object modelKey);
994 
995     /**
996      * Get the Model's nodeFocusPathMap
997      *
998      * @return the Model's nodeFocusPathMap
999      */
1000     public Map<Object, List<Object>> getNodeFocusPathMap(Object modelKey);
1001 
1002     /**
1003      * Get the Model's viewIdFocusPathMap
1004      *
1005      * @return the Model's viewIdFocusPathMap
1006      */
1007     public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey);
1008     
1009     public void setRootHandler(boolean isRoot);
1010     
1011     /**
1012      * sets the id of this content handler
1013      */
1014     public void setId(int id);
1015     
1016     /**
1017      * gets the id of this content handler
1018      * @return the id
1019      */
1020     public int getId();
1021     
1022   }
1023 
1024   private Object  _currentNode       = null;
1025   private Object  _prevFocusPath     = null;
1026   private String  _prevViewId        = null;
1027   private String  _requestMethod     = _METHOD_NONE;
1028   private String  _mdSource          = null;
1029   private boolean _createHiddenNodes = false;
1030   private String  _modelId           = null;
1031 
1032   private Map<String, List<Object>> _viewIdFocusPathMap;
1033   private Map<Object, List<Object>> _nodeFocusPathMap;
1034   private Map<String, Object>       _idNodeMap;
1035 
1036   private MenuContentHandler _contentHandler  = null;
1037   private boolean _isRoot;
1038   
1039 
1040   // Only set this if _currentNode is a duplicate
1041    private Object             _prevRequestNode = null;
1042 
1043    // this key is used to store the map of models in the request.
1044   static private final String _ROOT_MODEL_KEY =
1045     "org.apache.myfaces.trinidad.model.XMLMenuModel.__root_menu__";
1046   
1047   // this key is used to store the map of content handlers in the scope map
1048   static private final String _CACHED_MODELS_KEY =
1049     "org.apache.myfaces.trinidad.model.XMLMenuModel.__handler_key__";
1050   
1051   // this supplies the id of the content handlers
1052   static private final String _CACHED_MODELS_ID_CNTR_KEY =
1053     "org.apache.myfaces.trinidad.model.XMLMenuModel.__id_cntr_key__";
1054   
1055 /**
1056  * This key is used to store information about the included xml menu
1057  * models which are constructed during parsing.
1058  */
1059   static public final String SHARED_MODEL_INDICATOR_KEY =
1060     "org.apache.myfaces.trinidad.model.XMLMenuModel.__indicator_key__";
1061 
1062   static private final String _NODE_ID_PROPERTY     = "nodeId";
1063   static private final String _METHOD_GET           = "get";
1064   static private final String _METHOD_POST          = "post";
1065   static private final String _METHOD_NONE          = "none";
1066   static private final String _CUSTOM_ATTR_LIST     = "customPropList";
1067   static private final String _MENUCONTENTHANDLER_SERVICE =
1068             "org.apache.myfaces.trinidad.model.XMLMenuModel$MenuContentHandler";
1069 
1070   static private final TrinidadLogger _LOG =
1071          TrinidadLogger.createTrinidadLogger(XMLMenuModel.class);
1072   
1073 }