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}