You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ma...@apache.org on 2009/04/09 12:48:00 UTC

svn commit: r763596 - in /myfaces/trinidad/branches/1.2.11.2-branch: trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/

Author: matzew
Date: Thu Apr  9 10:48:00 2009
New Revision: 763596

URL: http://svn.apache.org/viewvc?rev=763596&view=rev
Log:
TRINIDAD-708 - NullPointerException with multiple XMLMenuModel beans in one page

Thanks to Abhijit S Ghosh for his patch

Added:
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableGroupNode.java
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableItemNode.java
Modified:
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java
    myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java

Modified: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java?rev=763596&r1=763595&r2=763596&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java (original)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java Thu Apr  9 10:48:00 2009
@@ -24,13 +24,18 @@
 import java.net.URL;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.el.ELContext;
 import javax.el.ELResolver;
 import javax.el.PropertyNotFoundException;
 
+import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
@@ -81,6 +86,8 @@
  *  </managed-bean>
  * </pre>
  *
+ * Objects of this class are not thread safe and should be used
+ * only in request scope.
  *
  */
 
@@ -130,7 +137,7 @@
  * have the currently selected node!
  */
 public class XMLMenuModel extends BaseMenuModel
-                          implements Serializable
+                          
 {
   public XMLMenuModel()
   {
@@ -169,7 +176,7 @@
     // There is no need to create the hashmaps or anything
     // on the child menu models.  A lot of overhead (performance and
     // memory) would be wasted.
-    if (this == _getRootModel())
+    if (_isRoot)
     {
       _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(_mdSource);
       _nodeFocusPathMap   = _contentHandler.getNodeFocusPathMap(_mdSource);
@@ -565,7 +572,7 @@
    */
   public Map<String, List<Object>> getViewIdFocusPathMap()
   {
-    if (this != _getRootModel() || _contentHandler == null)
+    if (!_isRoot || _contentHandler == null)
       return null;
 
     if (_viewIdFocusPathMap == null)
@@ -574,13 +581,60 @@
     return _viewIdFocusPathMap;
   }
 
+  /**
+ * Returns the map of content handlers
+ * which hold the state of one XML tree.
+ * @param scopeMap
+ * @return
+ */
+  protected Map<Object, List<MenuContentHandler> > getContentHandlerMap()
+  {
+    FacesContext facesContext = FacesContext.getCurrentInstance();
+    ExternalContext externalContext = facesContext.getExternalContext();
+    Map<String, Object> scopeMap =
+        externalContext.getApplicationMap();
+   Object lock  = externalContext.getContext();
+   
+   // cannot use double checked lock here as
+   // we cannot mark the reference as volatile
+   // therefore any reads should happen inside
+   // a synchronized block.
+   synchronized (lock)
+   {
+      Map contentHandlerMap = (Map) scopeMap.get(_CACHED_MODELS_KEY);
+      if (contentHandlerMap == null)
+      {
+        contentHandlerMap =
+            new ConcurrentHashMap<String, List<MenuContentHandler>>();
+        scopeMap.put(_CACHED_MODELS_KEY, contentHandlerMap);
+        scopeMap.put(_CACHED_MODELS_ID_CNTR_KEY,new AtomicInteger(-1));
+      }
+      return contentHandlerMap;
+    }
+    
+  }
+  
+  protected int getContentHandlerId()
+  {
+    FacesContext facesContext = FacesContext.getCurrentInstance();
+    ExternalContext externalContext = facesContext.getExternalContext();
+    Map<String, Object> scopeMap =
+        externalContext.getApplicationMap();
+    AtomicInteger counter = (AtomicInteger) scopeMap.get(_CACHED_MODELS_ID_CNTR_KEY);
+    return counter.getAndIncrement();
+  }
+  protected Object getCacheKey()
+  {
+    return _mdSource;
+  }
+  
   /* ====================================================================
    * Private Methods
    * ==================================================================== */
 
   private  Map<String, Object> _getIdNodeMap()
   {
-    return (this == _getRootModel()) ? _idNodeMap : null;
+    return (_isRoot) ? _idNodeMap : null;
   }
 
   /**
@@ -613,7 +667,33 @@
   {
     try
     {
-      if (_contentHandler == null)
+      // this block of code handles the injection of the 
+      // correct content handler for this xml menu model.
+      _isRoot = _isThisRootModel();
+      
+      boolean newHandlerCreated = false;
+      List<MenuContentHandler> listOfHandlers = getContentHandlerMap().get(getCacheKey());
+      Map<Integer,XMLMenuModel> requestModelMap = _getRootModelMap();
+      if(listOfHandlers != null)
+      {
+        if(requestModelMap == null)
+          _contentHandler = listOfHandlers.get(0);
+        else
+        {
+          
+          for (MenuContentHandler handler : listOfHandlers)
+          {
+            int id = handler.getId();
+            if (!requestModelMap.containsKey(id))
+            {
+              _contentHandler = handler;
+              break;
+            }
+          }
+        }
+      }
+      
+      if(_contentHandler == null)
       {
         List<MenuContentHandler> services =
           ClassLoaderUtils.getServices(_MENUCONTENTHANDLER_SERVICE);
@@ -629,18 +709,59 @@
         {
           throw new NullPointerException();
         }
+        newHandlerCreated = true;
+      }
+      
+      if(_isRoot)
+      {
+        if(listOfHandlers == null)
+        {
+          listOfHandlers =  Collections.synchronizedList(new ArrayList<MenuContentHandler>());
+          getContentHandlerMap().put(getCacheKey(), listOfHandlers);
+        }
+        if(newHandlerCreated)
+        {
+          listOfHandlers.add(_contentHandler);
+          _contentHandler.setRootHandler(true);
+          _contentHandler.setId(getContentHandlerId());
+        }
+        
       }
 
       // Set the root, top-level menu model's URI on the contentHandler.
       // In this model, the menu content handler and nodes need to have
       // access to the model's data structures and to notify the model
       // of the currently selected node (in the case of a POST).
+      _populateRootModelMap();
       _setRootModelKey(_contentHandler);
 
       // Set the local model (model created by a sharedNode) on the
       // contentHandler so that nodes can get back to their local model
       // if necessary.
-      _setModelId(_contentHandler);
+      
+      // Nodes never get back to their local models,which is good
+      // because if they did they would not have found them as the 
+      // hash code of newly created XMLMenuModels would be different
+      // and hence the modelIds of the menu nodes would be stale
+      // as they are longer lived than the menu models
+      
+      // On the other hand the content handler does refer to it's
+      // local model during parsing at which time it is guaranteed
+      // to be referring to just one model due to synchronization
+      // of the parsing activity.
+      
+      // Therefore we only set the model id if this is a newly
+      // created content handler,as we know it will start parsing
+      // shortly.This is also necessary so that the model id of
+      // the content handler does not change during the time it
+      // is initializing it's state by parsing.Thus we can do
+      // without synchronization here as the protection is needed only during
+      // parsing.Any other request thread for the same meta-data URI will
+      // find the content handler already published on the concurrent hash map
+      // of content handlers.
+     
+      if(newHandlerCreated)
+        _setModelId(_contentHandler);
 
       TreeModel treeModel = _contentHandler.getTreeModel(_mdSource);
       setWrappedData(treeModel);
@@ -653,6 +774,7 @@
     }
   }
 
+  
   /**
    * _setRootModelKey - sets the top-level, menu model's Key on the
    * menu content handler. This is so nodes will only operate
@@ -662,22 +784,43 @@
   @SuppressWarnings("unchecked")
   private void _setRootModelKey(MenuContentHandler contentHandler)
   {
-    if (_getRootModel() == null)
+    contentHandler.setRootModelKey(_ROOT_MODEL_KEY);
+  }
+  
+
+  /*
+   * sets the model into the requestMap
+   */
+  private void _populateRootModelMap()
+  {
+    if (_isRoot)
     {
-      // Put the root model on the Request Map so that it
-      // Can be picked up by the nodes to call back into the
-      // root model
-      FacesContext facesContext = FacesContext.getCurrentInstance();
-      Map<String, Object> requestMap =
-        facesContext.getExternalContext().getRequestMap();
-
-      requestMap.put(_ROOT_MODEL_KEY, this);
-
-      // Set the key to the root model on the content
-      // handler so that it can then be set on each of the nodes
-      contentHandler.setRootModelKey(_ROOT_MODEL_KEY);
+      Map<String, Object> requestMap = _getRequestMap();
+      Map<Integer, XMLMenuModel> modelMap =
+          (Map<Integer, XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY);
+      if(modelMap == null)
+      {
+        modelMap =  new HashMap<Integer,XMLMenuModel>();
+        requestMap.put(_ROOT_MODEL_KEY, modelMap);
+      }
+      modelMap.put(_contentHandler.getId(), this);
     }
   }
+  
+  private boolean _isThisRootModel()
+  {
+   Map<String, Object> requestMap = _getRequestMap();
+   return !requestMap.containsKey(SHARED_MODEL_INDICATOR_KEY);
+  }
+  
+  private Map<String, Object> _getRequestMap()
+  {
+    FacesContext facesContext = FacesContext.getCurrentInstance();
+    Map<String, Object> requestMap =
+      facesContext.getExternalContext().getRequestMap();
+    return requestMap;
+  }
+  
 
   /**
    * Returns the root menu model.
@@ -687,10 +830,17 @@
   @SuppressWarnings("unchecked")
   private XMLMenuModel _getRootModel()
   {
+    Map<Integer, XMLMenuModel> map = _getRootModelMap();
+    return map.get(_contentHandler.getId());
+  }
+  
+  private Map<Integer,XMLMenuModel> _getRootModelMap()
+  {
     FacesContext facesContext = FacesContext.getCurrentInstance();
     Map<String, Object> requestMap =
       facesContext.getExternalContext().getRequestMap();
-    return (XMLMenuModel) requestMap.get(_ROOT_MODEL_KEY);
+    Map<Integer,XMLMenuModel> map = (Map<Integer,XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY);
+    return map;
   }
 
 
@@ -833,6 +983,20 @@
      * @return the Model's viewIdFocusPathMap
      */
     public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey);
+    
+    public void setRootHandler(boolean isRoot);
+    
+    /**
+     * sets the id of this content handler
+     */
+    public void setId(int id);
+    
+    /**
+     * gets the id of this content handler
+     * @return the id
+     */
+    public int getId();
+    
   }
 
   private Object  _currentNode       = null;
@@ -847,13 +1011,31 @@
   private Map<Object, List<Object>> _nodeFocusPathMap;
   private Map<String, Object>       _idNodeMap;
 
-  static private MenuContentHandler _contentHandler  = null;
+  private MenuContentHandler _contentHandler  = null;
+  private boolean _isRoot;
+  
 
   // Only set this if _currentNode is a duplicate
-  static private Object             _prevRequestNode = null;
+   private Object             _prevRequestNode = null;
 
+   // this key is used to store the map of models in the request.
   static private final String _ROOT_MODEL_KEY =
     "org.apache.myfaces.trinidad.model.XMLMenuModel.__root_menu__";
+  
+  // this key is used to store the map of content handlers in the scope map
+  static private final String _CACHED_MODELS_KEY =
+    "org.apache.myfaces.trinidad.model.XMLMenuModel.__handler_key__";
+  
+  // this supplies the id of the content handlers
+  static private final String _CACHED_MODELS_ID_CNTR_KEY =
+    "org.apache.myfaces.trinidad.model.XMLMenuModel.__id_cntr_key__";
+  
+/**
+ * This key is used to store information about the included xml menu
+ * models which are constructed during parsing.
+ */
+  static public final String SHARED_MODEL_INDICATOR_KEY =
+    "org.apache.myfaces.trinidad.model.XMLMenuModel.__indicator_key__";
 
   static private final String _NODE_ID_PROPERTY     = "nodeId";
   static private final String _METHOD_GET           = "get";
@@ -865,5 +1047,5 @@
 
   static private final TrinidadLogger _LOG =
          TrinidadLogger.createTrinidadLogger(XMLMenuModel.class);
-  private static final long serialVersionUID = 1L;
+  
 }

