You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ar...@apache.org on 2010/07/26 20:41:00 UTC
svn commit: r979405 -
/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java
Author: arobinson74
Date: Mon Jul 26 18:41:00 2010
New Revision: 979405
URL: http://svn.apache.org/viewvc?rev=979405&view=rev
Log:
TRINIDAD-1836 Commit new patch
Added:
myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java (with props)
Added: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java?rev=979405&view=auto
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java (added)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java Mon Jul 26 18:41:00 2010
@@ -0,0 +1,1122 @@
+/*
+ * 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.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;
+
+import java.util.Map;
+
+import java.util.Stack;
+
+import javax.faces.context.FacesContext;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.myfaces.trinidad.logging.TrinidadLogger;
+import org.apache.myfaces.trinidad.menu.MenuNode;
+import org.apache.myfaces.trinidad.menu.ItemNode;
+import org.apache.myfaces.trinidad.menu.GroupNode;
+import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
+import org.apache.myfaces.trinidad.model.TreeModel;
+import org.apache.myfaces.trinidad.model.XMLMenuModel;
+import org.apache.myfaces.trinidad.model.XMLMenuModel.MenuContentHandler;
+
+import org.xml.sax.helpers.DefaultHandler;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Handler called by the SAXParser when parsing menu metadata
+ * as part of the XML Menu Model of Trinidad Faces.
+ * <p>
+ * This is called through the Services API (See XMLMenuModel.java) to
+ * keep the separation between API's and internal modules.
+ * <p>
+ * startElement() and endElement() are called as one would expect,
+ * at the start of parsing an element in the menu metadata file and
+ * at the end of parsing an element in the menu metadata file.
+ *
+ * The menu model is created as a List of itemNodes and groupNodes
+ * which is available to and used by the XMLMenuModel to create the
+ * TreeModel and internal Maps.
+ *
+ */
+ /*
+ * IMPORTANT NOTE: Much of the work and data structures used by the
+ * XMLMenuModel are created (and kept) in this class. This is necessarily the
+ * case because the scope of the XMLMenuModel is request. The
+ * MenuContentHandlerImpl is shared so it does not get rebuilt upon each
+ * request as the XMLMenuModel does. So the XMLMenuModel can get its data
+ * each time it is rebuilt (on each request) without having to reparse and
+ * recreate all of its data structures. It simply gets them from here.
+ *
+ * As well as the tree, three hashmaps are created in order to be able to
+ * resolve cases where multiple menu items cause navigation to the same viewId.
+ * All 3 of these maps are created after the metadata is parsed and the tree is
+ * built, in the _addToMaps method.
+ *
+ * o The first hashMap is called the viewIdFocusPathMap and is built by
+ * traversing the tree after it is built (see endDocument()).
+ * Each node's focusViewId is
+ * obtained and used as the key to an entry in the viewIdHashMap. An ArrayList
+ * is used as the entry's value and each item in the ArrayList is a node's
+ * rowkey from the tree. This allows us to have duplicate rowkeys for a single
+ * focusViewId which translates to a menu that contains multiple items pointing
+ * to the same page. In general, each entry will have an ArrayList of rowkeys
+ * with only 1 rowkey, AKA focus path.
+ * o The second hashMap is called the nodeFocusPathMap and is built at the
+ * same time the viewIdHashMap is built. Each entry's key is the actual node
+ * and the value is the row key. Since the model keeps track of the currently
+ * selected menu node, this hashmap can be used to resolve viewId's with
+ * multiple focus paths. Since we have the currently selected node, we just
+ * use this hashMap to get its focus path.
+ * o The third hashMap is called idNodeMap and is built at the same time as the
+ * previous maps. This map is populated by having each entry contain the node's
+ * id as the key and the actual node as the value. In order to keep track of
+ * the currently selected node in the case of a GET, the node's id is appended
+ * to the request URL as a parameter. The currently selected node's id is
+ * picked up and this map is used to get the actual node that is currently
+ * selected.
+ */
+public class MenuContentHandlerUsingApiImpl extends DefaultHandler
+ implements MenuContentHandler,Serializable
+{
+
+ /**
+ * Constructs a Menu Content Handler.
+ */
+ public MenuContentHandlerUsingApiImpl()
+ {
+ super();
+
+ }
+
+ /**
+ * Called by the SAX Parser at the start of parsing a document.
+ */
+ @Override
+ public void startDocument()
+ {
+ _nodeDepth = 0;
+ _menuNodes = new ArrayList<List<MenuNode>>();
+ _menuList = null;
+
+ // Handler Id will have to change also to be unique
+ _handlerId = Integer.toString(System.identityHashCode(_menuNodes));
+ }
+
+ /**
+ * Start the parsing of an node element entry in the menu metadata file.
+ * <p>
+ * If the entry is for an itemNode or a destinationNode, create the node
+ * and it to the List. If the entry is for a sharedNode, a new submenu
+ * model is created.
+ *
+ * @param nameSpaceUri - only used when passed to super class.
+ * @param localElemName - only used when passed to super class.
+ * @param qualifiedElemName - String designating the node type of the entry.
+ * @param attrList - List of attributes in the menudata entry.
+ * @throws SAXException
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public void startElement(String nameSpaceUri, String localElemName,
+ String qualifiedElemName, Attributes attrList)
+ throws SAXException
+ {
+ super.startElement(nameSpaceUri, localElemName, qualifiedElemName,
+ attrList);
+
+ if (_ROOT_NODE.equals(qualifiedElemName))
+ {
+ // Unless both of these are specified, don't attempt to load
+ // the resource bundle.
+ String resBundle = attrList.getValue(_RES_BUNDLE_ATTR);
+ String resBundleKey = attrList.getValue(_VAR_ATTR);
+
+ if ( (resBundle != null && !"".equals(resBundle))
+ && (resBundleKey != null && !"".equals(resBundleKey))
+ )
+ {
+ // Load the resource Bundle.
+ // Ensure the bundle key is unique by appending the
+ // handler Id.
+ MenuUtils.loadBundle(resBundle, resBundleKey + getHandlerId());
+ _resBundleKey = resBundleKey;
+ _resBundleName = resBundle;
+ }
+ }
+ else
+ {
+ // Either itemNode, destinationNode, or groupNode
+ boolean isNonSharedNode = ( _ITEM_NODE.equals(qualifiedElemName)
+ || _GROUP_NODE.equals(qualifiedElemName)
+ );
+
+ if (isNonSharedNode)
+ {
+ _currentNodeStyle = ( _ITEM_NODE.equals(qualifiedElemName)
+ ? MenuConstants.NODE_STYLE_ITEM
+ : MenuConstants.NODE_STYLE_GROUP
+ );
+ _nodeDepth++;
+
+ if ((_skipDepth >= 0) && (_nodeDepth > _skipDepth))
+ {
+ // This sub-tree is being skipped, so just return
+ return;
+ }
+
+ if (_menuNodes.size() < _nodeDepth)
+ {
+ _menuNodes.add(new ArrayList<MenuNode>());
+ }
+
+ _attrMap = _getMapFromList(attrList);
+
+ // Create either an itemNode or groupNode.
+ MenuNode menuNode = _createMenuNode();
+
+ if (menuNode == null)
+ {
+ // No menu item is created, so note that we are
+ // now skipping the subtree
+ _skipDepth = _nodeDepth;
+ }
+ else
+ {
+ if ( (_resBundleName != null && !"".equals(_resBundleName))
+ && (_resBundleKey != null && !"".equals(_resBundleKey))
+ )
+ {
+ menuNode.setResBundleKey(_resBundleKey);
+ menuNode.setResBundleName(_resBundleName);
+ }
+
+ // Set the node's MenuContentHandlerUsingApiImpl id so that when
+ // the node's getLabel() method is called, we can
+ // use the handlerId to insert into the label
+ // if it is an EL expression.
+ menuNode.setHandlerId(getHandlerId());
+
+ // Set the root model on the node so we can call into
+ // the root model from the node to populate its
+ // idNodeMap (See XMLMenuModel.java)
+ menuNode.setRootModelKey(getRootModelKey());
+
+ // Set the local model (created when parsing a sharedNode)
+ // on the node in case the node needs to get back to its
+ // 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.getThreadSafeCopy());
+ }
+ }
+ else if (_SHARED_NODE.equals(qualifiedElemName))
+ {
+ _nodeDepth++;
+
+ // SharedNode's "ref" property points to another submenu's metadata,
+ // and thus a new model, which we build here. Note: this will
+ // recursively call into this MenuContentHandlerUsingApiImpl 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.
+ _saveModelData();
+
+ // Create the sub menu model specified in the sharedNode
+ XMLMenuModel menuModel = (XMLMenuModel)MenuUtils.getBoundValue(expr,
+ Object.class);
+
+
+ // 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,
+ expr.indexOf('}'));
+
+ // There are 2 ways that a Model can be invalid:
+ // 1) Something such as a missing managed bean definition
+ // for the submenu model causes the creation of the
+ // XMLMenuModel for the submenu to fail. This will result
+ // in menuModel being NULL.
+ // 2) Some kind of parsing error in its metadata. If a node
+ // type is invalid, an exception will be thrown (see below)
+ // and caught in getTreeModel(). This will result in a
+ // null submenu list the following SAXException will also
+ // be logged.
+ if (menuModel != null)
+ {
+ Object subMenuObj = menuModel.getWrappedData();
+ List<MenuNode> subMenuList = null;
+
+ if (subMenuObj instanceof ChildPropertyTreeModel)
+ {
+ subMenuList =
+ (List<MenuNode>)((ChildPropertyTreeModel)subMenuObj).getWrappedData();
+ }
+
+ if (subMenuList != null)
+ {
+ // SharedNode could be the first child
+ // So we need a new list for the children
+ if (_menuNodes.size() < _nodeDepth)
+ {
+ _menuNodes.add(new ArrayList<MenuNode>());
+ }
+
+ List<MenuNode> list = _menuNodes.get(_nodeDepth-1);
+ list.addAll(subMenuList);
+ }
+ else
+ {
+ // Let it go through but log it. This way the rest of
+ // the Tree gets built and this submenu is skipped.
+ SAXException npe =
+ new SAXException("Shared Node Model not created for " + modelStr);
+ }
+ }
+ else
+ {
+ // Let it go through but log it. This way the rest of
+ // the Tree gets built and this submenu is skipped.
+ NullPointerException npe =
+ new NullPointerException("Shared Node Model not created for "
+ + modelStr + ". Check for the existence of the corresponding "
+ + "managed bean in your config files.");
+
+ _LOG.severe (npe.getMessage(), npe);
+ }
+ }
+ else
+ {
+ // Throw an Exception for any node that is not of type
+ // menu, itemNode, groupNode, or sharedNode. This will get
+ // caught in getTreeModel()
+ throw new SAXException("Invalid Node type: " + localElemName);
+ }
+ }
+ }
+
+ /**
+ * Processing done at the end of parsing a node enty element from the
+ * menu metadata file. This manages the node depth properly so that
+ * method startElement works correctly to build the List.
+ *
+ * @param nameSpaceUri - not used.
+ * @param localElemName - not used.
+ * @param qualifiedElemName - String designating the node type of the entry.
+ */
+ @Override
+ public void endElement(String nameSpaceUri, String localElemName, String qualifiedElemName)
+ {
+ if ( _ITEM_NODE.equals(qualifiedElemName)
+ || _GROUP_NODE.equals(qualifiedElemName)
+ )
+ {
+ _nodeDepth--;
+
+ if (_skipDepth >= 0)
+ {
+ if (_nodeDepth < _skipDepth)
+ {
+ _skipDepth = -1;
+ }
+ }
+ else
+ {
+ if (_nodeDepth > 0)
+ {
+ // The parent menu item is the last menu item at the previous depth
+ List<MenuNode> parentList = _menuNodes.get(_nodeDepth-1);
+ MenuNode parentNode = parentList.get(parentList.size()-1);
+
+ parentNode.setChildren(_menuNodes.get(_nodeDepth));
+ }
+
+ // If we have dropped back two levels, then we are done adding
+ // the parent's sub tree, remove the list of menu items at that level
+ // so they don't get added twice.
+ if (_nodeDepth == (_menuNodes.size() - 2))
+ {
+ _menuNodes.remove(_nodeDepth+1);
+ }
+ }
+ }
+ else if (_SHARED_NODE.equals(qualifiedElemName))
+ {
+ _nodeDepth--;
+
+ // In processing a sharedNode in startElement(), it is possible
+ // that a sharedNode model is not created properly. However,
+ // we only log an error and let parsing continue so that the whole
+ // Tree can get created w/o the failed sharedNode submenu model.
+ // Thus we need the 2nd conditional here to detect if we are at
+ // the end of parsing a failed sharedNode.
+ if (_nodeDepth > 0 && _menuNodes.size() > _nodeDepth)
+ {
+ // The parent menu item is the last menu item at the previous depth
+ List<MenuNode> parentList = _menuNodes.get(_nodeDepth-1);
+ MenuNode parentNode = parentList.get(parentList.size()-1);
+
+ parentNode.setChildren(_menuNodes.get(_nodeDepth));
+ }
+ }
+ }
+
+ /**
+ * Called by the SAX Parser at the end of parsing a document.
+ * Here, the menuList is put on the menuList map.
+ */
+ @Override
+ public void endDocument()
+ {
+ if (_menuNodes.isEmpty())
+ {
+ // Empty tree is created to prevent
+ // later NPEs if menu model methods are called.
+ _LOG.warning ("CREATE_TREE_WARNING: Empty Tree!");
+
+ List<MenuNode> list = Collections.emptyList();
+ _menuList = list;
+ }
+ else
+ {
+ _menuList = _menuNodes.get(0);
+
+ // Create the treeModel
+ ChildPropertyTreeModel treeModel =
+ new ChildPropertyTreeModel(_menuList, "children");
+
+
+ if (_isRootHandler)
+ {
+ _viewIdFocusPathMap = new HashMap<String,List<Object>>();
+ _nodeFocusPathMap = new HashMap<Object, List<Object>>();
+ _idNodeMap = new HashMap<String, Object>();
+ Object oldPath = treeModel.getRowKey();
+
+ treeModel.setRowKey(null);
+
+ // Populate the maps
+ _addToMaps(treeModel, _viewIdFocusPathMap, _nodeFocusPathMap, _idNodeMap);
+
+ treeModel.setRowKey(oldPath);
+ }
+ }
+ }
+
+ /**
+ * Get the Model's viewIdFocusPathMap
+ *
+ * @return the Model's viewIdFocusPathMap
+ */
+ public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey)
+ {
+ return _viewIdFocusPathMap;
+ }
+
+ /**
+ * Get the Model's nodeFocusPathMap
+ *
+ * @return the Model's nodeFocusPathMap
+ */
+ public Map<Object, List<Object>> getNodeFocusPathMap(Object modelKey)
+ {
+ return _nodeFocusPathMap;
+ }
+
+ /**
+ * Get the Model's idNodeMap
+ *
+ * @return the Model's idNodeMap
+ */
+ public Map<String, Object> getIdNodeMap(Object modelKey)
+ {
+ return _idNodeMap;
+ }
+
+ /**
+ * Get the treeModel built during parsing
+ *
+ * @return List of menu nodes.
+ */
+ public TreeModel getTreeModel(String uri)
+ {
+ List<MenuNode> list = _menuList;
+
+ // If we have a cached model, return it.
+ if (list != null)
+ return new ChildPropertyTreeModel(list,"children");
+
+ synchronized(this)
+ {
+ 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);
+
+ 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 new ChildPropertyTreeModel(list,"children");
+ }
+
+ /**
+ * Get the top-level, root menu model, which contains
+ * the entire menu tree.
+ *
+ * @return root, top-level XMLMenuModel
+ */
+ @SuppressWarnings("unchecked")
+ public XMLMenuModel getRootModel()
+ {
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ Map<String, Object> requestMap =
+ facesContext.getExternalContext().getRequestMap();
+
+ 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));
+ }
+
+ /**
+ * Get the top-level, root menu model's Request Map Key.
+ *
+ * @return root, top-level XMLMenuModel's Request Map Key.
+ */
+ public String getRootModelKey()
+ {
+ return _rootModelKey;
+ }
+
+ /**
+ * Sets the root menu Model's Request map key.
+ * <p>
+ * This is always only the top-level, root model's Request map key.
+ * We do this because the MenuContentHandlerUsingApiImpl and nodes need to be able
+ * to call into the root model to:
+ * <ul>
+ * <li>notify them root menu model of the currently selected node on a POST
+ * <li>group node needs to find its referenced item node.
+ * </ul>
+ *
+ * @param rootModelKey - String the root, top-level menu model's Request
+ * map key.
+ */
+ public void setRootModelKey(String rootModelKey)
+ {
+ _rootModelKey = rootModelKey;
+ }
+
+ /**
+ * Get the local (sharedNode) menu model.
+ *
+ * @return sharedNode's XMLMenuModel
+ */
+ @SuppressWarnings("unchecked")
+ public XMLMenuModel getModel()
+ {
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ Map<String, Object> requestMap =
+ facesContext.getExternalContext().getRequestMap();
+
+ return (XMLMenuModel) requestMap.get(getModelId());
+ }
+
+ /**
+ * Get the local (sharedNode) menu model's Uri.
+ *
+ * @return sharedNode's XMLMenuModel Uri
+ */
+ public String getModelId()
+ {
+ return _localModelId;
+ }
+
+ /**
+ * Sets the local (sharedNode's) menu Model's System Id.
+ *
+ * @param localModelId - String the root, top-level menu model's Uri.
+ */
+ public void setModelId(String localModelId)
+ {
+ _localModelId = localModelId;
+ }
+
+ /**
+ * Sets the treeModel map key used to get a cached treeModel
+ * from the MenuContentHandlerUsingApiImpl.
+ *
+ * Note: this is set from the XMLMenuModel BEFORE parsing begins
+ *
+ * @param uri String path to the menu model's metadata
+ */
+ public void setTreeModelKey(String uri)
+ {
+ _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
+ //=======================================================================
+ /**
+ * Gets the MenuContentHandlerUsingApiImpl's id.
+ *
+ * This is set in the MenuContentHandlerUsingApiImpl's Constructor
+ * and is used to ensure that the all resource bundle keys
+ * and node ids are unique.
+ *
+ * @return String handler id.
+ */
+ String getHandlerId()
+ {
+ return _handlerId;
+ }
+
+ /**
+ * Returns the hashmap key for a resource bundle.
+ *
+ * This the value of the "var" attribute for the menu root node
+ * from the menu's metadata
+ *
+ * @return String hashmap key.
+ */
+ String getBundleKey()
+ {
+ return _resBundleKey;
+ }
+
+ //=======================================================================
+ // Private Methods
+ //=======================================================================
+
+ /**
+ * Create a Map of name/value pairs from the attrList given
+ * to us by the Sax parser.
+ *
+ * @param attrList List of attributes of an XML element
+ * @return Map hashMap of attributes converted to name/value pairs.
+ */
+ @SuppressWarnings("unchecked")
+ private Map<String, String> _getMapFromList(Attributes attrList)
+ {
+ Map<String, String> attrMap = new HashMap<String, String>();
+
+ for (int i=0; i < attrList.getLength(); i++)
+ {
+ attrMap.put(attrList.getQName(i), attrList.getValue(i) );
+ }
+
+ return attrMap;
+ }
+
+ /**
+ * Creates a MenuNode from attribute list.
+ *
+ * @return MenuNode used in the Menu List.
+ */
+ private MenuNode _createMenuNode ()
+ {
+ // Get generic attributes
+
+ // If the node has rendered = false, do not create it.
+ // This is a security risk and cannot be allowed
+ String renderedStr = _getAndRemoveAttrValue(_RENDERED_ATTR);
+
+ // We do not create nodes whose rendered attr is false
+ // and if the Root model or the local model's (sharedNode
+ // model) says that nodes whose rendered attribute is false
+ // should not be created, then we don't either.
+ //
+ // This default value of false (don't create nodes whose
+ // rendered attr is false) can be overridden by the
+ // XMLMenuModel's managed property, createHiddenNodes.
+ // Typically this is done in faces-config.xml
+ //
+ if ( "false".equals(renderedStr)
+ && ( !getRootModel().getCreateHiddenNodes()
+ || !getModel().getCreateHiddenNodes()
+ )
+ )
+ {
+ return null;
+ }
+
+ String label = _getAndRemoveAttrValue(_LABEL_ATTR);
+ String icon = _getAndRemoveAttrValue(_ICON_ATTR);
+ String disabledStr = _getAndRemoveAttrValue(_DISABLED_ATTR);
+ String readOnlyStr = _getAndRemoveAttrValue(_READONLY_ATTR);
+ String accessKey = _getAndRemoveAttrValue(_ACCESSKEY_ATTR);
+ String labelAndAccessKey = _getAndRemoveAttrValue(_LABEL_AND_ACCESSKEY_ATTR);
+ String id = _getAndRemoveAttrValue(_ID_ATTR);
+ String visibleStr = _getAndRemoveAttrValue(_VISIBLE_ATTR);
+
+ MenuNode menuNode = ( _currentNodeStyle == MenuConstants.NODE_STYLE_ITEM
+ ? _createItemNode()
+ : _createGroupNode()
+ );
+
+ // Set the generic attributes
+ menuNode.setLabel(label);
+ menuNode.setIcon(icon);
+ menuNode.setDisabled(disabledStr);
+ menuNode.setRendered(renderedStr);
+ menuNode.setReadOnly(readOnlyStr);
+ menuNode.setAccessKey(accessKey);
+ menuNode.setId(id);
+ menuNode.setVisible(visibleStr);
+
+ if (labelAndAccessKey != null)
+ menuNode.setLabelAndAccessKey(labelAndAccessKey);
+
+ return menuNode;
+ }
+
+ /**
+ * Creates an itemNode from attribute list obtained by parsing an
+ * itemNode menu metadata entry.
+ *
+ * @return Node of type ItemNode.
+ */
+ private ItemNode _createItemNode()
+ {
+ // Create the itemNode
+ ItemNode itemNode = new ItemNode();
+
+ String action = _getAndRemoveAttrValue(_ACTION_ATTR);
+ String actionListener = _getAndRemoveAttrValue(_ACTIONLISTENER_ATTR);
+ String launchListener = _getAndRemoveAttrValue(_LAUNCHLISTENER_ATTR);
+ String returnListener = _getAndRemoveAttrValue(_RETURNLISTENER_ATTR);
+ String immediate = _getAndRemoveAttrValue(_IMMEDIATE_ATTR);
+ String useWindow = _getAndRemoveAttrValue(_USEWINDOW_ATTR);
+ String windowHeight = _getAndRemoveAttrValue(_WINDOWHEIGHT_ATTR);
+ String windowWidth = _getAndRemoveAttrValue(_WINDOWWIDTH_ATTR);
+ String defaultFocusPathStr = _getAndRemoveAttrValue(_DEFAULT_FOCUS_PATH_ATTR);
+ String focusViewId = _getAndRemoveAttrValue(_FOCUS_VIEWID_ATTR);
+
+ // Former Destination node attrs
+ String destination = _getAndRemoveAttrValue(_DESTINATION_ATTR);
+ String targetFrame = _getAndRemoveAttrValue(_TARGETFRAME_ATTR);
+
+ // An item node with one of two(2) possible values:
+ // 1) outcome
+ // 2) EL method binding (which can return either a URI or
+ // an outcome
+
+ // Set its properties - null is ok.
+ itemNode.setAction(action);
+ itemNode.setActionListener(actionListener);
+ itemNode.setLaunchListener(launchListener);
+ itemNode.setReturnListener(returnListener);
+ itemNode.setImmediate(immediate);
+ itemNode.setUseWindow(useWindow);
+ itemNode.setWindowHeight(windowHeight);
+ itemNode.setWindowWidth(windowWidth);
+ itemNode.setFocusViewId(focusViewId);
+ itemNode.setDefaultFocusPath(defaultFocusPathStr);
+
+ // Former destination node attrs
+ itemNode.setDestination(destination);
+ itemNode.setTargetFrame(targetFrame);
+
+ // Set the Any Attributes Attrlist
+ if (_attrMap.size() > 0)
+ {
+ itemNode.setCustomPropList(_attrMap);
+ }
+
+ return itemNode;
+ }
+
+ /**
+ * Creates a GroupNode from attribute list passed obtained by parsing
+ * a GroupNode menu metadata entry.
+ *
+ * @return Node of type GroupNode
+ */
+ private GroupNode _createGroupNode()
+ {
+ // Create the GroupNode
+ GroupNode groupNode = new GroupNode();
+ String idRef = _getAndRemoveAttrValue(_IDREF_ATTR);
+
+ // Set its attributes - null is ok
+ groupNode.setIdRef(idRef);
+
+ return groupNode;
+ }
+
+ /**
+ * Saves all information needed for parsing and building model data
+ * before recursing into the new model of a sharedNode.
+ *
+ * Note: if you add a new push in this method, you must also add
+ * a corresponding pop in _restoreModelData() below in the correct order.
+ */
+ @SuppressWarnings("unchecked")
+ private void _saveModelData()
+ {
+ if (_saveDataStack == null)
+ {
+ _saveDataStack = new Stack<Object>();
+ }
+
+ // DO NOT CHANGE THE ORDER HERE. IT MUST MATCH
+ // "pops" DONE BELOW in _restoreModelData.
+ int nodeDepthSave = _nodeDepth;
+ ArrayList<List<MenuNode>> menuNodesSave =
+ new ArrayList<List<MenuNode>>(_menuNodes);
+
+
+ ArrayList<Object> menuListSave =
+ ( _menuList != null
+ ? new ArrayList<Object>(_menuList)
+ : null
+ );
+
+ String mapTreeKeySave = _currentTreeModelMapKey;
+ String localModelIdSave = _localModelId;
+ String handlerId = _handlerId;
+ String resBundleName = _resBundleName;
+ String resBundleKey = _resBundleKey;
+ _saveDataStack.push(nodeDepthSave);
+ _saveDataStack.push(menuNodesSave);
+ _saveDataStack.push(menuListSave);
+ _saveDataStack.push(mapTreeKeySave);
+ _saveDataStack.push(localModelIdSave);
+ _saveDataStack.push(handlerId);
+ _saveDataStack.push(resBundleName);
+ _saveDataStack.push(resBundleKey);
+ }
+
+ /**
+ * Restores data needed for parsing and building model data
+ * as execution returns from creating a sharedNode child menu model.
+ *
+ * Note: if you add a new pop in this method, you must also add
+ * a corresponding push in _saveModelData() above in the correct order.
+ */
+ @SuppressWarnings("unchecked")
+ private void _restoreModelData()
+ {
+ // DO NOT CHANGE THE ORDER HERE. IT MUST MATCH
+ // "pushes" DONE ABOVE in _saveModelData.
+ _resBundleKey = (String) _saveDataStack.pop();
+ _resBundleName = (String) _saveDataStack.pop();
+ _handlerId = (String) _saveDataStack.pop();
+ _localModelId = (String) _saveDataStack.pop();
+ _currentTreeModelMapKey = (String) _saveDataStack.pop();
+ _menuList = (ArrayList<MenuNode>) _saveDataStack.pop();
+ _menuNodes = (ArrayList<List<MenuNode>>) _saveDataStack.pop();
+ _nodeDepth = ((Integer)_saveDataStack.pop()).intValue();
+ }
+
+ /**
+ * Gets the specified attribute's value from the Attributes List
+ * passed in by the parser. Also removes this attribute so that
+ * once we are finished processing and removing all the known
+ * attributes, those left are custom attributes.
+ *
+ * @param attrName
+ * @return String value of the attribute in the Attributes List.
+ */
+ private String _getAndRemoveAttrValue(String attrName)
+ {
+ String attrValue = _attrMap.get(attrName);
+
+ if (attrValue != null)
+ _attrMap.remove(attrName);
+
+ return attrValue;
+ }
+
+ /*=========================================================================
+ * Menu Model Data Structure section.
+ * ======================================================================*/
+ /**
+ * Traverses the tree and builds the model's viewIdFocusPathMap,
+ * nodeFocusPathMap, and _idNodeMap
+ *
+ * @param tree
+ */
+ @SuppressWarnings("unchecked")
+ private void _addToMaps(
+ TreeModel tree,
+ Map viewIdFocusPathMap,
+ Map nodeFocusPathMap,
+ Map idNodeMap)
+ {
+ for ( int i = 0; i < tree.getRowCount(); i++)
+ {
+ tree.setRowIndex(i);
+
+ // Get the node
+ MenuNode node = (MenuNode) tree.getRowData();
+
+ // Get its focus path
+ List<Object> focusPath = (List<Object>)tree.getRowKey();
+
+ // Get the focusViewId of the node
+ Object viewIdObject = node.getFocusViewId();
+
+ if (viewIdObject != null)
+ {
+ // Put this entry in the nodeFocusPathMap
+ nodeFocusPathMap.put(node, focusPath);
+
+ // Does this viewId already exist in the _viewIdFocusPathMap?
+ List<Object> existingFpArrayList =
+ _viewIdFocusPathMap.get(viewIdObject);
+
+ if (existingFpArrayList == null)
+ {
+ // This is a node with a unique focusViewId. Simply create
+ // and Arraylist and add the focusPath as the single entry
+ // to the focus path ArrayList. Then put the focusPath
+ // ArrayList in the focusPath HashMap.
+ List<Object> fpArrayList = new ArrayList<Object>();
+ fpArrayList.add(focusPath);
+ viewIdFocusPathMap.put(viewIdObject, fpArrayList);
+ }
+ else
+ {
+ // This is a node that points to the same viewId as at least one
+ // other node.
+
+ // If the node's defaultFocusPath is set to true, we move it to
+ // the head of the ArrayList. The 0th element of the list is
+ // always returned when navigation to a viewId occurs from outside
+ // the menu model (that is _currentNode is null)
+ boolean defFocusPath = node.getDefaultFocusPath();
+
+ if (defFocusPath)
+ {
+ existingFpArrayList.add(0, focusPath);
+ }
+ else
+ {
+ existingFpArrayList.add(focusPath);
+ }
+ }
+ }
+
+ // Get the Id of the node
+ String idProp = node.getUniqueId();
+
+ if (idProp != null)
+ {
+ idNodeMap.put(idProp, node);
+ }
+
+ if (tree.isContainer() && !tree.isContainerEmpty())
+ {
+ tree.enterContainer();
+ _addToMaps(tree, viewIdFocusPathMap, nodeFocusPathMap, idNodeMap);
+ tree.exitContainer();
+ }
+ }
+ }
+
+ //========================================================================
+ // Private variables
+ //========================================================================
+
+ private List<List<MenuNode>> _menuNodes;
+ private volatile List<MenuNode> _menuList;
+ private String _currentTreeModelMapKey;
+ private int _nodeDepth;
+ private int _skipDepth = -1;
+ private String _currentNodeStyle;
+ private String _handlerId;
+ private String _resBundleKey;
+ private String _resBundleName;
+
+ private Map<String, String> _attrMap;
+ private Stack<Object> _saveDataStack;
+ private Map<String, List<Object>> _viewIdFocusPathMap;
+ private Map<Object, List<Object>> _nodeFocusPathMap;
+ private Map<String, Object> _idNodeMap;
+
+
+ // Local (shared) Menu models Uri
+ private String _localModelId = null;
+
+ // 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";
+ private final static String _ITEM_NODE = "itemNode";
+ private final static String _SHARED_NODE = "sharedNode";
+ private final static String _ROOT_NODE = "menu";
+
+ // Attributes
+ private final static String _LABEL_ATTR = "label";
+ private final static String _RENDERED_ATTR = "rendered";
+ private final static String _ID_ATTR = "id";
+ private final static String _IDREF_ATTR = "idref";
+ private final static String _ICON_ATTR = "icon";
+ private final static String _DISABLED_ATTR = "disabled";
+ private final static String _DESTINATION_ATTR = "destination";
+ private final static String _ACTION_ATTR = "action";
+ private final static String _REF_ATTR = "ref";
+ private final static String _READONLY_ATTR = "readOnly";
+ private final static String _VAR_ATTR = "var";
+ private final static String _RES_BUNDLE_ATTR = "resourceBundle";
+ private final static String _FOCUS_VIEWID_ATTR = "focusViewId";
+ private final static String _ACCESSKEY_ATTR = "accessKey";
+ private final static String _LABEL_AND_ACCESSKEY_ATTR = "labelAndAccessKey";
+ private final static String _TARGETFRAME_ATTR = "targetframe";
+ private final static String _ACTIONLISTENER_ATTR = "actionListener";
+ private final static String _LAUNCHLISTENER_ATTR = "launchListener";
+ private final static String _RETURNLISTENER_ATTR = "returnListener";
+ private final static String _IMMEDIATE_ATTR = "immediate";
+ private final static String _USEWINDOW_ATTR = "useWindow";
+ private final static String _WINDOWHEIGHT_ATTR = "windowHeight";
+ private final static String _WINDOWWIDTH_ATTR = "windowWidth";
+ private final static String _DEFAULT_FOCUS_PATH_ATTR = "defaultFocusPath";
+ private final static String _VISIBLE_ATTR = "visible";
+
+ private static final SAXParserFactory _SAX_PARSER_FACTORY;
+ static
+ {
+ _SAX_PARSER_FACTORY = SAXParserFactory.newInstance();
+ }
+
+ private final static TrinidadLogger _LOG =
+ TrinidadLogger.createTrinidadLogger(MenuContentHandlerUsingApiImpl.class);
+
+ private static final String _RECURSE_COUNTER =
+ "org.apache.myfaces.trinidadinternal.menu.MenuContentHandlerUsingApiImpl._RECURSE_COUNTER";
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+} // endclass MenuContentHandlerUsingApiImpl
Propchange: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerUsingApiImpl.java
------------------------------------------------------------------------------
svn:eol-style = native