1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.myfaces.trinidad.model; 20 21 import java.io.InputStream; 22 import java.io.Serializable; 23 24 import java.net.URL; 25 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.List; 30 import java.util.Map; 31 import java.util.concurrent.ConcurrentHashMap; 32 import java.util.concurrent.atomic.AtomicInteger; 33 34 import javax.el.ELContext; 35 import javax.el.ELResolver; 36 import javax.el.PropertyNotFoundException; 37 38 import javax.faces.context.ExternalContext; 39 import javax.faces.context.FacesContext; 40 41 import org.apache.myfaces.trinidad.logging.TrinidadLogger; 42 import org.apache.myfaces.trinidad.util.ClassLoaderUtils; 43 import org.apache.myfaces.trinidad.util.ContainerUtils; 44 import org.apache.myfaces.trinidad.util.TransientHolder; 45 46 47 /** 48 * Creates a Menu Model from a TreeModel where nodes in the treeModel 49 * contain viewId information. 50 * <p> 51 * Each node must have either a bean getter method or a Map property 52 * that returns a viewId. There are several restrictions on the data: 53 * <ul> 54 * o The nodes in the tree must either be all beans or all maps, 55 * but not a mix of beans and maps. 56 * o The viewId of a node can be null, but if set it must be unique. 57 * o The tree cannot be mutable. 58 * </ul> 59 * <p> 60 * The getFocusRowKey method 61 * <ul> 62 * o gets the current viewId by calling 63 * FacesContext.getCurrentInstance().getViewRoot().getViewId() 64 * o compares the current viewId with the viewId's in the viewIdFocusPathMap 65 * that was built by traversing the tree when the model was created. 66 * o returns the focus path to the node with the current viewId or null if the 67 * current viewId can't be found. 68 * o in the case where a viewId has multiple focus paths, the currently 69 * selected node is used as a key into the nodeFocusPathMap to return the 70 * correct focus path. 71 * </ul> 72 * <p> 73 * The Model is created by specifying it in the faces-config.xml file 74 * as follows 75 * <pre> 76 * <managed-bean> 77 * <managed-bean-name>hr_menu</managed-bean-name> 78 * <managed-bean-class> 79 * org.apache.myfaces.trinidad.model.XMLMenuModel 80 * </managed-bean-class> 81 * <managed-bean-scope>request</managed-bean-scope> 82 * <managed-property> 83 * <property-name>source</property-name> 84 * <property-class>java.lang.String</property-class> 85 * <value>/WEB-INF/hr-menu.xml</value> 86 * </managed-property> 87 * </managed-bean> 88 * </pre> 89 * 90 * Objects of this class are not thread safe and should be used 91 * only in request scope. 92 * 93 */ 94 95 /* 96 * Three hashmaps are also created in order to be able to resolve cases where 97 * multiple menu items cause navigation to the same viewId. All 3 of these maps 98 * are created after the metadata is parsed and the tree is built, in the 99 * MenuContentHandlerImpl. 100 * 101 * o The first hashMap is called the viewIdFocusPathMap and is built by 102 * traversing the tree when the model is created. Each node's focusViewId is 103 * obtained and used as the key to an entry in the viewIdHashMap. An ArrayList 104 * is used as the entry's value and each item in the ArrayList is a node's 105 * rowkey from the tree. This allows us to have duplicate rowkeys for a single 106 * focusViewId which translates to a menu that contains multiple items pointing 107 * to the same page. In general, each entry will have an ArrayList of rowkeys 108 * with only 1 rowkey, AKA focus path. 109 * o The second hashMap is called the nodeFocusPathMap and is built at the 110 * same time the viewIdHashMap is built. Each entry's key is the actual node and 111 * the value is the row key. Since the model keeps track of the currently 112 * selected menu node, this hashmap can be used to resolve viewId's with 113 * multiple focus paths. Since we have the currently selected node, we just 114 * use this hashMap to get its focus path. 115 * o The third hashMap is called idNodeMap and is built at the same time as the 116 * previous maps. This map is populated by having each entry contain the node's 117 * id as the key and the actual node as the value. In order to keep track of 118 * the currently selected node in the case of a GET, the node's id is appended 119 * to the request URL as a parameter. The currently selected node's id is 120 * picked up and this map is used to get the actual node that is currently 121 * selected. 122 * 123 * Keeping track of the currently selected menu item/node. 124 * 125 * If an itemNode in the metadata uses its "action" attribute, a POST is done 126 * and the node's "doAction" method is called when the menu item is clicked. At 127 * that time, the model is notified through its setCurrentlyPostedNode() method, 128 * where the current node is set and the request method is set to POST. 129 * 130 * If an itemNode in the metadata uses its "destination" attribute, a GET is 131 * done. Nothing is called on the model when the menu item is clicked. However 132 * at the time the page is rendered the "getDestination" method for all nodes 133 * using the "destination" attribute is called. At this point 134 * we append the node's id to the value of the destination attribute URL, as 135 * a parameter, and return it. So when getFocusRowKey() is called, we get the 136 * request the node's parameter matching the currently selected node's id. 137 * Using the node id, we find the matching node in the idNodeMap and voila, we 138 * have the currently selected node! 139 */ 140 public class XMLMenuModel extends BaseMenuModel 141 142 { 143 public XMLMenuModel() 144 { 145 super(); 146 _modelId = Integer.valueOf(System.identityHashCode(this)).toString(); 147 } 148 149 /** 150 * This needs to be overriden by classes extending XmlMenuModel and using APIs for the nodes 151 * of XmlMenuModel. Default value returned is true for backward compatibilty. The models using 152 * the external APIs for their nodes must return false. 153 * @return boolean 154 */ 155 protected boolean isCompatibilityMode() 156 { 157 return true; 158 } 159 160 /** 161 * setSource - specifies the XML metadata and creates 162 * the XML Menu Model. 163 * 164 * @param menuMetadataUri - String URI to the XML metadata. 165 */ 166 public void setSource(String menuMetadataUri) 167 { 168 if (menuMetadataUri == null || "".equals(menuMetadataUri)) 169 return; 170 171 _mdSource = menuMetadataUri; 172 _createModel(); 173 } 174 175 /** 176 * Makes the TreeModel part of the menu model. Also creates the 177 * _viewIdFocusPathMap, _nodeFocusPathMap, and idNodeMaps. 178 * 179 * @param data The Tree Model instance 180 */ 181 @Override 182 public void setWrappedData(Object data) 183 { 184 super.setWrappedData(data); 185 186 // The only thing the child menu models are needed for are their 187 // menuLists, which get incorporated into the Root Model's tree. 188 // There is no need to create the hashmaps or anything 189 // on the child menu models. A lot of overhead (performance and 190 // memory) would be wasted. 191 if (_isRoot) 192 { 193 _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(_mdSource); 194 _nodeFocusPathMap = _contentHandler.getNodeFocusPathMap(_mdSource); 195 _idNodeMap = _contentHandler.getIdNodeMap(_mdSource); 196 } 197 } 198 199 /** 200 * Returns the rowKey to the current viewId, or in the case of where the 201 * model has nodes with duplicate viewId's and one is encountered, we 202 * return the rowKey of the currently selected node. 203 * <p> 204 * 205 * The getFocusRowKey method 206 * <ul> 207 * <li>gets the current viewId by calling 208 * FacesContext.getCurrentInstance().getViewRoot().getViewId() 209 * <li>compares the current viewId with the viewId's in the viewIdFocusPathMap 210 * that was built by traversing the tree when the model was created. 211 * <li>returns the focus path to the node with the current viewId or null if 212 * the current viewId can't be found. 213 * <li>in the case where a viewId has multiple focus paths, the currently 214 * selected node is used as a key into the nodeFocusPathMap to return the 215 * correct focus path. 216 * </ul> 217 * 218 * @return the rowKey to the node with the current viewId or null if the 219 * current viewId can't be found. 220 */ 221 @SuppressWarnings("unchecked") 222 @Override 223 public Object getFocusRowKey() 224 { 225 Object focusPath = null; 226 String currentViewId = _getCurrentViewId(); 227 FacesContext context = FacesContext.getCurrentInstance(); 228 229 // getFocusRowKey() is called multiple times during the Process Validations 230 // Phase and again during the Render Response Phase during each Request. 231 // During each phase, as described below, the same viewId is passed in. To 232 // prevent unnecessary looking up of the focus path each time, the previous 233 // focus path is returned after the first call in each phase, as described 234 // below. 235 // 236 // ** Process Validations Phase: 237 // During the Process Validations Phase, the prevViewId is initially 238 // null (gets set to this at the start of each Request). The first time 239 // getFocusRowKey is called, the currentViewId is that of the node we are 240 // navigating "from" during the Request. This is stored in prevViewId. 241 // Because the currentViewId is not equal to the prevViewId, 242 // the node is looked up, its focus path stored in prevFocusPath, and the 243 // focus path is returned. On subsequent calls during the Process 244 // Validations Phase, the currentViewId is always that of the "from" node, 245 // the currentViewId is equal to the prevViewId, and so we simply 246 // return prevFocusPath. 247 // 248 // ** Render Response Phase: 249 // During the Render Response Phase, the prevViewId is initially 250 // that of the "from" node. The first time getFocusRowKey is called 251 // the currentViewId is that of the node we are navigating "to" during 252 // the Request. This is stored in prevViewId. 253 // Because the currentViewId is not equal to the prevViewId, 254 // the node is looked up, its focus path stored in prevFocusPath, and the 255 // focus path is returned. On subsequent calls during the Render 256 // Response Phase, the currentViewId is always that of the "to" node, 257 // the currentViewId is equal to the prevViewId, and so we simply 258 // return prevFocusPath. 259 // 260 // IMPORTANT: Code that returns the correct focus path for duplicate nodes 261 // in the node tree actually depends on this optimization. 262 // 263 if ((_prevViewId != null) && _prevViewId.equals(currentViewId)) 264 return _prevFocusPath; 265 266 // Initializations 267 _prevViewId = currentViewId; 268 269 // How did we get to this page? 270 // 1) Clicked on a menu item with its action attribute set. This does 271 // a POST. 272 // 2) Clicked on a menu item with its destination attribute set. This 273 // does a GET. 274 // 3) Navigation to a viewId within our model but done from outside the 275 // model. Examples, button, text link, etc. 276 // 277 278 // Case 1: POST method. Current Node has already been set and so has the 279 // request method. The doAction() method of the clicked node calls 280 // the setCurrentlyPostedNode() method of this model, which sets both. So 281 // we have nothing to do in this case. 282 283 if (_getRequestMethod() != _METHOD_POST) 284 { 285 // Case 2: GET method. We have hung the selected node's id off the 286 // request's URL, which enables us to get the selected node and also 287 // to know that the request method is GET. 288 Map<String, String> paramMap = 289 context.getExternalContext().getRequestParameterMap(); 290 String UrlNodeId = paramMap.get(_NODE_ID_PROPERTY); 291 292 if (UrlNodeId != null) 293 { 294 _setCurrentlySelectedNode(_getNodeFromURLParams(UrlNodeId)); 295 _setRequestMethod(_METHOD_GET); 296 } 297 } 298 299 // Case 3: Navigation to a page within the model from an outside 300 // method, e.g. button, link text, etc. In this case we set the 301 // currently selected node to null. This tells us to get the 0th 302 // element of the ArrayList returned from the viewId hashMap. This 303 // should be a focus path match to the node whose "defaultFocusPath" 304 // attribute was set to 'true'. 305 if (_getRequestMethod() == _METHOD_NONE) 306 { 307 _setCurrentlySelectedNode(null); 308 } 309 310 // Get the matching focus path ArrayList for the currentViewId. 311 // This is an ArrayList because our map allows nodes with the same 312 // viewId, that is, different focus paths to the same viewId. 313 ArrayList<Object> fpArrayList = 314 (ArrayList<Object>) _viewIdFocusPathMap.get(currentViewId); 315 316 if (fpArrayList != null) 317 { 318 if (_prevRequestNode != null) 319 { 320 // _prevRequestNode will only be non-null when the previous 321 // request's node (AKA the "from" node) was a duplicate. 322 // We need to return the correct focus path, so we must 323 // use the node. If we don't do this, the wrong focus path 324 // could be returned from the _viewIdFocusPathMap. 325 focusPath = _nodeFocusPathMap.get(_prevRequestNode); 326 327 // Reset it to null 328 _prevRequestNode = null; 329 } 330 else 331 { 332 // Get the currently selected node 333 Object currentNode = _getCurrentlySelectedNode(); 334 335 if (fpArrayList.size() == 1 || currentNode == null) 336 { 337 // For fpArrayLists with multiple focusPaths, 338 // the 0th entry in the fpArrayList carries the 339 // focusPath of the node with its defaultFocusPath 340 // attribute set to "true", if there is one. If 341 // not, the 0th element is the default. 342 focusPath = fpArrayList.get(0); 343 344 // Not a duplicate node so set this to null 345 _prevRequestNode = null; 346 } 347 else 348 { 349 // This will be a duplicate node, meaning it navigates to the 350 // the same page as at least one other node in the tree. 351 focusPath = _nodeFocusPathMap.get(currentNode); 352 353 // Save this node for the next request. Otherwise, the 354 // next Request will go into previous part of this conditional 355 // and return (possibly) the wrong focus path during the 356 // Process Validations Phase. 357 _prevRequestNode = currentNode; 358 } 359 } 360 } 361 362 // Save all pertinent information 363 _prevFocusPath = focusPath; 364 365 // Reset this to _METHOD_NONE so we will know when 366 // Navigation to a viewId within our model has been 367 // done from outside the model, e.g. link, button. 368 // If this is not done, the current request method 369 // will be from the previous navigation and could 370 // be incorrrect. We always reset it to _METHOD_NONE 371 // so that the correct navigation method (see comment at top 372 // of getFocusRowKey() ) is determined each time. 373 _setRequestMethod(_METHOD_NONE); 374 375 return focusPath; 376 } 377 378 379 /** 380 * Gets the URI to the XML menu metadata. 381 * 382 * @return String URI to the XML menu metadata. 383 */ 384 public String getSource() 385 { 386 return _mdSource; 387 } 388 389 /** 390 * Sets the boolean value that determines whether or not to create 391 * nodes whose rendered attribute value is false. The default 392 * value is false. 393 * 394 * This is set through a managed property of the XMLMenuModel 395 * managed bean -- typically in the faces-config.xml file for 396 * a faces application. 397 */ 398 public void setCreateHiddenNodes(boolean createHiddenNodes) 399 { 400 _createHiddenNodes = createHiddenNodes; 401 } 402 403 /** 404 * Gets the boolean value that determines whether or not to create 405 * nodes whose rendered attribute value is false. The default 406 * value is false. 407 * 408 * This is called by the contentHandler when parsing the XML metadata 409 * for each node. 410 * 411 * @return the boolean value that determines whether or not to create 412 * nodes whose rendered attribute value is false. 413 */ 414 public boolean getCreateHiddenNodes() 415 { 416 return _createHiddenNodes; 417 } 418 419 420 /** 421 * Maps the focusPath returned when the viewId is newViewId 422 * to the focusPath returned when the viewId is aliasedViewId. 423 * This allows view id's not in the treeModel to be mapped 424 * to a focusPath. 425 * 426 * @param newViewId the view id to add a focus path for. 427 * @param aliasedViewId the view id to use to get the focusPath to use 428 * for newViewId. 429 */ 430 @SuppressWarnings("unchecked") 431 public void addViewId(String newViewId, String aliasedViewId) 432 { 433 List<Object> focusPath = 434 _viewIdFocusPathMap.get(aliasedViewId); 435 436 if (focusPath != null) 437 { 438 _viewIdFocusPathMap.put(newViewId, focusPath); 439 } 440 } 441 442 /** 443 * Sets the currently selected node and the request method. 444 * This is called by a selected node's doAction method. This 445 * menu node must have had its "action" attribute set, thus the 446 * method is POST. 447 * 448 * @param currentNode The currently selected node in the menu 449 */ 450 public void setCurrentlyPostedNode(Object currentNode) 451 { 452 _setCurrentlySelectedNode(currentNode); 453 _setRequestMethod(_METHOD_POST); 454 455 456 // Do this in the case where a menu item is selected 457 // that has the same viewId as the previous menu item 458 // that is selected. If not, the test at the beginning 459 // of getFocusRowKey() (currentViewId == _prevViewId) 460 // is true and just returns, even though we have selected 461 // a new node and the focus path should change. 462 _prevViewId = null; 463 } 464 465 /** 466 * Get a the MenuNode corresponding to the key "id" from the 467 * node id hashmap. 468 * 469 * @param id - String node id key for the hashmap entry. 470 * @return The Node Object that corresponds to id. 471 */ 472 public Object getNode (String id) 473 { 474 XMLMenuModel rootModel = _getRootModel(); 475 Map<String, Object> idNodeMap = rootModel._getIdNodeMap(); 476 477 if (idNodeMap == null) 478 return null; 479 480 // This needs to be public because the nodes call into this map 481 return idNodeMap.get(id); 482 } 483 484 /** 485 * Gets the list of custom properties from the node 486 * and returns the value of propName. Node must be an itemNode. 487 * If it is not an itemNode, the node will not have any custom 488 * properties and null will be returned. 489 * 490 * @param node Object used to get its list of custom properties 491 * @param propName String name of the property whose value is desired 492 * 493 * @return Object value of propName for Object node. 494 */ 495 @SuppressWarnings("unchecked") 496 public Object getCustomProperty(Object node, String propName) 497 { 498 if (node == null) 499 return null; 500 501 FacesContext context = FacesContext.getCurrentInstance(); 502 ELContext elContext = context.getELContext(); 503 ELResolver resolver = elContext.getELResolver(); 504 String value = null; 505 506 try 507 { 508 Map<String, String> propMap = 509 (Map<String, String>) resolver.getValue(elContext, 510 node, _CUSTOM_ATTR_LIST); 511 512 // Need to check to see if propMap is null. If there are 513 // no custom properties for this itemNode, there will be 514 // no propMap. See MenuContentHandler._createItemNode(). 515 if (propMap == null) 516 return null; 517 518 value = propMap.get(propName); 519 } 520 catch (PropertyNotFoundException ex) 521 { 522 // if the node is not an itemNode, the node 523 // has no custom properties, so we simply 524 // return null 525 return null; 526 } 527 528 // If it is an EL expression, we must evaluate it 529 // and return its value 530 if ( value != null 531 && ContainerUtils.isValueReference(value) 532 ) 533 { 534 Object elValue = null; 535 536 try 537 { 538 elValue = context.getApplication().evaluateExpressionGet(context, 539 value, 540 Object.class); 541 } 542 catch (Exception ex) 543 { 544 _LOG.warning("INVALID_EL_EXPRESSION", value); 545 _LOG.warning(ex); 546 return null; 547 } 548 return elValue; 549 } 550 551 return value; 552 } 553 554 /** 555 * getStream - Opens an InputStream to the provided URI. 556 * 557 * @param uri - String uri to a data source. 558 * @return InputStream to the data source. 559 */ 560 public InputStream getStream(String uri) 561 { 562 // This is public so that extended menu models can override 563 // and provide their own InputStream metadata source. 564 // And it is called by the MenuContentHandlerImpl. 565 try 566 { 567 // Open the metadata 568 FacesContext context = FacesContext.getCurrentInstance(); 569 URL url = context.getExternalContext().getResource(uri); 570 return url.openStream(); 571 } 572 catch (Exception ex) 573 { 574 _LOG.severe("OPEN_URI_EXCEPTION", uri); 575 _LOG.severe(ex); 576 return null; 577 } 578 } 579 580 /** 581 * Get the Model's viewIdFocusPathMap 582 * 583 * @return the Model's viewIdFocusPathMap 584 */ 585 public Map<String, List<Object>> getViewIdFocusPathMap() 586 { 587 if (!_isRoot || _contentHandler == null) 588 return null; 589 590 if (_viewIdFocusPathMap == null) 591 _viewIdFocusPathMap = _contentHandler.getViewIdFocusPathMap(_mdSource); 592 593 return _viewIdFocusPathMap; 594 } 595 596 /** 597 * Returns the map of content handlers 598 * which hold the state of one XML tree. 599 * @param scopeMap 600 * @return 601 */ 602 protected Map<Object, List<MenuContentHandler> > getContentHandlerMap() 603 { 604 FacesContext facesContext = FacesContext.getCurrentInstance(); 605 ExternalContext externalContext = facesContext.getExternalContext(); 606 Map<String, Object> scopeMap = 607 externalContext.getApplicationMap(); 608 Object lock = externalContext.getContext(); 609 610 // cannot use double checked lock here as 611 // we cannot mark the reference as volatile 612 // therefore any reads should happen inside 613 // a synchronized block. 614 synchronized (lock) 615 { 616 TransientHolder<Map<Object, List<MenuContentHandler> >> holder = 617 (TransientHolder<Map<Object, List<MenuContentHandler> >>) scopeMap.get(_CACHED_MODELS_KEY); 618 Map<Object, List<MenuContentHandler>> contentHandlerMap = (holder != null) ? holder.getValue() : null; 619 if (contentHandlerMap == null) 620 { 621 contentHandlerMap = 622 new ConcurrentHashMap<Object, List<MenuContentHandler>>(); 623 scopeMap.put(_CACHED_MODELS_KEY, TransientHolder.newTransientHolder( contentHandlerMap) ); 624 scopeMap.put(_CACHED_MODELS_ID_CNTR_KEY,new AtomicInteger(-1)); 625 } 626 return contentHandlerMap; 627 } 628 629 } 630 631 protected int getContentHandlerId() 632 { 633 FacesContext facesContext = FacesContext.getCurrentInstance(); 634 ExternalContext externalContext = facesContext.getExternalContext(); 635 Map<String, Object> scopeMap = 636 externalContext.getApplicationMap(); 637 AtomicInteger counter = (AtomicInteger) scopeMap.get(_CACHED_MODELS_ID_CNTR_KEY); 638 return counter.getAndIncrement(); 639 } 640 protected Object getCacheKey() 641 { 642 return _mdSource; 643 } 644 645 /* ==================================================================== 646 * Private Methods 647 * ==================================================================== */ 648 649 private Map<String, Object> _getIdNodeMap() 650 { 651 return (_isRoot) ? _idNodeMap : null; 652 } 653 654 /** 655 * Get a the MenuNode corresponding to the key "id" from the 656 * node id hashmap. 657 * 658 * @param id - String node id key for the hashmap entry. 659 * @return The MenuNode that corresponds to id. 660 */ 661 private Object _getNodeFromURLParams (String urlNodeId) 662 { 663 // This needs to be public because the nodes call into this map 664 return _idNodeMap.get(urlNodeId); 665 } 666 667 /** 668 * Creates a menu model based on the menu metadata Uri. 669 * This is accomplished by: 670 * <ol> 671 * <li> Get the MenuContentHandlerImpl through the Services API. 672 * <li> Set the root model and current model on the content handler, which, 673 * in turn, sets the models on each of the nodes. 674 * <li> Parse the metadata. This calls into the MenuContentHandler's 675 * startElement and endElement methods, where a List of nodes and a TreeModel 676 * are created, along with the 3 hashMaps needed by the Model.</li> 677 * <li> Use the TreeModel to create the XMLMenuModel.</li> 678 * </ol> 679 */ 680 private void _createModel() 681 { 682 try 683 { 684 // this block of code handles the injection of the 685 // correct content handler for this xml menu model. 686 _isRoot = _isThisRootModel(); 687 688 boolean newHandlerCreated = false; 689 List<MenuContentHandler> listOfHandlers = getContentHandlerMap().get(getCacheKey()); 690 Map<Integer,XMLMenuModel> requestModelMap = _getRootModelMap(); 691 if(listOfHandlers != null) 692 { 693 if(requestModelMap == null) 694 _contentHandler = listOfHandlers.get(0); 695 else 696 { 697 698 for (MenuContentHandler handler : listOfHandlers) 699 { 700 int id = handler.getId(); 701 if (!requestModelMap.containsKey(id)) 702 { 703 _contentHandler = handler; 704 break; 705 } 706 } 707 } 708 } 709 710 if(_contentHandler == null) 711 { 712 List<MenuContentHandler> services = 713 ClassLoaderUtils.getServices(_MENUCONTENTHANDLER_SERVICE); 714 715 if (services.isEmpty()) 716 { 717 throw new IllegalStateException(_LOG.getMessage( 718 "NO_MENUCONTENTHANDLER_REGISTERED")); 719 } 720 721 if(isCompatibilityMode()) 722 { 723 _contentHandler = services.get(0); 724 } 725 else 726 { 727 _contentHandler = services.get(1); 728 } 729 730 if (_contentHandler == null) 731 { 732 throw new NullPointerException(); 733 } 734 newHandlerCreated = true; 735 } 736 737 if(_isRoot) 738 { 739 if(listOfHandlers == null) 740 { 741 listOfHandlers = Collections.synchronizedList(new ArrayList<MenuContentHandler>()); 742 getContentHandlerMap().put(getCacheKey(), listOfHandlers); 743 } 744 if(newHandlerCreated) 745 { 746 listOfHandlers.add(_contentHandler); 747 _contentHandler.setRootHandler(true); 748 _contentHandler.setId(getContentHandlerId()); 749 } 750 751 } 752 753 // Set the root, top-level menu model's URI on the contentHandler. 754 // In this model, the menu content handler and nodes need to have 755 // access to the model's data structures and to notify the model 756 // of the currently selected node (in the case of a POST). 757 _populateRootModelMap(); 758 _setRootModelKey(_contentHandler); 759 760 // Set the local model (model created by a sharedNode) on the 761 // contentHandler so that nodes can get back to their local model 762 // if necessary. 763 764 // Nodes never get back to their local models,which is good 765 // because if they did they would not have found them as the 766 // hash code of newly created XMLMenuModels would be different 767 // and hence the modelIds of the menu nodes would be stale 768 // as they are longer lived than the menu models 769 770 // On the other hand the content handler does refer to it's 771 // local model during parsing at which time it is guaranteed 772 // to be referring to just one model due to synchronization 773 // of the parsing activity. 774 775 // Therefore we only set the model id if this is a newly 776 // created content handler,as we know it will start parsing 777 // shortly.This is also necessary so that the model id of 778 // the content handler does not change during the time it 779 // is initializing it's state by parsing.Thus we can do 780 // without synchronization here as the protection is needed only during 781 // parsing.Any other request thread for the same meta-data URI will 782 // find the content handler already published on the concurrent hash map 783 // of content handlers. 784 785 if(newHandlerCreated) 786 _setModelId(_contentHandler); 787 788 TreeModel treeModel = _contentHandler.getTreeModel(_mdSource); 789 setWrappedData(treeModel); 790 } 791 catch (Exception ex) 792 { 793 _LOG.severe("ERR_CREATE_MENU_MODEL", _mdSource); 794 _LOG.severe(ex); 795 return; 796 } 797 } 798 799 800 /** 801 * _setRootModelKey - sets the top-level, menu model's Key on the 802 * menu content handler. This is so nodes will only operate 803 * on the top-level, root model. 804 * 805 */ 806 @SuppressWarnings("unchecked") 807 private void _setRootModelKey(MenuContentHandler contentHandler) 808 { 809 contentHandler.setRootModelKey(_ROOT_MODEL_KEY); 810 } 811 812 813 /* 814 * sets the model into the requestMap 815 */ 816 private void _populateRootModelMap() 817 { 818 if (_isRoot) 819 { 820 Map<String, Object> requestMap = _getRequestMap(); 821 Map<Integer, XMLMenuModel> modelMap = 822 (Map<Integer, XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY); 823 if(modelMap == null) 824 { 825 modelMap = new HashMap<Integer,XMLMenuModel>(); 826 requestMap.put(_ROOT_MODEL_KEY, modelMap); 827 } 828 modelMap.put(_contentHandler.getId(), this); 829 } 830 } 831 832 private boolean _isThisRootModel() 833 { 834 Map<String, Object> requestMap = _getRequestMap(); 835 return !requestMap.containsKey(SHARED_MODEL_INDICATOR_KEY); 836 } 837 838 private Map<String, Object> _getRequestMap() 839 { 840 FacesContext facesContext = FacesContext.getCurrentInstance(); 841 Map<String, Object> requestMap = 842 facesContext.getExternalContext().getRequestMap(); 843 return requestMap; 844 } 845 846 847 /** 848 * Returns the root menu model. 849 * 850 * @return XMLMenuModel the root menu model. 851 */ 852 @SuppressWarnings("unchecked") 853 private XMLMenuModel _getRootModel() 854 { 855 Map<Integer, XMLMenuModel> map = _getRootModelMap(); 856 return map.get(_contentHandler.getId()); 857 } 858 859 private Map<Integer,XMLMenuModel> _getRootModelMap() 860 { 861 FacesContext facesContext = FacesContext.getCurrentInstance(); 862 Map<String, Object> requestMap = 863 facesContext.getExternalContext().getRequestMap(); 864 Map<Integer,XMLMenuModel> map = (Map<Integer,XMLMenuModel>) requestMap.get(_ROOT_MODEL_KEY); 865 return map; 866 } 867 868 869 /** 870 * _getModelId - gets the local, menu model's Sys Id. 871 * 872 * @return String the model's System Id. 873 */ 874 private String _getModelId() 875 { 876 return _modelId; 877 } 878 879 /** 880 * _setModelId - sets the local, menu model's id on the 881 * menu content handler. 882 */ 883 @SuppressWarnings("unchecked") 884 private void _setModelId(MenuContentHandler contentHandler) 885 { 886 String modelId = _getModelId(); 887 888 // Put the local model on the Request Map so that it 889 // Can be picked up by the nodes to call back into the 890 // local model 891 FacesContext facesContext = FacesContext.getCurrentInstance(); 892 Map<String, Object> requestMap = 893 facesContext.getExternalContext().getRequestMap(); 894 895 requestMap.put(modelId, this); 896 897 // Set the key (modelId) to the local model on the content 898 // handler so that it can then be set on each of the nodes 899 contentHandler.setModelId(modelId); 900 } 901 902 /** 903 * Returns the current viewId. 904 * 905 * @return the current viewId or null if the current viewId can't be found 906 */ 907 908 private String _getCurrentViewId() 909 { 910 String currentViewId = 911 FacesContext.getCurrentInstance().getViewRoot().getViewId(); 912 913 return currentViewId; 914 } 915 916 /** 917 * Gets the currently selected node in the menu 918 */ 919 private Object _getCurrentlySelectedNode() 920 { 921 return _currentNode; 922 } 923 924 /** 925 * Sets the currently selected node. 926 * 927 * @param currentNode. The currently selected node in the menu. 928 */ 929 private void _setCurrentlySelectedNode(Object currentNode) 930 { 931 _currentNode = currentNode; 932 } 933 934 /** 935 * Sets the request method 936 * 937 * @param method 938 */ 939 private void _setRequestMethod(String method) 940 { 941 _requestMethod = method; 942 } 943 944 /** 945 * Get the request method 946 */ 947 private String _getRequestMethod() 948 { 949 return _requestMethod; 950 } 951 952 /* ================================================================ 953 * Public inner interface for the menu content handler 954 * implementation 955 * ================================================================ */ 956 957 /* 958 * Interface corresponding to the MenuContentHandlerImpl 959 * in org.apache.myfaces.trinidadinternal.menu. This is used to achieve 960 * separation between the api (trinidad) and the implementation 961 * (trinidadinternal). It is only used by the XMLMenuModel, thus it is 962 * an internal interface. 963 */ 964 public interface MenuContentHandler 965 { 966 /** 967 * Get the TreeModel built while parsing metadata. 968 * 969 * @param uri String mapkey to a (possibly) treeModel cached on 970 * the MenuContentHandlerImpl. 971 * @return TreeModel. 972 */ 973 public TreeModel getTreeModel(String uri); 974 975 /** 976 * Sets the root model's request map key on the ContentHandler so 977 * that the nodes can get back to their root model 978 * through the request map. 979 */ 980 public void setRootModelKey(String key); 981 982 /** 983 * Sets the local, sharedNode model's Model id on the ContentHandler so that 984 * the local model can be gotten to, if necessary. 985 */ 986 public void setModelId(String modelId); 987 988 /** 989 * Get the Model's idNodeMap 990 * 991 * @return the Model's idNodeMap 992 */ 993 public Map<String, Object> getIdNodeMap(Object modelKey); 994 995 /** 996 * Get the Model's nodeFocusPathMap 997 * 998 * @return the Model's nodeFocusPathMap 999 */ 1000 public Map<Object, List<Object>> getNodeFocusPathMap(Object modelKey); 1001 1002 /** 1003 * Get the Model's viewIdFocusPathMap 1004 * 1005 * @return the Model's viewIdFocusPathMap 1006 */ 1007 public Map<String, List<Object>> getViewIdFocusPathMap(Object modelKey); 1008 1009 public void setRootHandler(boolean isRoot); 1010 1011 /** 1012 * sets the id of this content handler 1013 */ 1014 public void setId(int id); 1015 1016 /** 1017 * gets the id of this content handler 1018 * @return the id 1019 */ 1020 public int getId(); 1021 1022 } 1023 1024 private Object _currentNode = null; 1025 private Object _prevFocusPath = null; 1026 private String _prevViewId = null; 1027 private String _requestMethod = _METHOD_NONE; 1028 private String _mdSource = null; 1029 private boolean _createHiddenNodes = false; 1030 private String _modelId = null; 1031 1032 private Map<String, List<Object>> _viewIdFocusPathMap; 1033 private Map<Object, List<Object>> _nodeFocusPathMap; 1034 private Map<String, Object> _idNodeMap; 1035 1036 private MenuContentHandler _contentHandler = null; 1037 private boolean _isRoot; 1038 1039 1040 // Only set this if _currentNode is a duplicate 1041 private Object _prevRequestNode = null; 1042 1043 // this key is used to store the map of models in the request. 1044 static private final String _ROOT_MODEL_KEY = 1045 "org.apache.myfaces.trinidad.model.XMLMenuModel.__root_menu__"; 1046 1047 // this key is used to store the map of content handlers in the scope map 1048 static private final String _CACHED_MODELS_KEY = 1049 "org.apache.myfaces.trinidad.model.XMLMenuModel.__handler_key__"; 1050 1051 // this supplies the id of the content handlers 1052 static private final String _CACHED_MODELS_ID_CNTR_KEY = 1053 "org.apache.myfaces.trinidad.model.XMLMenuModel.__id_cntr_key__"; 1054 1055 /** 1056 * This key is used to store information about the included xml menu 1057 * models which are constructed during parsing. 1058 */ 1059 static public final String SHARED_MODEL_INDICATOR_KEY = 1060 "org.apache.myfaces.trinidad.model.XMLMenuModel.__indicator_key__"; 1061 1062 static private final String _NODE_ID_PROPERTY = "nodeId"; 1063 static private final String _METHOD_GET = "get"; 1064 static private final String _METHOD_POST = "post"; 1065 static private final String _METHOD_NONE = "none"; 1066 static private final String _CUSTOM_ATTR_LIST = "customPropList"; 1067 static private final String _MENUCONTENTHANDLER_SERVICE = 1068 "org.apache.myfaces.trinidad.model.XMLMenuModel$MenuContentHandler"; 1069 1070 static private final TrinidadLogger _LOG = 1071 TrinidadLogger.createTrinidadLogger(XMLMenuModel.class); 1072 1073 }