Modified: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java?rev=763596&r1=763595&r2=763596&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java (original)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java Thu Apr  9 10:48:00 2009
@@ -191,7 +191,18 @@
   {
     return _idref;
   }
+  
+  public String[] getIdRefListProperty()
+  {
+    return _idrefList;
+  }
 
+  @Override
+  protected MenuNode getThreadSafeCopy()
+  {
+    return new ImmutableGroupNode(this);
+  }
+  
   /* =============================================================
    * Private methods
    * =============================================================*/

Added: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableGroupNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableGroupNode.java?rev=763596&view=auto
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableGroupNode.java (added)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableGroupNode.java Thu Apr  9 10:48:00 2009
@@ -0,0 +1,549 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.myfaces.trinidadinternal.menu;
+
+import java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.myfaces.trinidad.logging.TrinidadLogger;
+import org.apache.myfaces.trinidad.util.ContainerUtils;
+
+/**
+ * This class is a thread safe version of GroupNode class.
+ * It replicates most of the code in GroupNode but makes
+ * sure it does not modify state of the object.
+ * 
+ * Therefore multiple request threads can access the
+ * properties of the objects of this class,in a thread safe
+ * manner.
+ * 
+ * Please note that setters should not be called on objects
+ * of this class.Objects of this class are fully initialized
+ * on construction.
+ *
+ */
+public class ImmutableGroupNode extends GroupNode
+{
+  
+  public ImmutableGroupNode(GroupNode node)
+  {
+    _icon = node.getIconProperty();
+    _focusViewId = node.getFocusViewIdProperty();
+    _renderedStr = node.getRenderedProperty();
+    _disabledStr = node.getDisabledProperty();
+    _visibleStr = node.getVisibleProperty();
+    _readOnlyStr = node.getReadOnlyProperty();
+    _handlerId = node.getHandlerIdProperty();
+    _bundleKey = node.getBundleKeyProperty();
+    _bundleName = node.getBundleNameProperty();
+    _accessKey = node.getAccessKeyProperty();
+    _id = node.getIdProperty();
+    _modelId = node.getModelIdProperty();
+    _labelAndAccessKey = node.getLabelAndAccessKeyProperty();
+    _defaultFocusPathStr = node.getDefaultFocusPathProperty();
+
+    // Root Menu model's Request Map Key
+    _rootModelKey = node.getRootModelKeyProperty();
+
+    _rootId = node.getRootIdProperty();
+    _label = node.getLabelProperty();
+    _idref = node.getIdRef();
+    _idrefList = node.getIdRefListProperty();
+  }
+  
+  
+  public String getIdRef()
+  {
+    return _idref;
+  }
+  
+  public MenuNode getRefNode()
+  {
+    MenuNode refNode = null;
+
+    
+    // Get idrefList
+    String[] idrefList = _idrefList;
+
+    // get group node's children
+    List<MenuNode> children = getChildren();
+
+    // Traverse the list. Do the following:
+    //    o get Node from Model's hashMap of nodes and ids
+    //    o check attributes (rendered, disabled, readOnly)
+    //    o if they are ok, return the node
+    for (int i=0; i < Array.getLength(idrefList); i++)
+    {
+      Iterator<MenuNode> childIter = children.iterator();
+
+      // All node "id" attribute values had the node's
+      // system hashcode id appended to the id when
+      // placed in the model's idNodeMap.
+      //
+      // Each id in the idreflist of a group node does
+      // NOT have this node sys id appended it to it
+      // and needs to or we won't find the group's
+      // ref node.
+      //
+      // Since group nodes can only point to one of
+      // its children, we iterate through them, get
+      // their sys id and append it to idref until
+      // we find a match (or not).
+      while (childIter.hasNext())
+      {
+        MenuNode childNode = childIter.next();
+        String modelId = childNode.getModelId();
+
+        // Need to append mode's sys id here to create a
+        // unique id.
+        String refNodeId = idrefList[i] + modelId;
+
+        refNode = (MenuNode) getRootModel().getNode(refNodeId);
+
+        // if nothing found, move on to the next child
+        if (refNode != null)
+         break;
+      }
+
+      if (refNode == null)
+        continue;
+
+      // Check the attributes of the found node
+      if (   !refNode.getRendered()
+          ||  refNode.getDisabled()
+          ||  refNode.getReadOnly()
+          || !refNode.getVisible()
+         )
+      {
+        refNode = null;
+        continue;
+      }
+
+      // Ok, we have a valid RefNode
+      break;
+    }
+
+    // If no valid node is found,
+    // log an error
+    if (refNode == null)
+    {
+        _LOG.severe("GroupNode " + getLabel() + " refers to no valid node.\n");
+        return null;
+    }
+
+    return refNode;
+  }
+
+  public final String getLabel()
+  {
+    if (_bundleKey != null && _bundleName != null)
+    {
+      // Load the resource bundle based on the locale of the
+      // current request. If the locale has not changed, this
+      // method just returns.
+      MenuUtils.loadBundle(_bundleName, _bundleKey + getHandlerId());
+    }
+
+    if (_label != null && ContainerUtils.isValueReference(_label))
+    {
+      // do not set _label to the evaluated EL.
+      // It may change at times in the EL.
+      return _evalElStr(_label);
+    }
+    if (_label == null && _labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      return label;
+
+    }
+    return _label;
+  }
+
+  public final String getIcon()
+  {
+    return MenuUtils.evalString(_icon);
+  }
+
+  public final List<MenuNode> getChildren()
+  {
+    return _children;
+  }
+
+  public final String getFocusViewId()
+  {
+    return _focusViewId;
+  }
+
+  public final boolean getRendered()
+  {
+    boolean rendered = MenuUtils.evalBoolean(_renderedStr, true);
+    return rendered;
+  }
+
+  public final boolean getDisabled()
+  {
+    boolean disabled = MenuUtils.evalBoolean(_disabledStr, false);
+    return disabled;
+  }
+
+  public final boolean getVisible()
+  {
+    boolean visible = MenuUtils.evalBoolean(_visibleStr, true);
+    return visible;
+  }
+
+  public final boolean getReadOnly()
+  {
+    boolean readOnly = MenuUtils.evalBoolean(_readOnlyStr, false);
+    return readOnly;
+  }
+
+  protected final String getHandlerId()
+  {
+    return _handlerId;
+  }
+
+  public final String getBundleKey()
+  {
+    return _bundleKey;
+  }
+
+  public final String getBundleName()
+  {
+    return _bundleName;
+  }
+
+  
+
+  
+
+  public final char getAccessKey()
+  {
+    if (_accessKey == null && _labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } 
+      else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey = null;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      return (accessKey != null)? accessKey.charAt(0):'\0';
+
+    }
+    else 
+    {
+      String accessKeyStr = MenuUtils.evalString(_accessKey);
+      if (accessKeyStr == null || accessKeyStr.length() > 1)
+        return '\0';
+      return accessKeyStr.charAt(0);
+    }
+    
+  }
+
+  public final String getLabelAndAccessKey()
+  {
+    if (_labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (_bundleKey != null && _bundleName != null)
+      {
+        // Load the resource bundle based on the locale of the
+        // current request. If the locale has not changed, this
+        // method just returns.
+        MenuUtils.loadBundle(_bundleName, _bundleKey + getHandlerId());
+      }
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey = null;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      if(accessKey == null)
+        return label;
+      
+      return _joinLabelAndAccessKey(label, accessKey);
+    }
+    return null;
+  }
+  public final String getId()
+  {
+    return _id;
+  }
+
+  public final String getModelId()
+  {
+    return _modelId;
+  }
+
+  //TODO make this work
+//  public final String getLabelAndAccessKey()
+//  {
+//    String labelAndAcessKeyEval;
+//    if ( _labelAndAccessKey != null
+//        && ContainerUtils.isValueReference(_labelAndAccessKey)
+//       )
+//    {
+//       labelAndAcessKeyEval= _evalElStr(_labelAndAccessKey);
+//    }
+//    else
+//    {
+//      labelAndAcessKeyEval = _labelAndAccessKey;
+//    }
+//    
+//    
+//  }
+
+  public final boolean getDefaultFocusPath()
+  {
+    boolean defaultFocusPath =
+        MenuUtils.evalBoolean(_defaultFocusPathStr, false);
+    return defaultFocusPath;
+  }
+
+  public final String getRootModelKey()
+  {
+    return _rootModelKey;
+  }
+
+  public final int getRootId()
+  {
+    return _rootId;
+  }
+
+  private String _evalElStr(String str)
+  {
+    if (str == null)
+      return null;
+
+    String keystr =
+        MenuUtils.stringReplaceFirst(str.trim(), _bundleKey, _bundleKey +
+            getHandlerId());
+    String elVal = MenuUtils.getBoundValue(keystr, String.class);
+    return elVal;
+  }
+  
+  private String _joinLabelAndAccessKey(String label, String accessKey)
+  {
+    char[] keyArray = label.toCharArray();
+    int len = label.length();
+    int lentimes2 = len * 2;
+    char[] keyArray2 = new char[lentimes2];
+    int i, j = 0;
+    boolean accessKeyFound = false;
+
+    // find the first occurrence of a single Ampersand
+    for (i = 0, j = 0; i < len; i++, j++)
+    {
+      // AccessKey
+      if (keyArray[i] == accessKey.charAt(0) && !accessKeyFound)
+      {
+        keyArray2[j] = '&';
+        j++;
+        accessKeyFound = true;
+      }
+
+      keyArray2[j] = keyArray[i];
+
+      // Ampersand as regular character
+      // double it up.
+      if (keyArray[i] == '&')
+      {
+        j++;
+        keyArray2[j] = keyArray[i];
+      }
+    }
+
+    String combinedLabel = new String(keyArray2, 0, j);
+    return combinedLabel;
+  }
+
+  private final String _icon;
+  private List<MenuNode> _children;
+  private final String _focusViewId;
+  private final String _renderedStr;
+  private final String _disabledStr;
+  private final String _visibleStr;
+  private final String _readOnlyStr;
+  private final String _handlerId;
+  private final String _bundleKey;
+  private final String _bundleName;
+  private final String _accessKey;
+  private final String _id;
+  private final String _modelId;
+  private final String _labelAndAccessKey;
+  private final String _defaultFocusPathStr;
+
+  // Root Menu model's Request Map Key
+  private final String _rootModelKey;
+
+  private final int _rootId;
+
+  
+
+  private final String _label;
+  
+  
+  private final String   _idref ;
+  private final String[] _idrefList ;
+
+  private final static TrinidadLogger _LOG =
+    TrinidadLogger.createTrinidadLogger(ImmutableGroupNode.class);
+}

