org.apache.myfaces.trinidadinternal.menu.MenuContentHandlerUsingApiImpl Maven / Gradle / Ivy
Show all versions of trinidad-impl Show documentation
/*
* 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.
*
* This is called through the Services API (See XMLMenuModel.java) to
* keep the separation between API's and internal modules.
*
* 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>();
_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.
*
* 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());
}
_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 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 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 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 subMenuList = null;
if (subMenuObj instanceof ChildPropertyTreeModel)
{
subMenuList =
(List)((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());
}
List 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 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 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 list = Collections.emptyList();
_menuList = list;
}
else
{
_menuList = _menuNodes.get(0);
// Create the treeModel
ChildPropertyTreeModel treeModel =
new ChildPropertyTreeModel(_menuList, "children");
if (_isRootHandler)
{
_viewIdFocusPathMap = new HashMap>();
_nodeFocusPathMap = new HashMap