View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.lifecycle;
17  
18  import java.lang.reflect.Array;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Queue;
30  import java.util.Set;
31  import java.util.WeakHashMap;
32  
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.log4j.Logger;
35  import org.kuali.rice.core.api.CoreApiServiceLocator;
36  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
37  import org.kuali.rice.krad.uif.UifConstants;
38  import org.kuali.rice.krad.uif.component.Component;
39  import org.kuali.rice.krad.uif.util.CopyUtils;
40  import org.kuali.rice.krad.uif.util.LifecycleElement;
41  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
42  import org.kuali.rice.krad.uif.util.RecycleUtils;
43  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
44  import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
45  import org.kuali.rice.krad.uif.view.View;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  import org.kuali.rice.krad.util.KRADConstants;
48  
49  /**
50   * Utilities for working with {@link LifecycleElement} instances.
51   *
52   * @author Kuali Rice Team (rice.collab@kuali.org)
53   */
54  public final class ViewLifecycleUtils {
55      private static final Logger LOG = Logger.getLogger(ViewLifecycleUtils.class);
56  
57      private static final String COMPONENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.COMPONENT + '.';
58      private static final String PARENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.PARENT + '.';
59      
60      /**
61       * Gets property names of all bean properties on the lifecycle element restricted for the
62       * indicated view phase.
63       *
64       * @param element The lifecycle element.
65       * @param viewPhase The view phase to retrieve restrictions for.
66       * @return set of property names
67       */
68      public static Set<String> getLifecycleRestrictedProperties(LifecycleElement element, String viewPhase) {
69          Set<String> restrictedPropertyNames = getMetadata(element.getClass()).lifecycleRestrictedProperties.get(
70                  viewPhase);
71          if (restrictedPropertyNames == null) {
72              return Collections.emptySet();
73          } else {
74              return restrictedPropertyNames;
75          }
76      }
77  
78      /**
79       * Gets the next lifecycle phase to be executed on the provided element.
80       *
81       * <dl>
82       * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#CREATED CREATED}</dt>
83       * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#CACHED CACHED}</dt>
84       * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#INITIALIZE INITIALIZE}</dd>
85       * 
86       * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#INITIALIZED INITIALIZED}</dt>
87       * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#APPLY_MODEL APPLY_MODEL}</dd>
88       * 
89       * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#MODEL_APPLIED MODEL_APPLIED}</dt>
90       * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#FINALIZE FINALIZE}</dd>
91       * 
92       * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#FINAL FINAL}</dt>
93       * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#RENDER RENDER}</dd>
94       * </dl>
95       *
96       * <p>
97       * If the view status is null, invalid, or {@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#RENDERED RENDERED},
98       * then {@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#INITIALIZE} will be returned and a warning logged.
99       * </p>
100      *
101      * @param element The lifecycle element.
102      * @return The next phase in the element's lifecycle based on view status
103      * @see LifecycleElement#getViewStatus()
104      * @see org.kuali.rice.krad.uif.UifConstants.ViewPhases
105      */
106     public static String getNextLifecyclePhase(LifecycleElement element) {
107         if (element == null) {
108             return UifConstants.ViewPhases.INITIALIZE;
109         }
110 
111         String viewStatus = element.getViewStatus();
112 
113         if (viewStatus == null || UifConstants.ViewStatus.CACHED.equals(viewStatus) || UifConstants.ViewStatus.CREATED
114                 .equals(viewStatus)) {
115             return UifConstants.ViewPhases.INITIALIZE;
116 
117         } else if (UifConstants.ViewStatus.INITIALIZED.equals(viewStatus)) {
118             return UifConstants.ViewPhases.APPLY_MODEL;
119 
120         } else if (UifConstants.ViewStatus.MODEL_APPLIED.equals(viewStatus)) {
121             return UifConstants.ViewPhases.FINALIZE;
122 
123         } else if (UifConstants.ViewStatus.FINAL.equals(viewStatus) || UifConstants.ViewStatus.RENDERED.equals(
124                 viewStatus)) {
125             return UifConstants.ViewPhases.RENDER;
126 
127         } else {
128             ViewLifecycle.reportIllegalState("Invalid view status " + viewStatus);
129             return UifConstants.ViewPhases.INITIALIZE;
130         }
131     }
132 
133     /**
134      * Gets sub-elements for lifecycle processing.
135      *
136      * @param element The element to scan.
137      * @return map of lifecycle elements
138      */
139     public static Map<String, LifecycleElement> getElementsForLifecycle(LifecycleElement element) {
140         return getElementsForLifecycle(element, getNextLifecyclePhase(element));
141     }
142 
143     /**
144      * Helper method for {@link #getElementsForLifecycle(LifecycleElement, String)}.
145      *
146      * <p>
147      * Unwraps the lifecycle element if not null, and dynamically creates the lifecycle map when the
148      * first non-null element is added.
149      * </p>
150      *
151      * @param map The lifecycle map.
152      * @param propertyName The property path to the nested element.
153      * @param nestedElement The nested element to add.
154      * @return map, or a newly created map if the provided map was empty and the nested element was
155      * not null.
156      */
157     private static Map<String, LifecycleElement> addElementToLifecycleMap(Map<String, LifecycleElement> map,
158             String propertyName, LifecycleElement nestedElement) {
159         if (nestedElement == null) {
160             return map;
161         }
162 
163         Map<String, LifecycleElement> returnMap = map;
164         if (returnMap == Collections.EMPTY_MAP) {
165             returnMap = RecycleUtils.getInstance(LinkedHashMap.class);
166         }
167 
168         returnMap.put(propertyName, CopyUtils.unwrap((LifecycleElement) nestedElement));
169         return returnMap;
170     }
171 
172     /**
173      * Gets subcomponents for lifecycle processing.
174      *
175      * @param element The component to scan.
176      * @param viewPhase The view phase to return subcomponents for.
177      * @return lifecycle components
178      */
179     public static Map<String, LifecycleElement> getElementsForLifecycle(LifecycleElement element, String viewPhase) {
180         if (element == null) {
181             return Collections.emptyMap();
182         }
183 
184         Set<String> nestedElementProperties = ObjectPropertyUtils.getReadablePropertyNamesByType(element,
185                 LifecycleElement.class);
186         Set<String> nestedElementCollectionProperties = ObjectPropertyUtils.getReadablePropertyNamesByCollectionType(
187                 element, LifecycleElement.class);
188         if (nestedElementProperties.isEmpty() && nestedElementCollectionProperties.isEmpty()) {
189             return Collections.emptyMap();
190         }
191 
192         Set<String> restrictedPropertyNames = getLifecycleRestrictedProperties(element, viewPhase);
193         Map<String, String> conditionalPropertyRestrictions = getMetadata(
194                 element.getClass()).conditionalPropertyRestrictions;
195 
196         Map<String, LifecycleElement> elements = Collections.emptyMap();
197         for (String propertyName : nestedElementProperties) {
198             if (conditionalPropertyRestrictions.containsKey(propertyName) && ViewLifecycle.isActive() && !UifConstants
199                     .ViewPhases.PRE_PROCESS.equals(viewPhase)) {
200                 boolean conditionResult = evaluateLifecycleCondition(conditionalPropertyRestrictions.get(propertyName));
201                 if (!conditionResult) {
202                     continue;
203                 }
204             }
205 
206             if (restrictedPropertyNames.contains(propertyName)) {
207                 continue;
208             }
209 
210             Object propertyValue = ObjectPropertyUtils.getPropertyValue(element, propertyName);
211             elements = addElementToLifecycleMap(elements, propertyName, (LifecycleElement) propertyValue);
212         }
213 
214         for (String propertyName : nestedElementCollectionProperties) {
215             if (conditionalPropertyRestrictions.containsKey(propertyName) && ViewLifecycle.isActive() && !UifConstants
216                     .ViewPhases.PRE_PROCESS.equals(viewPhase)) {
217                 boolean conditionResult = evaluateLifecycleCondition(conditionalPropertyRestrictions.get(propertyName));
218                 if (!conditionResult) {
219                     continue;
220                 }
221             }
222 
223             if (restrictedPropertyNames.contains(propertyName)) {
224                 continue;
225             }
226 
227             Object nestedElementCollection = ObjectPropertyUtils.getPropertyValue(element, propertyName);
228             if (element.getClass().isArray()) {
229                 for (int i = 0; i < Array.getLength(nestedElementCollection); i++) {
230                     elements = addElementToLifecycleMap(elements, propertyName + "[" + i + "]",
231                             (LifecycleElement) Array.get(nestedElementCollection, i));
232                 }
233             } else if (nestedElementCollection instanceof List) {
234                 for (int i = 0; i < ((List<?>) nestedElementCollection).size(); i++) {
235                     elements = addElementToLifecycleMap(elements, propertyName + "[" + i + "]",
236                             (LifecycleElement) ((List<?>) nestedElementCollection).get(i));
237                 }
238             } else if (nestedElementCollection instanceof Map) {
239                 for (Entry<?, ?> entry : ((Map<?, ?>) nestedElementCollection).entrySet()) {
240                     elements = addElementToLifecycleMap(elements, propertyName + "[" + entry.getKey() + "]",
241                             (LifecycleElement) entry.getValue());
242                 }
243             }
244         }
245 
246         return elements == Collections.EMPTY_MAP ? elements : Collections.unmodifiableMap(elements);
247     }
248 
249     /**
250      * Evaluates a condition on a property lifecycle restriction.
251      *
252      * @param condition expression to evaluate
253      * @return boolean result of the expression evaluation
254      */
255     private static boolean evaluateLifecycleCondition(String condition) {
256         ExpressionEvaluator expressionEvaluator =
257                 KRADServiceLocatorWeb.getExpressionEvaluatorFactory().createExpressionEvaluator();
258         expressionEvaluator.initializeEvaluationContext(ViewLifecycle.getModel());
259 
260         Map<String, Object> context = new HashMap<String, Object>();
261 
262         Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService().getAllProperties();
263         context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
264         context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
265         context.put(UifConstants.ContextVariableNames.UIF_CONSTANTS, UifConstants.class);
266         context.put(UifConstants.ContextVariableNames.USER_SESSION, GlobalVariables.getUserSession());
267 
268         Boolean result = (Boolean) expressionEvaluator.evaluateExpression(context, condition);
269 
270         return result.booleanValue();
271     }
272     
273     /**
274      * Recycle a map returned by {@link #getElementsForLifecycle(LifecycleElement, String)}.
275      * 
276      * @param elementMap map to recycle
277      */
278     public static void recycleElementMap(Map<?, ?> elementMap) {
279         if (elementMap instanceof LinkedHashMap) {
280             elementMap.clear();
281             RecycleUtils.recycle(elementMap);
282         }
283     }
284 
285     /**
286      * Return the lifecycle elements of the specified type from the given list
287      *
288      * <p>
289      * Elements that match, implement or are extended from the specified {@code elementType} are
290      * returned in the result. If an element is a parent to other elements then these child elements
291      * are searched for matching types as well.
292      * </p>
293      *
294      * @param items list of elements from which to search
295      * @param elementType the class or interface of the element type to return
296      * @param <T> the type of the elements that are returned
297      * @return List of matching elements
298      */
299     public static <T extends LifecycleElement> List<T> getElementsOfTypeDeep(
300             Collection<? extends LifecycleElement> items, Class<T> elementType) {
301         if (items == null) {
302             return Collections.emptyList();
303         }
304 
305         List<T> elements = Collections.emptyList();
306 
307         @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
308                 LinkedList.class);
309         elementQueue.addAll(items);
310 
311         try {
312             while (!elementQueue.isEmpty()) {
313                 LifecycleElement currentElement = elementQueue.poll();
314                 if (currentElement == null) {
315                     continue;
316                 }
317 
318                 if (elementType.isInstance(currentElement)) {
319                     if (elements.isEmpty()) {
320                         elements = new ArrayList<T>();
321                     }
322 
323                     elements.add(elementType.cast(currentElement));
324                 }
325 
326                 elementQueue.addAll(getElementsForLifecycle(currentElement).values());
327             }
328         } finally {
329             elementQueue.clear();
330             RecycleUtils.recycle(elementQueue);
331         }
332         return elements;
333     }
334 
335     /**
336      * Returns the lifecycle elements of the specified type from the given list.
337      *
338      * <p>
339      * Elements that match, implement or are extended from the specified {@code elementType} are
340      * returned in the result. If an element is a parent to other elements then these child
341      * components are searched for matching component types as well.
342      * </p>
343      *
344      * @param element The elements to search
345      * @param elementType the class or interface of the elements type to return
346      * @param <T> the type of the elements that are returned
347      * @return List of matching elements
348      */
349     public static <T extends LifecycleElement> List<T> getElementsOfTypeDeep(LifecycleElement element,
350             Class<T> elementType) {
351         return getElementsOfTypeDeep(Collections.singletonList(element), elementType);
352     }
353 
354     /**
355      * Returns elements of the given type that are direct children of the given element including
356      * itself, or a child of a non-component child of the given element.
357      *
358      * <p>
359      * Deep search is only performed on non-component nested elements.
360      * </p>
361      *
362      * @param element instance to get children for
363      * @param elementType type for component to return
364      * @param <T> type of component that will be returned
365      * @return list of child components with the given type
366      */
367     public static <T extends LifecycleElement> List<T> getElementsOfTypeShallow(LifecycleElement element,
368             Class<T> elementType) {
369         if (element == null) {
370             return Collections.emptyList();
371         }
372 
373         List<T> typeElements = getNestedElementsOfTypeShallow(element, elementType);
374 
375         if (elementType.isInstance(element)) {
376             if (typeElements.isEmpty()) {
377                 typeElements = Collections.singletonList(elementType.cast(element));
378             } else {
379                 typeElements.add(0, elementType.cast(element));
380             }
381         }
382 
383         return typeElements;
384     }
385 
386     /**
387      * Get nested elements of the type specified one layer deep; this defers from
388      * getElementsOfTypeShallow because it does NOT include itself as a match if it also matches the
389      * type being requested.
390      *
391      * @param element instance to get children for
392      * @param elementType type for element to return
393      * @param <T> type of element that will be returned
394      * @return list of child elements with the given type
395      */
396     public static <T extends LifecycleElement> List<T> getNestedElementsOfTypeShallow(LifecycleElement element,
397             Class<T> elementType) {
398         if (element == null) {
399             return Collections.emptyList();
400         }
401 
402         List<T> elements = Collections.emptyList();
403 
404         @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
405                 LinkedList.class);
406         try {
407             elementQueue.add(element);
408 
409             while (!elementQueue.isEmpty()) {
410                 LifecycleElement currentElement = elementQueue.poll();
411                 if (currentElement == null) {
412                     continue;
413                 }
414 
415                 if (elementType.isInstance(currentElement) && currentElement != element) {
416                     if (elements.isEmpty()) {
417                         elements = new ArrayList<T>();
418                     }
419 
420                     elements.add(elementType.cast(currentElement));
421                 }
422 
423                 for (LifecycleElement nestedElement : getElementsForLifecycle(currentElement).values()) {
424                     if (!(nestedElement instanceof Component)) {
425                         elementQueue.offer(nestedElement);
426                     }
427                 }
428             }
429         } finally {
430             elementQueue.clear();
431             RecycleUtils.recycle(elementQueue);
432         }
433         return elements;
434     }
435 
436     /**
437      * Private constructor - utility class only.
438      */
439     private ViewLifecycleUtils() {}
440 
441     /**
442      * Internal metadata cache.
443      */
444     private static final Map<Class<?>, ElementMetadata> METADATA_CACHE = Collections.synchronizedMap(
445             new WeakHashMap<Class<?>, ElementMetadata>(2048));
446 
447     /**
448      * Gets the element metadata for a lifecycle element implementation class.
449      *
450      * @param elementClass The {@link LifecycleElement} class.
451      * @return {@link ElementMetadata} instance for elementClass
452      */
453     private static ElementMetadata getMetadata(Class<?> elementClass) {
454         ElementMetadata metadata = METADATA_CACHE.get(elementClass);
455 
456         if (metadata == null) {
457             metadata = new ElementMetadata(elementClass);
458             METADATA_CACHE.put(elementClass, metadata);
459         }
460 
461         return metadata;
462     }
463 
464     /**
465      * Stores metadata related to a lifecycle element class, for reducing overhead.
466      */
467     private static class ElementMetadata {
468 
469         // set of all restricted properties on the element class, keyed by view phase
470         private final Map<String, Set<String>> lifecycleRestrictedProperties;
471 
472         // properties that have an condition for inclusion in lifecycle, key is property name
473         // value is the condition to evaluate
474         private final Map<String, String> conditionalPropertyRestrictions;
475 
476         /**
477          * Creates a new metadata wrapper for a bean class.
478          *
479          * @param elementClass The element class.
480          */
481         private ElementMetadata(Class<?> elementClass) {
482             Set<String> restrictedPropertyNames = ObjectPropertyUtils.getReadablePropertyNamesByAnnotationType(
483                     elementClass, ViewLifecycleRestriction.class);
484 
485             if (restrictedPropertyNames.isEmpty()) {
486                 lifecycleRestrictedProperties = Collections.emptyMap();
487                 conditionalPropertyRestrictions = Collections.emptyMap();
488 
489                 return;
490             }
491 
492             Map<String, Set<String>> mutableLifecycleRestrictedProperties = new HashMap<String, Set<String>>();
493 
494             mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.FINALIZE, new HashSet<String>(
495                     restrictedPropertyNames));
496             mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.APPLY_MODEL, new HashSet<String>(
497                     restrictedPropertyNames));
498             mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.INITIALIZE, new HashSet<String>(
499                     restrictedPropertyNames));
500             mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.PRE_PROCESS, new HashSet<String>(
501                     restrictedPropertyNames));
502 
503             Map<String, String> mutableConditionalLifecycleProperties = new HashMap<>();
504 
505             // remove properties that should be included for certain phases
506             for (String restrictedPropertyName : restrictedPropertyNames) {
507                 ViewLifecycleRestriction restriction = ObjectPropertyUtils.getReadMethod(elementClass,
508                         restrictedPropertyName).getAnnotation(ViewLifecycleRestriction.class);
509 
510                 if (restriction.value().length > 0) {
511                     removedRestrictionsForPrecedingPhases(mutableLifecycleRestrictedProperties, restrictedPropertyName,
512                             restriction.value()[0]);
513                 } else if (restriction.exclude().length > 0) {
514                     // include all by default if a exclude is defined
515                     removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
516                             UifConstants.ViewPhases.PRE_PROCESS);
517                     removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
518                             UifConstants.ViewPhases.INITIALIZE);
519                     removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
520                             UifConstants.ViewPhases.APPLY_MODEL);
521                     removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
522                             UifConstants.ViewPhases.FINALIZE);
523                 }
524 
525                 // add back explicit exclusions
526                 if (restriction.exclude().length > 0) {
527                     for (String excludePhase : restriction.exclude()) {
528                         Set<String> restrictedProperties = mutableLifecycleRestrictedProperties.get(excludePhase);
529 
530                         restrictedProperties.add(restrictedPropertyName);
531                     }
532                 }
533 
534                 if (StringUtils.isNotBlank(restriction.condition())) {
535                     mutableConditionalLifecycleProperties.put(restrictedPropertyName, restriction.condition());
536                 }
537             }
538 
539             lifecycleRestrictedProperties = Collections.unmodifiableMap(mutableLifecycleRestrictedProperties);
540             conditionalPropertyRestrictions = Collections.unmodifiableMap(mutableConditionalLifecycleProperties);
541         }
542 
543         private void removedRestrictionsForPrecedingPhases(
544                 Map<String, Set<String>> mutableLifecycleRestrictedProperties, String propertyName, String phase) {
545             if (phase.equals(UifConstants.ViewPhases.FINALIZE)) {
546                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
547                         UifConstants.ViewPhases.FINALIZE);
548                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
549                         UifConstants.ViewPhases.APPLY_MODEL);
550                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
551                         UifConstants.ViewPhases.INITIALIZE);
552                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
553                         UifConstants.ViewPhases.PRE_PROCESS);
554             } else if (phase.equals(UifConstants.ViewPhases.APPLY_MODEL)) {
555                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
556                         UifConstants.ViewPhases.APPLY_MODEL);
557                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
558                         UifConstants.ViewPhases.INITIALIZE);
559                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
560                         UifConstants.ViewPhases.PRE_PROCESS);
561             } else if (phase.equals(UifConstants.ViewPhases.INITIALIZE)) {
562                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
563                         UifConstants.ViewPhases.INITIALIZE);
564                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
565                         UifConstants.ViewPhases.PRE_PROCESS);
566             } else if (phase.equals(UifConstants.ViewPhases.PRE_PROCESS)) {
567                 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
568                         UifConstants.ViewPhases.PRE_PROCESS);
569             }
570         }
571 
572         private void removedRestrictionsForPhase(Map<String, Set<String>> mutableLifecycleRestrictedProperties,
573                 String propertyName, String phase) {
574             Set<String> restrictedProperties = mutableLifecycleRestrictedProperties.get(phase);
575 
576             restrictedProperties.remove(propertyName);
577         }
578 
579         private void addRestrictionForAllPhases(Map<String, Set<String>> mutableLifecycleRestrictedProperties,
580                         String propertyName) {
581             mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.PRE_PROCESS).add(propertyName);
582             mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.INITIALIZE).add(propertyName);
583             mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.APPLY_MODEL).add(propertyName);
584             mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.FINALIZE).add(propertyName);
585         }
586     }
587 
588     /**
589      * Determines if a component should be excluded from the current lifecycle.
590      * 
591      * @param component The component.
592      */
593     public static boolean isExcluded(Component component) {
594         String excludeUnless = component.getExcludeUnless();        
595         if (StringUtils.isNotBlank(excludeUnless) &&
596                 !resolvePropertyPath(excludeUnless, component)) {
597             return true;
598         }
599         
600         return resolvePropertyPath(component.getExcludeIf(), component);
601     }
602 
603     /**
604      * Helper method for use with {@link #isExcluded(Component)}.
605      * 
606      * <p>
607      * Resolves a property path based on either the model, or on the pre-model context when the path
608      * expression starts with '#'. Note that this method is intended for resolution at the
609      * initialize phase, so the full context is not available. However, in addition to the values
610      * evident in {@link View#getPreModelContext()}, #component and #parent will resolve to the
611      * component, and its lifecycle parent, respectively.
612      * </p>
613      * 
614      * @param path property path
615      * @param component component to evaluate the expression relative to
616      * @return true if the path resolves to the boolean value true, otherwise false
617      */
618     private static boolean resolvePropertyPath(String path, Component component) {
619         if (StringUtils.isBlank(path)) {
620             return false;
621         }
622 
623         Object root;
624         if (path.startsWith(COMPONENT_CONTEXT_PREFIX)) {
625             root = component;
626             path = path.substring(COMPONENT_CONTEXT_PREFIX.length());
627         } else if (path.startsWith(PARENT_CONTEXT_PREFIX)) {
628             root = ViewLifecycle.getPhase().getParent();
629             path = path.substring(PARENT_CONTEXT_PREFIX.length());
630         } else if (path.charAt(0) == '#') {
631             Map<String, Object> context = ViewLifecycle.getView().getPreModelContext();
632 
633             int iod = path.indexOf('.');
634             if (iod == -1) {
635                 return Boolean.TRUE.equals(context.get(path.substring(1)));
636             }
637 
638             String contextVariable = path.substring(1, iod);
639             root = context.get(contextVariable);
640             path = path.substring(iod + 1);
641         } else {
642             root = ViewLifecycle.getModel();
643         }
644 
645         return Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(root, path));
646     }
647     
648 }