Added: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableItemNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableItemNode.java?rev=763596&view=auto
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableItemNode.java (added)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ImmutableItemNode.java Thu Apr  9 10:48:00 2009
@@ -0,0 +1,621 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.myfaces.trinidadinternal.menu;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.el.ELContext;
+import javax.el.ExpressionFactory;
+import javax.el.MethodExpression;
+import javax.faces.context.FacesContext;
+import javax.faces.event.ActionEvent;
+
+import org.apache.myfaces.trinidad.util.ContainerUtils;
+
+/**
+ * This class is a thread safe version of ItemNode class.
+ * It replicates most of the code in ItemNode but makes
+ * sure it does not modify state of the object.
+ * 
+ * Therefore multiple request threads can access the
+ * properties of the objects of this class,in a thread safe
+ * manner.
+ * 
+ * Please note that setters should not be called on objects
+ * of this class.Objects of this class are fully initialized
+ * on construction.
+ *
+ */
+public class ImmutableItemNode extends ItemNode
+{
+  
+  public ImmutableItemNode(ItemNode node)
+  {
+    _icon = node.getIconProperty();
+    _focusViewId = node.getFocusViewIdProperty();
+    _renderedStr = node.getRenderedProperty();
+    _disabledStr = node.getDisabledProperty();
+    _visibleStr = node.getVisibleProperty();
+    _readOnlyStr = node.getReadOnlyProperty();
+    _handlerId = node.getHandlerIdProperty();
+    _bundleKey = node.getBundleKeyProperty();
+    _bundleName = node.getBundleNameProperty();
+    _accessKey = node.getAccessKeyProperty();
+    _id = node.getIdProperty();
+    _modelId = node.getModelIdProperty();
+    _labelAndAccessKey = node.getLabelAndAccessKeyProperty();
+    _defaultFocusPathStr = node.getDefaultFocusPathProperty();
+
+    // Root Menu model's Request Map Key
+    _rootModelKey = node.getRootModelKeyProperty();
+
+    _rootId = node.getRootIdProperty();
+
+    _customPropList = node.getCustomPropListProperty();
+
+    _destination = node.getDestinationProperty();
+    _targetFrame = node.getTargetFrameProperty();
+    _action = node.getActionProperty();
+    _actionListener = node.getActionListenerProperty();
+    _launchListener = node.getLaunchListenerProperty();
+    _returnListener = node.getReturnListenerProperty();
+    _immediateStr = node.getImmediateProperty();
+    _useWindowStr = node.getUseWindowProperty();
+    _windowHeightStr = node.getWindowHeightProperty();
+    _windowWidthStr = node.getWindowWidthProperty();
+
+    _label = node.getLabelProperty();
+  }
+
+  public final Map<String, String> getCustomPropList()
+  {
+    return _customPropList;
+  }
+
+  public final String getDestination()
+  {
+
+    String value = _destination;
+
+    // Could be EL expression
+    if (value != null && ContainerUtils.isValueReference(value))
+    {
+      // Value of action is EL method binding, so we
+      // need to evaluate it
+      value = MenuUtils.getBoundValue(value, String.class);
+    }
+
+    // Appending nodeId to URL so that we can identify the node
+    // when getFocusRowKey() is called on the model.
+    return value != null ? value + "?nodeId=" + getUniqueId() : value;
+
+  }
+
+  public final String getTargetFrame()
+  {
+    String value = _targetFrame;
+
+    // Could be EL expression
+    if (value != null && ContainerUtils.isValueReference(value))
+    {
+      // Value of destination is EL value binding, so we
+      // need to evaluate it
+      value = MenuUtils.getBoundValue(value, String.class);
+    }
+
+    return value;
+  }
+
+  public final String getActionListener()
+  {
+    String value = _actionListener;
+
+    // Could be EL expression
+    if (value != null && ContainerUtils.isValueReference(value))
+    {
+      // Value of action is EL method binding, so we
+      // need to evaluate it
+      value = MenuUtils.getBoundValue(value, String.class);
+    }
+
+    return value;
+  }
+
+  public final String getLaunchListener()
+  {
+
+    String value = _launchListener;
+
+    // Could be EL expression
+    if (value != null && ContainerUtils.isValueReference(value))
+    {
+      // Value of action is EL method binding, so we
+      // need to evaluate it
+      value = MenuUtils.getBoundValue(value, String.class);
+    }
+
+    return value;
+  }
+
+  public final String getReturnListener()
+  {
+    String value = _returnListener;
+
+    // Could be EL expression
+    if (value != null && ContainerUtils.isValueReference(value))
+    {
+      // Value of action is EL method binding, so we
+      // need to evaluate it
+      value = MenuUtils.getBoundValue(value, String.class);
+    }
+
+    return value;
+
+  }
+
+  public final boolean getImmediate()
+  {
+    boolean immediate = MenuUtils.evalBoolean(_immediateStr, false);
+    return immediate;
+  }
+
+  public final boolean getUseWindow()
+  {
+    boolean useWindow = MenuUtils.evalBoolean(_useWindowStr, false);
+    return useWindow;
+  }
+
+  public final int getWindowHeight()
+  {
+    int windowHeight = MenuUtils.evalInt(_windowHeightStr);
+    return windowHeight;
+  }
+
+  public final int getWindowWidth()
+  {
+    int windowWidth = MenuUtils.evalInt(_windowWidthStr);
+    return windowWidth;
+  }
+
+  public final String getLabel()
+  {
+    if (_bundleKey != null && _bundleName != null)
+    {
+      // Load the resource bundle based on the locale of the
+      // current request. If the locale has not changed, this
+      // method just returns.
+      MenuUtils.loadBundle(_bundleName, _bundleKey + getHandlerId());
+    }
+
+    if (_label != null && ContainerUtils.isValueReference(_label))
+    {
+      // do not set _label to the evaluated EL.
+      // It may change at times in the EL.
+      return _evalElStr(_label);
+    }
+    if (_label == null && _labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      return label;
+
+    }
+    return _label;
+  }
+
+  public final String getIcon()
+  {
+    return MenuUtils.evalString(_icon);
+  }
+
+  public final List<MenuNode> getChildren()
+  {
+    return _children;
+  }
+
+  public void setChildren(List<MenuNode> children)
+  {
+    _children = children;
+  }
+
+  public final String getFocusViewId()
+  {
+    return _focusViewId;
+  }
+
+  public final boolean getRendered()
+  {
+    boolean rendered = MenuUtils.evalBoolean(_renderedStr, true);
+    return rendered;
+  }
+
+  public final boolean getDisabled()
+  {
+    boolean disabled = MenuUtils.evalBoolean(_disabledStr, false);
+    return disabled;
+  }
+
+  public final boolean getVisible()
+  {
+    boolean visible = MenuUtils.evalBoolean(_visibleStr, true);
+    return visible;
+  }
+
+  public final boolean getReadOnly()
+  {
+    boolean readOnly = MenuUtils.evalBoolean(_readOnlyStr, false);
+    return readOnly;
+  }
+
+  protected final String getHandlerId()
+  {
+    return _handlerId;
+  }
+
+  public final String getBundleKey()
+  {
+    return _bundleKey;
+  }
+
+  public final String getBundleName()
+  {
+    return _bundleName;
+  }
+
+  public void actionListener(ActionEvent event)
+  {
+    String value = _actionListener;
+    if (value != null)
+    {
+      FacesContext facesContext = FacesContext.getCurrentInstance();
+      ExpressionFactory expressionFactory =
+          facesContext.getApplication().getExpressionFactory();
+      ELContext context = facesContext.getELContext();
+
+      MethodExpression methodExpression =
+          expressionFactory.createMethodExpression(context, value, Void.TYPE,
+              new Class<?>[]
+              { ActionEvent.class });
+      methodExpression.invoke(context, new Object[]
+      { event });
+    }
+
+  }
+
+  public String doAction()
+  {
+    String value = _action;
+
+    if (value != null)
+    {
+      FacesContext facesContext = FacesContext.getCurrentInstance();
+      ExpressionFactory expressionFactory =
+          facesContext.getApplication().getExpressionFactory();
+      ELContext context = facesContext.getELContext();
+      MethodExpression methodExpression =
+          expressionFactory.createMethodExpression(context, value,
+              String.class, new Class<?>[]
+              {});
+      value = (String) methodExpression.invoke(context, null);
+    }
+
+    // Post me as the selected Node for the request
+    postSelectedNode(this);
+
+    return value;
+  }
+
+  public final char getAccessKey()
+  {
+    if (_accessKey == null && _labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } 
+      else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey = null;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      return (accessKey != null)? accessKey.charAt(0):'\0';
+
+    }
+    else 
+    {
+      String accessKeyStr = MenuUtils.evalString(_accessKey);
+      if (accessKeyStr == null || accessKeyStr.length() > 1)
+        return '\0';
+      return accessKeyStr.charAt(0);
+    }
+    
+  }
+
+  public final String getId()
+  {
+    return _id;
+  }
+
+  public final String getModelId()
+  {
+    return _modelId;
+  }
+
+  public final String getLabelAndAccessKey()
+  {
+    String labelAndAcessKeyEval;
+    if (_labelAndAccessKey != null)
+    {
+      int ampIdx = 0;
+      String labelAndAccessKeyEval = null;
+      String labelAndAccessKey = _labelAndAccessKey;
+      String label;
+      if (_bundleKey != null && _bundleName != null)
+      {
+        // Load the resource bundle based on the locale of the
+        // current request. If the locale has not changed, this
+        // method just returns.
+        MenuUtils.loadBundle(_bundleName, _bundleKey + getHandlerId());
+      }
+      if (ContainerUtils.isValueReference(labelAndAccessKey))
+      {
+        labelAndAccessKeyEval = _evalElStr(labelAndAccessKey);
+      } else
+      {
+        labelAndAccessKeyEval = labelAndAccessKey;
+      }
+
+      String accessKey = null;
+      if (labelAndAccessKeyEval == null ||
+          (ampIdx = labelAndAccessKeyEval.indexOf('&')) == -1)
+      {
+        // String is null or a label w/o an accesskey
+        label = labelAndAccessKeyEval;
+      } else if (ampIdx == (labelAndAccessKeyEval.length() - 1))
+      {
+        // & is last character, strip it.
+        label = labelAndAccessKeyEval.substring(0, ampIdx);
+      } else
+      {
+        // We have a string with an accessKey somewhere
+        char[] keyArray = labelAndAccessKeyEval.toCharArray();
+        int len = labelAndAccessKeyEval.length();
+        char[] keyArray2 = new char[len];
+        int i, j = 0;
+        boolean accessKeyFound = false;
+
+        for (i = 0, j = 0; i < len; i++, j++)
+        {
+          if (keyArray[i] == '&')
+          {
+            i++;
+
+            if (!accessKeyFound && keyArray[i] != '&')
+            {
+              // We have our accessKey
+              accessKey = labelAndAccessKeyEval.substring(i, i + 1);
+              accessKeyFound = true;
+            }
+          }
+
+          keyArray2[j] = keyArray[i];
+        }
+
+        String label1 = new String(keyArray2, 0, j);
+        label = label1;
+      }
+      return _joinLabelAndAccessKey(label, accessKey);
+    }
+    return null;
+  }
+
+  public final boolean getDefaultFocusPath()
+  {
+    boolean defaultFocusPath =
+        MenuUtils.evalBoolean(_defaultFocusPathStr, false);
+    return defaultFocusPath;
+  }
+
+  public final String getRootModelKey()
+  {
+    return _rootModelKey;
+  }
+
+  public final int getRootId()
+  {
+    return _rootId;
+  }
+
+  private String _evalElStr(String str)
+  {
+    if (str == null)
+      return null;
+
+    String keystr =
+        MenuUtils.stringReplaceFirst(str.trim(), _bundleKey, _bundleKey +
+            getHandlerId());
+    String elVal = MenuUtils.getBoundValue(keystr, String.class);
+    return elVal;
+  }
+
+  private String _joinLabelAndAccessKey(String label, String accessKey)
+  {
+    char[] keyArray = label.toCharArray();
+    int len = label.length();
+    int lentimes2 = len * 2;
+    char[] keyArray2 = new char[lentimes2];
+    int i, j = 0;
+    boolean accessKeyFound = false;
+
+    // find the first occurrence of a single Ampersand
+    for (i = 0, j = 0; i < len; i++, j++)
+    {
+      // AccessKey
+      if (keyArray[i] == accessKey.charAt(0) && !accessKeyFound)
+      {
+        keyArray2[j] = '&';
+        j++;
+        accessKeyFound = true;
+      }
+
+      keyArray2[j] = keyArray[i];
+
+      // Ampersand as regular character
+      // double it up.
+      if (keyArray[i] == '&')
+      {
+        j++;
+        keyArray2[j] = keyArray[i];
+      }
+    }
+
+    String combinedLabel = new String(keyArray2, 0, j);
+    return combinedLabel;
+  }
+
+  private final String _icon;
+  private List<MenuNode> _children;
+  private final String _focusViewId;
+  private final String _renderedStr;
+  private final String _disabledStr;
+  private final String _visibleStr;
+  private final String _readOnlyStr;
+  private final String _handlerId;
+  private final String _bundleKey;
+  private final String _bundleName;
+  private final String _accessKey;
+  private final String _id;
+  private final String _modelId;
+  private final String _labelAndAccessKey;
+  private final String _defaultFocusPathStr;
+
+  // Root Menu model's Request Map Key
+  private final String _rootModelKey;
+
+  private final int _rootId;
+
+  private final Map<String, String> _customPropList;
+
+  private final String _destination;
+  private final String _targetFrame;
+  private final String _action;
+  private final String _actionListener;
+  private final String _launchListener;
+  private final String _returnListener;
+  private final String _immediateStr;
+  private final String _useWindowStr;
+  private final String _windowHeightStr;
+  private final String _windowWidthStr;
+
+  private final String _label;
+
+}

