001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.lifecycle;
017
018import java.lang.reflect.Array;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.LinkedHashMap;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Queue;
030import java.util.Set;
031import java.util.WeakHashMap;
032
033import org.apache.commons.lang.StringUtils;
034import org.apache.log4j.Logger;
035import org.kuali.rice.core.api.CoreApiServiceLocator;
036import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
037import org.kuali.rice.krad.uif.UifConstants;
038import org.kuali.rice.krad.uif.component.Component;
039import org.kuali.rice.krad.uif.util.CopyUtils;
040import org.kuali.rice.krad.uif.util.LifecycleElement;
041import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
042import org.kuali.rice.krad.uif.util.RecycleUtils;
043import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
044import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
045import org.kuali.rice.krad.uif.view.View;
046import org.kuali.rice.krad.util.GlobalVariables;
047import org.kuali.rice.krad.util.KRADConstants;
048
049/**
050 * Utilities for working with {@link LifecycleElement} instances.
051 *
052 * @author Kuali Rice Team (rice.collab@kuali.org)
053 */
054public final class ViewLifecycleUtils {
055    private static final Logger LOG = Logger.getLogger(ViewLifecycleUtils.class);
056
057    private static final String COMPONENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.COMPONENT + '.';
058    private static final String PARENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.PARENT + '.';
059    
060    /**
061     * Gets property names of all bean properties on the lifecycle element restricted for the
062     * indicated view phase.
063     *
064     * @param element The lifecycle element.
065     * @param viewPhase The view phase to retrieve restrictions for.
066     * @return set of property names
067     */
068    public static Set<String> getLifecycleRestrictedProperties(LifecycleElement element, String viewPhase) {
069        Set<String> restrictedPropertyNames = getMetadata(element.getClass()).lifecycleRestrictedProperties.get(
070                viewPhase);
071        if (restrictedPropertyNames == null) {
072            return Collections.emptySet();
073        } else {
074            return restrictedPropertyNames;
075        }
076    }
077
078    /**
079     * Gets the next lifecycle phase to be executed on the provided element.
080     *
081     * <dl>
082     * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#CREATED CREATED}</dt>
083     * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#CACHED CACHED}</dt>
084     * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#INITIALIZE INITIALIZE}</dd>
085     * 
086     * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#INITIALIZED INITIALIZED}</dt>
087     * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#APPLY_MODEL APPLY_MODEL}</dd>
088     * 
089     * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#MODEL_APPLIED MODEL_APPLIED}</dt>
090     * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#FINALIZE FINALIZE}</dd>
091     * 
092     * <dt>{@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#FINAL FINAL}</dt>
093     * <dd>{@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#RENDER RENDER}</dd>
094     * </dl>
095     *
096     * <p>
097     * If the view status is null, invalid, or {@link org.kuali.rice.krad.uif.UifConstants.ViewStatus#RENDERED RENDERED},
098     * then {@link org.kuali.rice.krad.uif.UifConstants.ViewPhases#INITIALIZE} will be returned and a warning logged.
099     * </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}