From adffaces-commits-return-518-apmail-incubator-adffaces-commits-archive=incubator.apache.org@incubator.apache.org Tue Aug 01 19:59:57 2006 Return-Path: Delivered-To: apmail-incubator-adffaces-commits-archive@locus.apache.org Received: (qmail 26025 invoked from network); 1 Aug 2006 19:59:57 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 1 Aug 2006 19:59:57 -0000 Received: (qmail 84507 invoked by uid 500); 1 Aug 2006 19:59:56 -0000 Delivered-To: apmail-incubator-adffaces-commits-archive@incubator.apache.org Received: (qmail 84494 invoked by uid 500); 1 Aug 2006 19:59:56 -0000 Mailing-List: contact adffaces-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: adffaces-dev@incubator.apache.org Delivered-To: mailing list adffaces-commits@incubator.apache.org Received: (qmail 84485 invoked by uid 99); 1 Aug 2006 19:59:56 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 01 Aug 2006 12:59:56 -0700 X-ASF-Spam-Status: No, hits=-9.4 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received-SPF: pass (asf.osuosl.org: local policy) Received: from [140.211.166.113] (HELO eris.apache.org) (140.211.166.113) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 01 Aug 2006 12:59:52 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id B18FC1A981D; Tue, 1 Aug 2006 12:59:32 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r427701 [1/2] - in /incubator/adffaces/trunk/trinidad: trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ Date: Tue, 01 Aug 2006 19:59:31 -0000 To: adffaces-commits@incubator.apache.org From: awiner@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20060801195932.B18FC1A981D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N Author: awiner Date: Tue Aug 1 12:59:30 2006 New Revision: 427701 URL: http://svn.apache.org/viewvc?rev=427701&view=rev Log: Add initial revision of XMLMenuModel Added: incubator/adffaces/trunk/trinidad/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuConstants.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuNode.java incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuUtils.java Added: incubator/adffaces/trunk/trinidad/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java URL: http://svn.apache.org/viewvc/incubator/adffaces/trunk/trinidad/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java?rev=427701&view=auto ============================================================================== --- incubator/adffaces/trunk/trinidad/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java (added) +++ incubator/adffaces/trunk/trinidad/trinidad-api/src/main/java/org/apache/myfaces/trinidad/model/XMLMenuModel.java Tue Aug 1 12:59:30 2006 @@ -0,0 +1,687 @@ +/* + * @(#)XMLMenuModel.java + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.trinidad.model; + +import java.io.Serializable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.faces.context.FacesContext; + +import javax.faces.el.PropertyResolver; + +import javax.faces.el.ValueBinding; +import javax.faces.webapp.UIComponentTag; + +import org.apache.myfaces.trinidad.logging.TrinidadLogger; + +import org.xml.sax.Attributes; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.myfaces.trinidad.util.ClassLoaderUtils; + +/** + * Creates a Menu Model from a TreeModel where nodes in the treeModel + * contain viewId information. + *

+ * Each node must have either a bean getter method or a Map property + * that returns a viewId. There are several restrictions on the data: + *

    + * o The nodes in the tree must either be all beans or all maps, + * but not a mix of beans and maps. + * o The viewId of a node can be null, but if set it must be unique. + * o The tree cannot be mutable. + *
+ *

+ * The getFocusRowKey method + *

    + * o gets the current viewId by calling + * FacesContext.getCurrentInstance().getViewRoot().getViewId() + * o compares the current viewId with the viewId's in the viewIdFocusPathMap + * that was built by traversing the tree when the model was created. + * o returns the focus path to the node with the current viewId or null if the + * current viewId can't be found. + * o in the case where a viewId has multiple focus paths, the currently + * selected node is used as a key into the nodeFocusPathMap to return the + * correct focus path. + *
+ *

+ * The Model is created by specifying it in the faces-config.xml file + * as follows + *