Modified: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java?rev=763596&r1=763595&r2=763596&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java (original)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java Thu Apr  9 10:48:00 2009
@@ -454,6 +454,61 @@
     return _customPropList;
   }
   
+  
+  public final Map<String, String> getCustomPropListProperty()
+  {
+    return _customPropList;
+  }
+
+  public final String getDestinationProperty()
+  {
+    return _destination;
+  }
+
+  public final String getTargetFrameProperty()
+  {
+    return _targetFrame;
+  }
+
+  public final String getActionProperty()
+  {
+    return _action;
+  }
+
+  public final String getActionListenerProperty()
+  {
+    return _actionListener;
+  }
+
+  public final String getLaunchListenerProperty()
+  {
+    return _launchListener;
+  }
+
+  public final String getReturnListenerProperty()
+  {
+    return _returnListener;
+  }
+
+  public final String getImmediateProperty()
+  {
+    return _immediateStr;
+  }
+
+  public final String getUseWindowProperty()
+  {
+    return _useWindowStr;
+  }
+
+  public final String getWindowHeightProperty()
+  {
+    return _windowHeightStr;
+  }
+
+  public final String getWindowWidthProperty()
+  {
+    return _windowWidthStr;
+  }
   /**
    * Set the list of custom attributes.
    * 
@@ -465,6 +520,11 @@
     _customPropList = attrMap;
   }
   
+  protected MenuNode getThreadSafeCopy()
+  {
+    return new ImmutableItemNode(this);
+  }
+  
   // Map for Custom attributes (properties)
   private Map<String, String> _customPropList = null;
   
@@ -478,4 +538,6 @@
   private String _useWindowStr    = null;
   private String _windowHeightStr = null;
   private String _windowWidthStr  = null;
+  
+ 
 }

Modified: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java?rev=763596&r1=763595&r2=763596&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java (original)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java Thu Apr  9 10:48:00 2009
@@ -20,8 +20,10 @@
 
 import java.io.InputStream;
 import java.io.IOException;
+import java.io.Serializable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 
@@ -100,8 +102,9 @@
   * selected.
   */
 public class MenuContentHandlerImpl extends DefaultHandler
