/*
* 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.util.ArrayList;
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.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 MenuContentHandlerImpl extends DefaultHandler
implements MenuContentHandler
{
/**
* Constructs a Menu Content Handler.
*/
public MenuContentHandlerImpl()
{
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>>();
}
/**
* 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 MenuContentHandlerImpl 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.setModel(getModel());
List<MenuNode> list = _menuNodes.get(_nodeDepth-1);
list.add(menuNode);
}
}
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 MenuContentHandlerImpl when parsing the
// submenu's metadata.
String expr = attrList.getValue(_REF_ATTR);
// 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);
// Now must pop the values cause we are back to the parent
// model.
_restoreModelData();
// 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!");
// Create empty treeModel
ChildPropertyTreeModel treeModel = new ChildPropertyTreeModel();
// Put it in the map
_treeModelMap.put(_currentTreeModelMapKey, treeModel);
}
else
{
_menuList = _menuNodes.get(0);
// Create the treeModel
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())
{
_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);
// 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);
}
}
}
/**
* Get the Model's viewIdFocusPathMap
*
* @return the Model's viewIdFocusPathMap
*/
public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey)
{
return _viewIdFocusPathMapMap.get(modelKey);
}
/**
* Get the Model's nodeFocusPathMap
*
* @return the Model's nodeFocusPathMap
*/
public Map<Object, List<Object>> getNodeFocusPathMap(Object modelKey)
{
return _nodeFocusPathMapMap.get(modelKey);
}
/**
* Get the Model's idNodeMap
*
* @return the Model's idNodeMap
*/
public Map<String, Object> getIdNodeMap(Object modelKey)
{
return _idNodeMapMap.get(modelKey);
}
/**
* Get the treeModel built during parsing
*
* @return List of menu nodes.
*/
public TreeModel getTreeModel(String uri)
{
TreeModel model = _treeModelMap.get(uri);
// 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;
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);
}
return _treeModelMap.get(uri);
}
/**
* 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();
XMLMenuModel model = (XMLMenuModel) requestMap.get(getRootModelKey());
return model;
}
/**
* 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 MenuContentHandlerImpl 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(getModelUri());
}
/**
* Get the local (sharedNode) menu model's Uri.
*
* @return sharedNode's XMLMenuModel Uri
*/
public String getModelUri()
{
return _localModelUri;
}
/**
* Sets the local (sharedNode's) menu Model's Uri.
*
* @param localModelUri - String the root, top-level menu model's Uri.
*/
public void setModelUri(String localModelUri)
{
_localModelUri = localModelUri;
}
/**
* Sets the treeModel map key used to get a cached treeModel
* from the MenuContentHandlerImpl.
*
* 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;
}
//=======================================================================
// Package Private Methods
//=======================================================================
/**
* Gets the MenuContentHandlerImpl's id.
*
* This is set in the MenuContentHandlerImpl'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 localModelUriSave = _localModelUri;
String handlerId = _handlerId;
String resBundleName = _resBundleName;
String resBundleKey = _resBundleKey;
_saveDataStack.push(nodeDepthSave);
_saveDataStack.push(menuNodesSave);
_saveDataStack.push(menuListSave);
_saveDataStack.push(mapTreeKeySave);
_saveDataStack.push(localModelUriSave);
_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();
_localModelUri = (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 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 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;
private Map<String, Object> _idNodeMap;
// Local (shared) Menu models Uri
private String _localModelUri = null;
// Root Menu model's Session map key
private String _rootModelKey = null;
// 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(MenuContentHandlerImpl.class);
} // endclass MenuContentHandlerImpl