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 }