-                                    implements MenuContentHandler
+                                    implements MenuContentHandler,Serializable
 {
+
   /**
     * Constructs a Menu Content Handler.
     */
@@ -109,11 +112,6 @@
   {
     super();
 
-    // Init the essential maps.
-    _treeModelMap = new HashMap<String, TreeModel>();
-    _viewIdFocusPathMapMap = new HashMap<Object, Map<String, List<Object>>>();
-    _nodeFocusPathMapMap = new HashMap<Object, Map<Object, List<Object>>>();
-    _idNodeMapMap = new HashMap<Object, Map<String, Object>>();
   }
 
   /**
@@ -234,8 +232,19 @@
           // local model.
           menuNode.setModelId(getModelId());
 
+          // menu nodes need to know how to refer
+          // back to the specific xml menu model
+          // so that they can mutate them.
+          FacesContext facesContext = FacesContext.getCurrentInstance();
+          Map<String, Object> requestMap =
+            facesContext.getExternalContext().getRequestMap();
+          if(!requestMap.containsKey(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY))
+            menuNode.setRootId( (getId() ) );
+          else
+            menuNode.setRootId((Integer) requestMap.get(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY));
+
           List<MenuNode> list = _menuNodes.get(_nodeDepth-1);
-          list.add(menuNode);
+          list.add(menuNode.getThreadSafeCopy());
         }
       }
       else if (_SHARED_NODE.equals(qualifiedElemName))
@@ -247,6 +256,20 @@
         // recursively call into this MenuContentHandlerImpl when parsing the
         // submenu's metadata.
         String expr = attrList.getValue(_REF_ATTR);
+        
+        // push this only when we are root model
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        Map<String, Object> requestMap =
+          facesContext.getExternalContext().getRequestMap();
+        Integer recurseLevel = (Integer) requestMap.get(_RECURSE_COUNTER);
+        if(recurseLevel == null) 
+          recurseLevel = 0;
+        if(recurseLevel == 0)
+          requestMap.put(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY, this.getId());
+        
+        recurseLevel++;
+        requestMap.put(_RECURSE_COUNTER, recurseLevel);
+        
 
         // Need to push several items onto the stack now as we recurse
         // into another menu model.
@@ -260,6 +283,14 @@
         // Now must pop the values cause we are back to the parent
         // model.
         _restoreModelData();
+        
+        recurseLevel = (Integer) requestMap.get(_RECURSE_COUNTER);
+        recurseLevel --;
+        requestMap.put(_RECURSE_COUNTER, recurseLevel);
+        
+        if(recurseLevel == 0)
+          requestMap.remove(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY);
+        
 
         // Name of the managed bean that is the sub menu XMLMenuModel.
         String modelStr = expr.substring(expr.indexOf('{')+1,
@@ -407,11 +438,8 @@
       // later NPEs if menu model methods are called.
       _LOG.warning ("CREATE_TREE_WARNING: Empty Tree!");
 
-      // Create empty treeModel
-      ChildPropertyTreeModel treeModel = new ChildPropertyTreeModel();
-
-      // Put it in the map
-      _treeModelMap.put(_currentTreeModelMapKey, treeModel);
+      List<MenuNode> list = Collections.emptyList();
+      _menuList = list;
     }
     else
     {
@@ -421,14 +449,8 @@
       ChildPropertyTreeModel treeModel =
                     new ChildPropertyTreeModel(_menuList, "children");
 
-      // Put it in the map
-      _treeModelMap.put(_currentTreeModelMapKey, treeModel);
-
-      // If Model is the Root, then build Model's hashmaps
-      // and set them on the Root Model.
-      XMLMenuModel rootModel = getRootModel();
 
-      if (rootModel == getModel())
+      if (_isRootHandler)
       {
         _viewIdFocusPathMap = new HashMap<String,List<Object>>();
         _nodeFocusPathMap   = new HashMap<Object, List<Object>>();
@@ -440,13 +462,6 @@
         // Populate the maps
         _addToMaps(treeModel, _viewIdFocusPathMap, _nodeFocusPathMap, _idNodeMap);
 
-        // Cache the maps.  There is a possibility of multiple
-        // root models so we must cache the maps on a per root model
-        // basis.
-        _viewIdFocusPathMapMap.put(_currentTreeModelMapKey, _viewIdFocusPathMap);
-        _nodeFocusPathMapMap.put(_currentTreeModelMapKey, _nodeFocusPathMap);
-        _idNodeMapMap.put(_currentTreeModelMapKey, _idNodeMap);
-
         treeModel.setRowKey(oldPath);
       }
     }
@@ -459,7 +474,7 @@
    */
   public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey)
   {
-    return _viewIdFocusPathMapMap.get(modelKey);
+    return _viewIdFocusPathMap;
   }
 
   /**
@@ -469,7 +484,7 @@
    */
   public Map<Object, List<Object>> getNodeFocusPathMap(Object modelKey)
   {
-    return _nodeFocusPathMapMap.get(modelKey);
+    return _nodeFocusPathMap;
   }
 
   /**
@@ -479,7 +494,7 @@
    */
   public Map<String, Object> getIdNodeMap(Object modelKey)
   {
-    return _idNodeMapMap.get(modelKey);
+    return _idNodeMap;
   }
 
   /**
@@ -489,51 +504,53 @@
     */
   public TreeModel getTreeModel(String uri)
   {
-    TreeModel model = _treeModelMap.get(uri);
-
+     List<MenuNode> list = _menuList;
+     
     // If we have a cached model, return it.
-    if (model != null)
-      return model;
-
-    // Build a Tree model.  Parsing puts the tree model
-    // in the map, see method endDocument().
-    _currentTreeModelMapKey = uri;
+    if (list != null)
+      return new ChildPropertyTreeModel(list,"children");
 
-    try
+    synchronized(this)
     {
-      // Get a parser.  NOTE: we are using the jdk's 1.5 SAXParserFactory
-      // and SAXParser here.
-      SAXParser parser = _SAX_PARSER_FACTORY.newSAXParser();
-
-      // Call the local menu model's getStream() method. This is a model
-      // method so that it can be overridden by any model extending
-      // XmlMenuModel.
-      InputStream inStream = getModel().getStream(uri);
+      list = _menuList;
+      if (list == null)// double check inside lock
+      {
+        // Build a Tree model.  Parsing puts the tree model
+        // in the map, see method endDocument().
+        _currentTreeModelMapKey = uri;
+        try
+        {
+          // Get a parser.  NOTE: we are using the jdk's 1.5 SAXParserFactory
+          // and SAXParser here.
+          SAXParser parser = _SAX_PARSER_FACTORY.newSAXParser();
+
+          // Call the local menu model's getStream() method. This is a model
+          // method so that it can be overridden by any model extending
+          // XmlMenuModel.
+          InputStream inStream = getModel().getStream(uri);
 
-      // Parse the metadata
-      parser.parse(inStream, this);
+          // Parse the metadata
+          parser.parse(inStream, this);
 
-      inStream.close();
-    }
-    catch (SAXException saxex)
-    {
-      _LOG.severe ( "SAX Parse Exception parsing " + uri + ": " +
-                    saxex.getMessage(), saxex);
-    }
-    catch (IOException ioe)
-    {
-      _LOG.severe ( "Unable to open an InputStream to " + uri, ioe);
-    }
-    catch (IllegalArgumentException iae)
-    {
-      _LOG.severe("InputStream to " + iae + " is null", iae);
-    }
-    catch (ParserConfigurationException pce)
-    {
-      _LOG.severe ( "Unable to create SAX parser for " + uri, pce);
+          inStream.close();
+        } catch (SAXException saxex)
+        {
+          _LOG.severe("SAX Parse Exception parsing " + uri + ": " +
+              saxex.getMessage(), saxex);
+        } catch (IOException ioe)
+        {
+          _LOG.severe("Unable to open an InputStream to " + uri, ioe);
+        } catch (IllegalArgumentException iae)
+        {
+          _LOG.severe("InputStream to " + iae + " is null", iae);
+        } catch (ParserConfigurationException pce)
+        {
+          _LOG.severe("Unable to create SAX parser for " + uri, pce);
+        }
+        list = _menuList;
+      }
     }
-
-    return _treeModelMap.get(uri);
+    return new ChildPropertyTreeModel(list,"children");
   }
 
   /**
@@ -549,8 +566,11 @@
     Map<String, Object> requestMap =
       facesContext.getExternalContext().getRequestMap();
 
-    XMLMenuModel model = (XMLMenuModel) requestMap.get(getRootModelKey());
-    return model;
+    Map<String,XMLMenuModel> modelMap = (Map<String,XMLMenuModel>) requestMap.get(getRootModelKey());
+    if(!requestMap.containsKey(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY))
+      return modelMap.get( (this.getId() ) );
+    else
+      return modelMap.get((Integer) requestMap.get(XMLMenuModel.SHARED_MODEL_INDICATOR_KEY));
   }
 
   /**
@@ -630,6 +650,31 @@
     _currentTreeModelMapKey = uri;
   }
 
+  
+  public void setRootHandler(boolean isRoot)
+  {
+    _isRootHandler = isRoot;
+  }
+  
+  /**
+   * sets the id of this content handler
+   * 
+   * No synchronization necessary,let
+   * the first thread set the id,
+   * the rest of the threads will immediately
+   * see the new value and will not try to 
+   * set again.
+   */
+  public void setId(int id)
+  {
+    if(_id == -1)
+      _id = id;
+  }
+  
+  public int getId()
+  {
+    return _id;
+  }
   //=======================================================================
   // Package Private Methods
   //=======================================================================