+ *   <managed-bean>
+ *    <managed-bean-name>hr_menu</managed-bean-name>
+ *    <managed-bean-class>
+ *      org.apache.myfaces.trinidad.model.XMLMenuModel
+ *    </managed-bean-class>
+ *    <managed-bean-scope>request</managed-bean-scope>
+ *    <managed-property>
+ *      <property-name>source</property-name>
+ *      <property-class>java.lang.String</property-class>
+ *      <value>/WEB-INF/hr-menu.xml</value>
+ *    </managed-property>
+ *  </managed-bean>
+ * 
+ * + * @author Gary Kind + * + */ + +/* + * Three hashmaps are also 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 + * MenuContentHandlerImpl. + * + * o The first hashMap is called the viewIdFocusPathMap and is built by + * traversing the tree when the model is created. 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. + * + * Keeping track of the currently selected menu item/node. + * + * If an itemNode in the metadata uses its "action" attribute, a POST is done + * and the node's "doAction" method is called when the menu item is clicked. At + * that time, the model is notified through its setCurrentlyPostedNode() method, + * where the current node is set and the request method is set to POST. + * + * If an itemNode in the metadata uses its "destination" attribute, a GET is + * done. Nothing is called on the model when the menu item is clicked. However + * at the time the page is rendered the "getDestination" method for all nodes + * using the "destination" attribute is called. At this point + * we append the node's id to the value of the destination attribute URL, as + * a parameter, and return it. So when getFocusRowKey() is called, we get the + * request the node's parameter matching the currently selected node's id. + * Using the node id, we find the matching node in the idNodeMap and voila, we + * have the currently selected node! + */ +public class XMLMenuModel extends BaseMenuModel + implements Serializable +{ + public XMLMenuModel() + { + super(); + } + + /** + * setSource - specifies the XML metadata and creates + * the XML Menu Model. + * + * @param menuMetadataUri - String URI to the XML metadata. + */ + public void setSource(String menuMetadataUri) + { + if (menuMetadataUri == null || "".equals(menuMetadataUri)) + return; + + _mdSource = menuMetadataUri; + _createModel(); + } + + /** + * Makes the TreeModel part of the menu model. Also creates the + * _viewIdFocusPathMap, _nodeFocusPathMap, and idNodeMaps. + * + * @param data. The Tree Model instance + */ + + public void setWrappedData(Object data) + { + super.setWrappedData(data); + + // The only thing the child menu models are needed for are their + // menuLists, which get incorporated into the Root Model's tree. + // 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 (_mdSource.equals(_getRootUri())) + { + _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(); + _nodeFocusPathMap = _contentHandler.getNodeFocusPathMap(); + _idNodeMap = _contentHandler.getIdNodeMap(); + } + } + + /** + * Returns the rowKey to the current viewId, or in the case of where the + * model has nodes with duplicate viewId's and one is encountered, we + * return the rowKey of the currently selected node. + *

+ * + * The getFocusRowKey method + *

    + *
  • gets the current viewId by calling + * FacesContext.getCurrentInstance().getViewRoot().getViewId() + *
  • compares the current viewId with the viewId's in the viewIdFocusPathMap + * that was built by traversing the tree when the model was created. + *
  • returns the focus path to the node with the current viewId or null if + * the current viewId can't be found. + *
  • in the case where a viewId has multiple focus paths, the currently + * selected node is used as a key into the nodeFocusPathMap to return the + * correct focus path. + *
+ * + * @return the rowKey to the node with the current viewId or null if the + * current viewId can't be found. + */ + + public Object getFocusRowKey() + { + Object focusPath = null; + String currentViewId = _getCurrentViewId(); + FacesContext context = FacesContext.getCurrentInstance(); + boolean beginNewRequest = (_begunRequest == false); + + _begunRequest = true; + + + if (beginNewRequest) + { + // Initializations + _prevFocusPath = null; + + // How did we get to this page? + // 1) Clicked on a menu item with its action attribute set. This does + // a POST. + // 2) Clicked on a menu item with its destination attribute set. This + // does a GET. + // 3) Navigation to a viewId within our model but done from outside the + // model. Examples, button, text link, etc. + // + + // Case 1: POST method. Current Node has already been set and so has the + // request method. The doAction() method of the clicked node calls + // the setCurrentlyPostedNode() method of this model, which sets both. So + // we have nothing to do in this case. + + if (_getRequestMethod() != _METHOD_POST) + { + // Case 2: GET method. We have hung the selected node's id off the + // requests URL, which enables us to get the selected node and also + // to know that the request method is GET. + Map paramMap = context.getExternalContext().getRequestParameterMap(); + String nodeId = (String) paramMap.get(_NODE_ID_PROPERTY); + + if (nodeId != null) + { + _setCurrentlySelectedNode(getNode(nodeId)); + _setRequestMethod(_METHOD_GET); + } + } + + // Case 3: Navigation to a page within the model from an outside + // method, e.g. button, link text, etc. In this case we set the + // currently selected node to null. This tells us to get the 0th + // element of the ArrayList returned from the viewId hashMap. This + // should be a focus path match to the node whose "defaultFocusPath" + // attribute was set to 'true'. + if (_getRequestMethod() == _METHOD_NONE) + { + _setCurrentlySelectedNode(null); + } + + // Get the matching focus path ArrayList for the currentViewId. + // This is an ArrayList because our map allows nodes with the same + // viewId, that is, different focus paths to the same viewId. + ArrayList fpArrayList = (ArrayList) _viewIdFocusPathMap.get(currentViewId); + + if (fpArrayList != null) + { + // Get the currently selected node + Object currentNode = _getCurrentlySelectedNode(); + + if (fpArrayList.size() == 1 || currentNode == null) + { + // For fpArrayLists with multiple focusPaths, + // the 0th entry in the fpArrayList carries the + // focusPath of the node with its defaultFocusPath + // attribute set to "true", if there is one. If + // not, the 0th element is the default. + focusPath = fpArrayList.get(0); + } + else + { + focusPath = _nodeFocusPathMap.get(currentNode); + } + } + + // Save all pertinent information + _prevFocusPath = focusPath; + + _setRequestMethod(_METHOD_NONE); + } + else + { + // Not at the beginning of a new Request. + // Return the previous focus path. + // This optimization is here because, for each menu + // item selected, getFocusRowKey gets called multiple times. + return _prevFocusPath; + } + + return focusPath; + } + + + /** + * Gets the URI to the XML menu metadata. + * + * @return String URI to the XML menu metadata. + */ + public String getSource() + { + return _mdSource; + } + + /** + * Sets the boolean value that determines whether or not to create + * nodes whose rendered attribute value is false. The default + * value is false. + * + * This is set through a managed property of the XMLMenuModel + * managed bean -- typically in the faces-config.xml file for + * a faces application. + */ + public void setCreateHiddenNodes(boolean createHiddenNodes) + { + _createHiddenNodes = createHiddenNodes; + } + + /** + * Gets the boolean value that determines whether or not to create + * nodes whose rendered attribute value is false. The default + * value is false. + * + * This is called by the contentHandler when parsing the XML metadata + * for each node. + * + * @return the boolean value that determines whether or not to create + * nodes whose rendered attribute value is false. + */ + public boolean getCreateHiddenNodes() + { + return _createHiddenNodes; + } + + + /** + * Maps the focusPath returned when the viewId is newViewId + * to the focusPath returned when the viewId is aliasedViewId. + * This allows view id's not in the treeModel to be mapped + * to a focusPath. + * + * @param newViewId the view id to add a focus path for. + * @param aliasedViewId the view id to use to get the focusPath to use + * for newViewId. + */ + public void addViewId(String newViewId, String aliasedViewId) + { + Object focusPath = _viewIdFocusPathMap.get(aliasedViewId); + if (focusPath != null) + { + _viewIdFocusPathMap.put(newViewId, focusPath); + } + } + + /** + * Sets the currently selected node and the request method. + * This is called by a selected node's doAction method. This + * menu node must have had its "action" attribute set, thus the + * method is POST. + * + * @param currentNode The currently selected node in the menu + */ + public void setCurrentlyPostedNode(Object currentNode) + { + _setCurrentlySelectedNode(currentNode); + _setRequestMethod(_METHOD_POST); + } + + /** + * Get a the MenuNode corresponding to the key "id" from the + * node id hashmap. + * + * @param id - String node id key for the hashmap entry. + * @return The MenuNode that corresponds to id. + */ + public Object getNode (String id) + { + // This needs to be public because the nodes call into this map + return _idNodeMap.get(id); + } + + /** + * Gets the list of custom properties from the node + * and returns the value of propName. + * + * @param node Object used to get its list of custom properties + * @param propName String name of the property whose value is desired + * + * @return Object value of propName for Object node. + */ + public Object getCustomProperty(Object node, String propName) + { + if (node == null) + return null; + + FacesContext context = FacesContext.getCurrentInstance(); + PropertyResolver resolver = context.getApplication().getPropertyResolver(); + + // =-=AEW Attributes? A Map would be more appropriate + Attributes propList = + (Attributes) resolver.getValue(node, _CUSTOM_ATTR_LIST); + + if (propList == null) + return null; + + String value = propList.getValue(propName); + + // If it is an El expression, we must evaluate it + // and return its value + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + Object elValue = null; + + try + { + FacesContext ctx = FacesContext.getCurrentInstance(); + ValueBinding binding = ctx.getApplication().createValueBinding(value); + elValue = binding.getValue(ctx); + } + catch (Exception ex) + { + _LOG.warning("EL Expression " + value + + " is invalid or returned a bad value", ex); + return null; + } + return elValue; + } + + return value; + } + + /* ==================================================================== + * Private Methods + * ==================================================================== */ + + /** + * Creates a menu model based on the menu metadata Uri. + * This is accomplished by: + *
    + *
  1. Get the MenuContentHandlerImpl through the Services API. + *
  2. Set the root model and current model on the content handler, which, + * in turn, sets the models on each of the nodes. + *
  3. Parse the metadata. This calls into the MenuContentHandler's + * startElement and endElement methods, where a List of nodes and a TreeModel + * are created, along with the 3 hashMaps needed by the Model.
  4. + *
  5. Use the TreeModel to create the XMLMenuModel.
  6. + *
+ */ + private void _createModel() + { + try + { + if (_contentHandler == null) + { + List services = + ClassLoaderUtils.getServices(_MENUCONTENTHANDLER_SERVICE); + + if (services.isEmpty()) + throw new IllegalStateException("No MenuContentHandler was registered."); + + _contentHandler = (MenuContentHandler) services.get(0); + if (_contentHandler == null) + { + throw new NullPointerException(); + } + } + + // 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). + _setRootModelUri(_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. + _setModelUri(_contentHandler); + + TreeModel treeModel = _contentHandler.getTreeModel(_mdSource); + setWrappedData(treeModel); + } + catch (Exception ex) + { + _LOG.severe( "Exception creating menu model " + + _mdSource, ex); + return; + } + } + + /** + * _setRootModelUri - sets the top-level, menu model's Uri on the + * menu content handler. This is so nodes will only operate + * on the top-level, root model. + * + */ + private void _setRootModelUri(MenuContentHandler contentHandler) + { + if (_rootUri == null) + { + _rootUri = _mdSource; + + // Put the root model on the Application Map so that it + // Can be picked up by the nodes to call back into the + // root model + FacesContext facesContext = FacesContext.getCurrentInstance(); + Map requestMap = facesContext.getExternalContext().getRequestMap(); + + requestMap.put(_rootUri, this); + + // Set the key (_rootUri) to the root model on the content + // handler so that it can then be set on each of the nodes + contentHandler.setRootModelUri(_rootUri); + } + } + + /** + * Returns the root menu model's Uri. + * + * @return the root menu model's Uri. + */ + private String _getRootUri() + { + return _rootUri; + } + + /** + * _setModelUri - sets the local, menu model's Uri on the + * menu content handler. + * + */ + private void _setModelUri(MenuContentHandler contentHandler) + { + String localUri = _mdSource; + + // Put the local model on the Request Map so that it + // Can be picked up by the nodes to call back into the + // local model + FacesContext facesContext = FacesContext.getCurrentInstance(); + Map requestMap = facesContext.getExternalContext().getRequestMap(); + + requestMap.put(localUri, this); + + // Set the key (_rootUri) to the root model on the content + // handler so that it can then be set on each of the nodes + contentHandler.setModelUri(localUri); + } + + /** + * Returns the current viewId. + * + * @return the current viewId or null if the current viewId can't be found + */ + + private String _getCurrentViewId() + { + String currentViewId = + FacesContext.getCurrentInstance().getViewRoot().getViewId(); + + return currentViewId; + } + + /** + * Gets the currently selected node in the menu + */ + private Object _getCurrentlySelectedNode() + { + return _currentNode; + } + + /** + * Sets the currently selected node. + * + * @param currentNode. The currently selected node in the menu. + */ + private void _setCurrentlySelectedNode(Object currentNode) + { + _currentNode = currentNode; + } + + /** + * Sets the request method + * + * @param method + */ + private void _setRequestMethod(String method) + { + _requestMethod = method; + } + + /** + * Get the request method + */ + private String _getRequestMethod() + { + return _requestMethod; + } + + /* ================================================================ + * Public inner interface for the menu content handler + * implementation + * ================================================================ */ + + /* + * Interface corresponding to the MenuContentHandlerImpl + * inorg.apache.myfaces.trinidadinternal.menu. This is used to achieve + * separation between the api (trinidad) and the implementation (trinidadinternal). + * It is only used by the XMLMenuModel, thus it is an internal interface. + */ + public interface MenuContentHandler + { + /** + * Get the TreeModel built while parsing metadata. + * + * @param uri String mapkey to a (possibly) treeModel cached on + * the MenuContentHandlerImpl. + * @return TreeModel. + */ + public TreeModel getTreeModel(String uri); + + /** + * Sets the root Uri on the ContentHandler so that the nodes + * can get back to the root model of the application menu tree + * through the request map. + */ + public void setRootModelUri(String uri); + + /** + * Sets the local, sharedNode model's Uri on the ContentHandler so that + * the local model can be gotte too if necessary. + */ + public void setModelUri(String uri); + + /** + * Get the Model's idNodeMap + * + * @return the Model's idNodeMap + */ + public Map getIdNodeMap(); + + /** + * Get the Model's nodeFocusPathMap + * + * @return the Model's nodeFocusPathMap + */ + public Map getNodeFocusPathMap(); + + /** + * Get the Model's viewIdFocusPathMap + * + * @return the Model's viewIdFocusPathMap + */ + public Map getViewIdFocusPathMap(); + } + + private Object _currentNode = null; + private Object _prevFocusPath = null; + private String _requestMethod = _METHOD_NONE; + private String _mdSource = null; + private boolean _createHiddenNodes = false; + private boolean _begunRequest = false; + + private Map _viewIdFocusPathMap; + private Map _nodeFocusPathMap; + private Map _idNodeMap; + + static private String _rootUri = null; + static private MenuContentHandler _contentHandler = null; + + static private final String _NODE_ID_PROPERTY = "nodeId"; + static private final String _METHOD_GET = "get"; + static private final String _METHOD_POST = "post"; + static private final String _METHOD_NONE = "none"; + static private final String _CUSTOM_ATTR_LIST = "customPropList"; + static private final String _MENUCONTENTHANDLER_SERVICE = + "org.apache.myfaces.trinidad.model.XMLMenuModel$MenuContentHandler"; + + static private final TrinidadLogger _LOG = + TrinidadLogger.createTrinidadLogger(XMLMenuModel.class); +} Added: incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java URL: http://svn.apache.org/viewvc/incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java?rev=427701&view=auto ============================================================================== --- incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java (added) +++ incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/GroupNode.java Tue Aug 1 12:59:30 2006 @@ -0,0 +1,199 @@ +/* + * @(#)GroupNode.java + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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 org.apache.myfaces.trinidad.logging.TrinidadLogger; + +/** + * Code specific to a Menu Model's GroupNode. + * + * @author Gary Kind + */ + +public class GroupNode extends MenuNode +{ + /** + * Constructs a GroupNode + */ + public GroupNode() + { + super(); + } + + /** + * Called by the Default ActionListener + * when a menu node is clicked/selected. + * + * @return String outcome or viewId used + * during a POST for navigation. + */ + public String doAction() + { + // Call the doAction method of my idref node + return getRefNode().doAction(); + } + + /** + * Get the Destination URL of a page for a + * GET. + * + * @return String URL of a page. + */ + public String getDestination() + { + // Call the getDestination method of my idref node + return getRefNode().getDestination(); + } + + /** + * Sets the idref of the node. + * + * The value of this attribute is an "id" of another node + * This tells the pointing node where to obtain its viewId and + * takes precedence (and will replace) the pointing nodes viewId, + * if one exists. This should point to a node of the same style, + * e.g. actionNode points to actionNode. + * + * @param idref - String name pointing to the "id" of another node + */ + public void setIdRef(String idref) + { + _idref = idref; + + // Create a list of idref's for easier access + if (_idref != null) + _makeIdRefList (idref); + } + + /** + * Get the node whose id matches this node's + * idref attribute value. + * + * @return the MenuNode whose id matches this + * node's idref attribute value. + */ + public MenuNode getRefNode() + { + MenuNode refNode = null; + + // create one if it does not exist + // should not happen, but can't hurt + if (_idrefList == null) + { + String idref = getIdRef(); + _makeIdRefList(idref); + } + + // Get idrefList + String[] idrefList = _getIdRefList(); + + // 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++) + { + String refNodeId = idrefList[i]; + + refNode = (MenuNode) getRootModel().getNode(refNodeId); + + // if nothing found, move on to the next idref + if (refNode == null) + continue; + + // Check the attributes of the found node + // IMPORTANT NOTE: nodes whose rendered attribute + // is set to false never get created, so the first + // test should never return true. But just in + // case the creation ever changes, we will leave + // this test. + if ( !refNode.getRendered() + || refNode.getDisabled() + || refNode.getReadOnly() + ) + { + 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; + } + + /** + * Get the id of the node referred to by + * the idref attribute of this node. + * + * @return String id of the node referred + * to by the idref attribure of + * this node. + */ + public String getIdRef() + { + return _idref; + } + + /* ============================================================= + * Private methods + * =============================================================*/ + + /** + * _getIdRefList. gets the list of idrefs for this node. + * + * @return String[] list of idrefs for this node. + */ + private String[] _getIdRefList() + { + return _idrefList; + } + + /** + * Make a list of idref entries from the nodes String + * of idref's. + * + * This should only be called from the node's setIdRef + * method. So if it is called more than once (highly + * unlikely), simply empty out the previous contents. + * + * @param entries - String of String entries + * + */ + private void _makeIdRefList (String entries) + { + _idrefList = entries.trim().split("\\s+"); + } + + private String _idref = null; + private String[] _idrefList = null; + + private final static TrinidadLogger _LOG = + TrinidadLogger.createTrinidadLogger(GroupNode.class); +} Added: incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java URL: http://svn.apache.org/viewvc/incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java?rev=427701&view=auto ============================================================================== --- incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java (added) +++ incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/ItemNode.java Tue Aug 1 12:59:30 2006 @@ -0,0 +1,424 @@ +/* +* @(#)ItemNode.java +* +* Copyright 2006 The Apache Software Foundation. +* +* Licensed 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 javax.faces.webapp.UIComponentTag; + +/** + * Code specific to a Menu Model's ItemNode. + * + * @author Gary Kind + */ + +public class ItemNode extends MenuNode +{ + /** + * Constructs an ItemNode + */ + public ItemNode() + { + super(); + } + + /** + * Sets the action of the node. This is obtained from the menu + * metadata file and is the string value of the "action" + * property. + * + * @param action - the string value of the ItemNode's "action" property. + */ + + public void setAction(String action) + { + _action = action; + } + + /** + * Gets the value of the node's action property. The action attr value + * could be one of 2 things: + * 1) An EL expression + * 2) An outcome referencing a navigation rule in the faces_config file. + * + * Since this method is called only when an ItemNode is clicked, the model + * is notified that this node is the currently selected node. + * + * @return String value of the ItemNode's "action" property. + */ + public String doAction() + { + String value = _action; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of action is EL method binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + } + + // Post me as the selected Node for the request + postSelectedNode(this); + + return value; + } + + /** + * setActionListener - sets the value of the Menu Node's actionListener + * atribute. + * + * @param actionListener - El expression method reference to an + * action listener + */ + public void setActionListener(String actionListener) + { + _actionListener = actionListener; + } + + /** + * getActionListener - gets the value of the Menu Node's actionListener + * attribute. + * + * @return String - method reference to an + * action listener + */ + public String getActionListener() + { + String value = _actionListener; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of action is EL method binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + setActionListener(value); + } + + return value; + } + + /** + * setLaunchListener - sets the value of the Menu Node's launchListener + * atribute. + * + * @param launchListener - El expression method reference to a + * launch listener + */ + public void setLaunchListener(String launchListener) + { + _launchListener = launchListener; + } + + /** + * getLaunchListener - gets the value of the Menu Node's launchListener + * attribute. + * + * @return String - method reference to an + * launch listener + */ + public String getLaunchListener() + { + String value = _launchListener; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of action is EL method binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + setLaunchListener(value); + } + + return value; + } + + /** + * setReturnListener - sets the value of the Menu Node's returnListener + * atribute. + * + * @param returnListener - El expression method reference to a + * return listener + */ + public void setReturnListener(String returnListener) + { + _returnListener = returnListener; + } + + /** + * getReturnListener - gets the value of the Menu Node's returnListener + * attribute. + * + * @return String - method reference to an + * return listener + */ + public String getReturnListener() + { + String value = _returnListener; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of action is EL method binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + setReturnListener(value); + } + + return value; + } + + /** + * Sets the immediate attribute of the menu item. + * + * @param immediate - boolean indicating whether or not data validation - + * client-side or server-side - should take place when + * events are generated by this component. + */ + public void setImmediate(boolean immediate) + { + _immediateStr = immediate ? "true" : "false"; + } + + /** + * Sets the immediate attribute of the menu item. + * + * @param immediateStr - string representing a boolean value + * or an EL expression + */ + public void setImmediate(String immediateStr) + { + _immediateStr = immediateStr; + } + + /** + * Gets the immediate attribute of the menu item. + * + * @return boolean - indicating whether or not data validation - + * client-side or server-side - should take place when events + * are generated by this component. + */ + public boolean getImmediate() + { + boolean immediate = MenuUtils.evalBoolean(_immediateStr, false); + return immediate; + } + + /** + * Sets the useWindow attribute of the menu item. + * + * @param useWindow - boolean indicating whether + * or not to use a new window when launching dialogs. + */ + public void setUseWindow(boolean useWindow) + { + _useWindowStr = useWindow ? "true" : "false"; + } + + /** + * Sets the useWindow attribute of the menu item. + * + * @param useWindowStr - string representing a boolean value or + * an EL Expression + */ + public void setUseWindow(String useWindowStr) + { + _useWindowStr = useWindowStr; + } + + /** + * Gets the useWindow attribute of the menu item. + * + * @return boolean - indicating whether + * or not to use a new window when launching dialogs. + */ + public boolean getUseWindow() + { + boolean useWindow = MenuUtils.evalBoolean(_useWindowStr, false); + return useWindow; + } + + /** + * Sets the windowHeight attribute of the menu item. + * + * @param windowHeight - int height of the window, if + * this command is used to launch a window. + */ + public void setWindowHeight(int windowHeight) + { + _windowHeightStr = Integer.toString(windowHeight); + } + + /** + * Sets the windowHeight attribute of the menu item. + * + * @param windowHeightStr - String Height of the window, if + * this command is used to launch a window. Could be an + * EL expression + */ + public void setWindowHeight(String windowHeightStr) + { + _windowHeightStr = windowHeightStr; + } + + /** + * Gets the windowHeight attribute of the menu item. + * + * @return int height of the window, if + * this command is used to launch a window. + */ + public int getWindowHeight() + { + int windowHeight = MenuUtils.evalInt(_windowHeightStr); + return windowHeight; + } + + /** + * Sets the windowWidth attribute of the menu item. + * + * @param windowWidth - int width of the window, if + * this command is used to launch a window. + */ + public void setWindowWidth(int windowWidth) + { + _windowWidthStr = Integer.toString(windowWidth); + } + + /** + * Sets the windowWidth attribute of the menu item. + * + * @param windowWidthStr - String width of the window, if + * this command is used to launch a window. Could be an + * EL expression + */ + public void setWindowWidth(String windowWidthStr) + { + _windowWidthStr = windowWidthStr; + } + + /** + * Gets the windowWidth attribute of the menu item. + * + * @return int width of the window, if + * this command is used to launch a window. + */ + public int getWindowWidth() + { + int windowWidth = MenuUtils.evalInt(_windowWidthStr); + return windowWidth; + } + + /** + * Sets the destination of the node. + * + * This is obtained from the metadata file and is the string + * value of the "destination" property. + * + * @param destination - either a URI or an EL method binding expression. + */ + public void setDestination(String destination) + { + _destination = destination; + } + + /** + * Gets the value of the node's destination property. + * The destination attr value could be one of 2 things: + * 1) a uri + * 2) An EL expression + * + * So that the model can identify this node as the currently selected + * node, the node's id is appended to the destination as a parameter + * that is picked up when the getFocusRowKey() method of the model + * is called to get the focus path. + * + * @return destination - the String value of the destinationNode's + * "destination" property. + */ + public String getDestination() + { + String value = _destination; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of action is EL method binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + } + + // Appending nodeId to URL so that we can identify the node + // when getFocusRowKey() is called on the model. + return value != null ? value + "?nodeId=" + getId() : value; + } + + /** + * setTargetFrame - sets the value of the Destination Node's + * targetFrame attribute + * + * @param targetFrame - the target frame for the goCommandMenuItem. + */ + public void setTargetFrame(String targetFrame) + { + _targetFrame = targetFrame; + } + + /** + * getTargetFrame - gets the value of the Destination Node's + * targetFrame attribute + * + * @return the target frame for the goCommandMenuItem. + */ + public String getTargetFrame() + { + String value = _targetFrame; + + // Could be EL expression + if ( value != null + && UIComponentTag.isValueReference(value) + ) + { + // Value of destination is EL value binding, so we + // need to evaluate it + value = (String)MenuUtils.getBoundValue(value); + setTargetFrame(value); + } + + return value; + } + + private String _destination = null; + private String _targetFrame = null; + private String _action = null; + private String _actionListener = null; + private String _launchListener = null; + private String _returnListener = null; + private String _immediateStr = null; + private String _useWindowStr = null; + private String _windowHeightStr = null; + private String _windowWidthStr = null; +} Added: incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuConstants.java URL: http://svn.apache.org/viewvc/incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuConstants.java?rev=427701&view=auto ============================================================================== --- incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuConstants.java (added) +++ incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuConstants.java Tue Aug 1 12:59:30 2006 @@ -0,0 +1,25 @@ +/* + * @(#)MenuConstants.java + * + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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; + +public interface MenuConstants +{ + public final static String NODE_STYLE_ITEM = "item"; + public final static String NODE_STYLE_GROUP = "group"; +} Added: incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java URL: http://svn.apache.org/viewvc/incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java?rev=427701&view=auto ============================================================================== --- incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java (added) +++ incubator/adffaces/trunk/trinidad/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/menu/MenuContentHandlerImpl.java Tue Aug 1 12:59:30 2006 @@ -0,0 +1,957 @@ +/* @(#)MenuContentHandlerImpl.java + * + * + * Copyright 2006 The Apache Software Foundation. + * + * Licensed 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.net.URL; + +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; +import org.xml.sax.helpers.AttributesImpl; + +/** + * 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. + * + * @author Kris McQueen and Gary Kind + */ + /* + * 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 menu list map + if (_treeModelMap == null) + _treeModelMap = new HashMap(); + } + + /** + * Called by the SAX Parser at the start of parsing a document. + */ + 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((Object) _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 + */ + 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) + { + ArrayList list = new ArrayList(); + _menuNodes.add(list); + } + + _attrList = new AttributesImpl(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 CombinationMenuModel.java) + menuNode.setRootModelUri(getRootModelUri()); + + // 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 list = (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(); + + XMLMenuModel menuModel = (XMLMenuModel)MenuUtils.getBoundValue(expr); + + // Now must pop the values cause we are back to the parent + // model. + _restoreModelData(); + + Object subMenuObj = menuModel.getWrappedData(); + List subMenuList = null; + + if (subMenuObj instanceof ChildPropertyTreeModel) + { + subMenuList = + (List)((ChildPropertyTreeModel)subMenuObj).getWrappedData(); + } + + // SharedNode could be the first child + // So we need a new list for the children + if (_menuNodes.size() < _nodeDepth) + { + ArrayList list = new ArrayList(); + _menuNodes.add(list); + } + + List list = (List)_menuNodes.get(_nodeDepth-1); + list.addAll(subMenuList); + } + } + } + + /** + * 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. + */ + 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 = (List)_menuNodes.get(_nodeDepth-1); + MenuNode parentNode = (MenuNode)parentList.get(parentList.size()-1); + + parentNode.setChildren((List)_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--; + + if (_nodeDepth > 0) + { + // The parent menu item is the last menu item at the previous depth + List parentList = (List)_menuNodes.get(_nodeDepth-1); + MenuNode parentNode = (MenuNode)parentList.get(parentList.size()-1); + + parentNode.setChildren((List)_menuNodes.get(_nodeDepth)); + } + } + } + + /** + * Called by the SAX Parser at the end of parsing a document. + * Here, the menuList is put on the menuList map. + */ + public void endDocument() + { + _menuList = (List)_menuNodes.get(0); + + // Create the treeModel + ChildPropertyTreeModel treeModel = + new ChildPropertyTreeModel((Object)_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. + if (getRootModelUri().equals(getModelUri())) + { + _viewIdFocusPathMap = new HashMap(); + _nodeFocusPathMap = new HashMap(); + _idNodeMap = new HashMap(); + 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 getViewIdFocusPathMap() + { + return _viewIdFocusPathMap; + } + + /** + * Get the Model's nodeFocusPathMap + * + * @return the Model's nodeFocusPathMap + */ + public Map getNodeFocusPathMap() + { + return _nodeFocusPathMap; + } + + /** + * Get the Model's idNodeMap + * + * @return the Model's idNodeMap + */ + public Map getIdNodeMap() + { + return _idNodeMap; + } + + /** + * Get the treeModel built during parsing + * + * @return List of menu nodes. + */ + public TreeModel getTreeModel(String uri) + { + TreeModel model = _treeModelMap.get(uri); + if (model == null) + { + _currentTreeModelMapKey = uri; + + try + { + // Get a parser. NOTE: we are using the jdk's 1.5 SAXParserFactory + // and SAXParser here. + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser parser = factory.newSAXParser(); + + + // Parse the metadata + InputStream inStream = _getStream(uri); + parser.parse(inStream, this); + } + catch (SAXException saxex) + { + _LOG.severe ( "Exception creating model " + uri, saxex); + } + catch (IOException ioe) + { + _LOG.severe ( "Exception creating model " + uri, ioe); + } + catch (ParserConfigurationException pce) + { + _LOG.severe ( "Exception creating model " + uri, pce); + } + } + + return _treeModelMap.get(uri); + } + + /** + * Get the top-level, root menu model, which contains + * the entire menu tree. + * + * @return root, top-level XMLMenuModel + */ + public XMLMenuModel getRootModel() + { + FacesContext facesContext = FacesContext.getCurrentInstance(); + Map requestMap = facesContext.getExternalContext().getRequestMap(); + + return (XMLMenuModel) requestMap.get(getRootModelUri()); + } + + /** + * Get the top-level, root menu model's Uri. + * + * @return root, top-level XMLMenuModel's Uri + */ + public String getRootModelUri() + { + return _rootModelUri; + } + + /** + * Sets the root menu Model's Uri. + *

+ * This is always only the top-level, root model's Uri. + * We do this because the MenuContentHandlerImpl and nodes need to be able + * to call into the root model to: + *

    + *
  • notify them root menu model of the currently selected node on a POST + *
+ * + * @param rootModelUri - String the root, top-level menu model's Uri. + */ + public void setRootModelUri(String rootModelUri) + { + _rootModelUri = rootModelUri; + } + + /** + * Get the local (sharedNode) menu model. + * + * @return sharedNode's XMLMenuModel + */ + public XMLMenuModel getModel() + { + FacesContext facesContext = FacesContext.getCurrentInstance(); + Map 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 + //======================================================================= + + /** + * 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); + + // Set the Any Attributes Attrlist + if (_attrList.getLength() > 0) + { + menuNode.setCustomPropList(_attrList); + } + + 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); + + 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. + */ + private void _saveModelData() + { + if (_saveDataStack == null) + { + _saveDataStack = new Stack(); + } + + // DO NOT CHANGE THE ORDER HERE. IT MUST MATCH + // "pushes" DONE BELOW in _restoreModelData. + int nodeDepthSave = _nodeDepth; + ArrayList menuNodesSave = new ArrayList(_menuNodes); + + + ArrayList menuListSave = ( _menuList != null + ? new ArrayList(_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. + */ + 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) _saveDataStack.pop(); + _menuNodes = (ArrayList) _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) + { + int idx = _attrList.getIndex(attrName); + + if (idx == -1) + return null; + + String attrValue = _attrList.getValue(idx); + _attrList.removeAttribute(idx); + return attrValue; + } + + /*========================================================================= + * Menu Model Data Structure section. + * ======================================================================*/ + /** + * Traverses the tree and builds the model's viewIdFocusPathMap, + * nodeFocusPathMap, and _idNodeMap + * + * @param tree + */ + 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 + ArrayList focusPath = (ArrayList)tree.getRowKey(); + + // Get the focusViewId of the node + Object viewIdObject = node.getFocusViewId(); + + if (viewIdObject != null) + { + // Put this entry in the nodeFocusPathMap + nodeFocusPathMap.put(node, (Object)focusPath); + + // Does this viewId already exist in the _viewIdFocusPathMap? + ArrayList existingFpArrayList = + (ArrayList) 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. + ArrayList fpArrayList = new ArrayList(); + fpArrayList.add(focusPath); + viewIdFocusPathMap.put(viewIdObject, (Object)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, (Object)focusPath); + } + else + { + existingFpArrayList.add((Object)focusPath); + } + } + } + + // Get the Id of the node + String idProp = node.getId(); + + if (idProp != null) + { + idNodeMap.put(idProp, node); + } + + if (tree.isContainer() && !tree.isContainerEmpty()) + { + tree.enterContainer(); + _addToMaps(tree, viewIdFocusPathMap, nodeFocusPathMap, idNodeMap); + tree.exitContainer(); + } + } + } + + + /** + * getStream - Opens an InputStream to the provided URI. + * + * @param uri - String uri to a data source. + * @return InputStream to the data source. + */ + private InputStream _getStream(String uri) + { + try + { + // Open the metadata + FacesContext context = FacesContext.getCurrentInstance(); + URL url = context.getExternalContext().getResource(uri); + return url.openStream(); + } + catch (Exception ex) + { + _LOG.severe("Exception opening URI " + uri, ex); + return null; + } + } + + + //======================================================================== + // Private variables + //======================================================================== + + private List _menuNodes; + private List _menuList; + private String _currentTreeModelMapKey; + private int _nodeDepth; + private int _skipDepth = -1; + private String _currentNodeStyle; + private String _handlerId; + private String _resBundleKey; + private String _resBundleName; + private AttributesImpl _attrList; + private Map _treeModelMap; + private Stack _saveDataStack; + private Map _viewIdFocusPathMap; + private Map _nodeFocusPathMap; + private Map _idNodeMap; + + + // Menu model Uri's + private String _rootModelUri = null; + private String _localModelUri = 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 final static TrinidadLogger _LOG = + TrinidadLogger.createTrinidadLogger(MenuContentHandlerImpl.class); + +} // endclass MenuContentHandlerImpl