@@ -996,7 +1041,7 @@
   //========================================================================
 
   private List<List<MenuNode>> _menuNodes;
-  private List<MenuNode>       _menuList;
+  private volatile List<MenuNode>       _menuList;
   private String _currentTreeModelMapKey;
   private int    _nodeDepth;
   private int    _skipDepth = -1;
@@ -1006,10 +1051,6 @@
   private String _resBundleName;
 
   private Map<String, String>    _attrMap;
-  private Map<String, TreeModel> _treeModelMap;
-  private Map<Object, Map<String, List<Object>>> _viewIdFocusPathMapMap;
-  private Map<Object, Map<String, Object>>       _idNodeMapMap;
-  private Map<Object, Map<Object, List<Object>>> _nodeFocusPathMapMap;
   private Stack<Object>             _saveDataStack;
   private Map<String, List<Object>> _viewIdFocusPathMap;
   private Map<Object, List<Object>> _nodeFocusPathMap;
@@ -1021,6 +1062,10 @@
 
   // Root Menu model's Session map key
   private String _rootModelKey  = null;
+  
+  private volatile boolean _isRootHandler;
+  
+  private volatile int _id  = -1;
 
   // Nodes
   private final static String _GROUP_NODE        = "groupNode";
@@ -1063,5 +1108,12 @@
   
   private final static TrinidadLogger _LOG =
                         TrinidadLogger.createTrinidadLogger(MenuContentHandlerImpl.class);
+  
+  private static final String _RECURSE_COUNTER =
+      "org.apache.myfaces.trinidadinternal.menu.MenuContentHandlerImpl._RECURSE_COUNTER";
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -5330432089846748485L;
 
 } // endclass MenuContentHandlerImpl

Modified: myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java?rev=763596&r1=763595&r2=763596&view=diff
==============================================================================
--- myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java (original)
+++ myfaces/trinidad/branches/1.2.11.2-branch/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java Thu Apr  9 10:48:00 2009
@@ -573,6 +573,107 @@
     _modelId = modelId;
   }
 
+  public int getRootId()
+  {
+	  return _rootId;
+  }
+  public void setRootId(int id)
+  {
+	  _rootId = id;
+  }
+
+  
+  public final String getLabelProperty()
+  {
+    return _label;
+  }
+
+  public final String getIconProperty()
+  {
+    return _icon;
+  }
+
+  public final List<MenuNode> getChildrenProperty()
+  {
+    return _children;
+  }
+
+  public final String getFocusViewIdProperty()
+  {
+    return _focusViewId;
+  }
+
+  public final String getRenderedProperty()
+  {
+    return _renderedStr;
+  }
+
+  public final String getDisabledProperty()
+  {
+    return _disabledStr;
+  }
+
+  public final String getVisibleProperty()
+  {
+    return _visibleStr;
+  }
+
+  public final String getReadOnlyProperty()
+  {
+    return _readOnlyStr;
+  }
+
+  public final String getHandlerIdProperty()
+  {
+    return _handlerId;
+  }
+
+  public final String getBundleKeyProperty()
+  {
+    return _bundleKey;
+  }
+
+  public final String getBundleNameProperty()
+  {
+    return _bundleName;
+  }
+
+  public final String getAccessKeyProperty()
+  {
+    return _accessKey;
+  }
+
+  public final String getIdProperty()
+  {
+    return _id;
+  }
+
+  public final String getModelIdProperty()
+  {
+    return _modelId;
+  }
+
+  
+  public final String getLabelAndAccessKeyProperty()
+  {
+    return _labelAndAccessKey;
+  }
+
+  public final String getDefaultFocusPathProperty()
+  {
+    return _defaultFocusPathStr;
+  }
+
+  public final String getRootModelKeyProperty()
+  {
+    return _rootModelKey;
+  }
+
+  public final int getRootIdProperty()
+  {
+    return _rootId;
+  }
+  
   /**
    * setResBundleKey - sets the name of the resource bundle used in
    * obtaining the node's label text. Used, along with the handerId,
@@ -655,11 +756,21 @@
     Map<String, Object> requestMap =
       facesContext.getExternalContext().getRequestMap();
 
-    XMLMenuModel model =  (XMLMenuModel) requestMap.get(getRootModelKey());
+    Map map =  (Map) requestMap.get(getRootModelKey());
+    XMLMenuModel model = (XMLMenuModel) map.get(getRootId());
     return model;
   }
 
   /**
+   * Construct a thread safe version
+   * of this object and return it.
+   * @return a thread safe copy of this object. 
+   */
+  protected MenuNode getThreadSafeCopy()
+  {
+    return null;
+  }
+  /**
    * _joinLabelAndAccessKey - takes a string label and string accessKey
    * and combines them into a single labelAndAccessKey string.
    *
@@ -778,4 +889,7 @@
 
   // Root Menu model's Request Map Key
   private String _rootModelKey  = null;
+  
+  private int _rootId;
+
 }