001/** 002 * Copyright 2005-2015 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 org.apache.commons.lang.StringUtils; 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021import org.apache.log4j.Logger; 022import org.kuali.rice.core.api.CoreApiServiceLocator; 023import org.kuali.rice.core.api.config.property.Config; 024import org.kuali.rice.core.api.config.property.ConfigContext; 025import org.kuali.rice.krad.datadictionary.validator.ValidationController; 026import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 027import org.kuali.rice.krad.uif.UifConstants; 028import org.kuali.rice.krad.uif.UifPropertyPaths; 029import org.kuali.rice.krad.uif.component.Component; 030import org.kuali.rice.krad.uif.container.Group; 031import org.kuali.rice.krad.uif.freemarker.LifecycleRenderingContext; 032import org.kuali.rice.krad.uif.service.ViewHelperService; 033import org.kuali.rice.krad.uif.util.LifecycleElement; 034import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 035import org.kuali.rice.krad.uif.view.DefaultExpressionEvaluator; 036import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 037import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory; 038import org.kuali.rice.krad.uif.view.View; 039import org.kuali.rice.krad.util.KRADConstants; 040import org.kuali.rice.krad.web.form.UifFormBase; 041 042import javax.servlet.http.HttpServletRequest; 043import java.io.Serializable; 044import java.util.ArrayList; 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.HashSet; 048import java.util.List; 049import java.util.Map; 050import java.util.Set; 051 052/** 053 * Lifecycle object created during the view processing to hold event registrations. 054 * 055 * @author Kuali Rice Team (rice.collab@kuali.org) 056 * @see LifecycleEventListener 057 */ 058public class ViewLifecycle implements Serializable { 059 060 private static Logger LOG = Logger.getLogger(ViewLifecycle.class); 061 private static final long serialVersionUID = -4767600614111642241L; 062 063 private static final ThreadLocal<ViewLifecycleProcessor> PROCESSOR = new ThreadLocal<ViewLifecycleProcessor>(); 064 065 private static Boolean strict; 066 private static Boolean renderInLifecycle; 067 private static Boolean trace; 068 069 private final List<EventRegistration> eventRegistrations; 070 private final View view; 071 072 private final ComponentPostMetadata refreshComponentPostMetadata; 073 074 final ViewHelperService helper; 075 076 final Object model; 077 078 final HttpServletRequest request; 079 private ViewPostMetadata viewPostMetadata; 080 081 private Set<String> visitedIds; 082 083 private static String refreshComponentId; 084 085 /** 086 * Private constructor, for spawning a lifecycle context. 087 * 088 * @param view The view to process with the lifecycle 089 * @param model The model to use in processing the lifecycle 090 * @param refreshComponentPostMetadata when a refresh lifecycle is requested, post metadata for the component 091 * that is being refreshed 092 * @param request The active servlet request 093 * @see #getActiveLifecycle() For access to a thread-local instance. 094 */ 095 private ViewLifecycle(View view, Object model, ComponentPostMetadata refreshComponentPostMetadata, 096 HttpServletRequest request) { 097 this.view = view; 098 this.model = model; 099 this.request = request; 100 this.refreshComponentPostMetadata = refreshComponentPostMetadata; 101 this.helper = view.getViewHelperService(); 102 this.eventRegistrations = new ArrayList<EventRegistration>(); 103 } 104 105 /** 106 * Encapsulate a new view lifecycle process on the current thread. 107 * 108 * @param view The view to perform lifecycle processing on. 109 * @param model The model associated with the view. 110 * @param request The active servlet request. 111 * @param lifecycleProcess The lifecycle process to encapsulate. 112 */ 113 public static void encapsulateLifecycle(View view, Object model, HttpServletRequest request, 114 Runnable lifecycleProcess) { 115 encapsulateLifecycle(view, model, null, null, request, lifecycleProcess); 116 } 117 118 /** 119 * Encapsulate a new view lifecycle process on the current thread. 120 * 121 * @param lifecycleProcess The lifecycle process to encapsulate. 122 */ 123 public static void encapsulateLifecycle(View view, Object model, ViewPostMetadata viewPostMetadata, 124 ComponentPostMetadata refreshComponentPostMetadata, HttpServletRequest request, Runnable lifecycleProcess) { 125 ViewLifecycleProcessor processor = PROCESSOR.get(); 126 if (processor != null) { 127 throw new IllegalStateException("Another lifecycle is already active on this thread"); 128 } 129 130 try { 131 ViewLifecycle viewLifecycle = new ViewLifecycle(view, model, refreshComponentPostMetadata, request); 132 processor = isAsynchronousLifecycle() ? new AsynchronousViewLifecycleProcessor(viewLifecycle) : 133 new SynchronousViewLifecycleProcessor(viewLifecycle); 134 PROCESSOR.set(processor); 135 136 if (viewPostMetadata != null) { 137 viewLifecycle.viewPostMetadata = viewPostMetadata; 138 } 139 140 lifecycleProcess.run(); 141 } finally { 142 PROCESSOR.remove(); 143 } 144 } 145 146 /** 147 * Performs preliminary processing on a view, prior to caching. 148 * 149 * <p>Logic evaluated at this preliminary phase result in global modifications to the view's 150 * subcomponents, so this method can be used apply additional logic to the View that is both 151 * pre-evaluated and shared by all instances of the component.</p> 152 * 153 * @param view view to preprocess 154 */ 155 public static void preProcess(View view) { 156 encapsulateLifecycle(view, null, null, new ViewLifecyclePreProcessBuild()); 157 } 158 159 /** 160 * Executes the view lifecycle on the given <code>View</code> instance which will prepare it for 161 * rendering 162 * 163 * <p> 164 * Any configuration sent through the options Map is used to initialize the View. This map 165 * contains present options the view is aware of and will typically come from request 166 * parameters. e.g. For maintenance Views there is the maintenance type option (new, edit, copy) 167 * </p> 168 * 169 * <p> 170 * After view retrieval, applies updates to the view based on the model data which Performs 171 * dynamic generation of fields (such as collection rows), conditional logic, and state updating 172 * (conditional hidden, read-only, required). 173 * </p> 174 * 175 * @param view view instance that should be built 176 * @param model object instance containing the view data 177 * @param request The active servlet request. 178 * @param parameters - Map of key values pairs that provide configuration for the 179 * <code>View</code>, this is generally comes from the request and can be the request 180 * parameter Map itself. Any parameters not valid for the View will be filtered out 181 */ 182 public static ViewPostMetadata buildView(View view, Object model, HttpServletRequest request, 183 final Map<String, String> parameters) { 184 ViewPostMetadata postMetadata = new ViewPostMetadata(view.getId()); 185 186 ViewLifecycle.encapsulateLifecycle(view, model, postMetadata, null, request, new ViewLifecycleBuild(parameters, 187 null)); 188 189 // Validation of the page's beans 190 if (CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsBoolean( 191 UifConstants.VALIDATE_VIEWS_ONBUILD)) { 192 ValidationController validator = new ValidationController(true, true, true, true, false); 193 Log tempLogger = LogFactory.getLog(ViewLifecycle.class); 194 validator.validate(view, tempLogger, false); 195 } 196 197 return postMetadata; 198 } 199 200 /** 201 * Performs a lifecycle process to rebuild the component given by the update id. 202 * 203 * @param view view instance the component belongs to 204 * @param model object containing the full view data 205 * @param request The active servlet request. 206 * @param viewPostMetadata post metadata for the view 207 * @param componentId id of the component within the view, used to pull the current component from the view 208 * @return component instance the lifecycle has been run on 209 */ 210 public static Component performComponentLifecycle(View view, Object model, HttpServletRequest request, 211 ViewPostMetadata viewPostMetadata, String componentId) { 212 if (viewPostMetadata == null) { 213 UifFormBase form = (UifFormBase) model; 214 215 throw new RuntimeException( 216 "View post metadata is null which cannot occur for refresh. Form id: " + form.getFormKey() 217 + ", requested form id: " + form.getRequestedFormKey()); 218 } 219 220 // set the refresh id to the component id 221 refreshComponentId = componentId; 222 223 ComponentPostMetadata componentPostMetadata = viewPostMetadata.getComponentPostMetadata(componentId); 224 boolean componentPostMetadataInitialized = false; 225 if (componentPostMetadata == null) { 226 componentPostMetadata = viewPostMetadata.initializeComponentPostMetadata(componentId); 227 componentPostMetadataInitialized = true; 228 } 229 230 String rootPath = componentPostMetadata.getRootObjectPath(); 231 232 if (rootPath != null && !rootPath.equals(componentPostMetadata.getPath())) { 233 ComponentPostMetadata rootComponentPostMetadata = viewPostMetadata.getComponentPostMetadataForPath( 234 rootPath); 235 236 // If the root object is a detached component, reinitialize it to refresh sub components correctly 237 if (rootComponentPostMetadata != null && rootComponentPostMetadata.isDetachedComponent()) { 238 setupStandaloneComponentForRefresh(view, rootComponentPostMetadata.getId(), rootComponentPostMetadata); 239 } 240 } 241 242 // If this component never had post metadata before or it is detached, then it is considered 'standalone' 243 if (componentPostMetadataInitialized || componentPostMetadata.isDetachedComponent()) { 244 setupStandaloneComponentForRefresh(view, componentId, componentPostMetadata); 245 } 246 247 Map<String, List<String>> refreshPathMappings = componentPostMetadata.getRefreshPathMappings(); 248 249 encapsulateLifecycle(view, model, viewPostMetadata, componentPostMetadata, request, new ViewLifecycleBuild(null, 250 refreshPathMappings)); 251 252 return ObjectPropertyUtils.getPropertyValue(view, componentPostMetadata.getPath()); 253 } 254 255 /** 256 * Before running the lifecycle on a component that is not attached to a view, we need to retrieve the component, 257 * add it to the dialogs list, and setup its refresh paths. 258 * 259 * @param view view instance the component should be attached to 260 * @param componentId id for the component the lifecycle should be run on 261 * @param componentPostMetadata post metadata instance for the component 262 * @return instance of component post metadata configured for the component 263 */ 264 protected static void setupStandaloneComponentForRefresh(View view, String componentId, 265 ComponentPostMetadata componentPostMetadata) { 266 Component refreshComponent = (Component) KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean( 267 componentId); 268 269 if ((refreshComponent == null) || !(refreshComponent instanceof Group)) { 270 throw new RuntimeException("Refresh component was null or not a group instance"); 271 } 272 273 List<Group> dialogs = new ArrayList<Group>(); 274 if ((view.getDialogs() != null) && !view.getDialogs().isEmpty()) { 275 dialogs.addAll(view.getDialogs()); 276 } 277 278 refreshComponent.addDataAttribute(UifConstants.DataAttributes.DETACHED, "true"); 279 dialogs.add((Group) refreshComponent); 280 view.setDialogs(dialogs); 281 282 String refreshPath = UifPropertyPaths.DIALOGS + "[" + (view.getDialogs().size() - 1) + "]"; 283 componentPostMetadata.setPath(refreshPath); 284 285 List<String> refreshPaths = new ArrayList<String>(); 286 refreshPaths.add(refreshPath); 287 288 Map<String, List<String>> refreshPathMappings = new HashMap<String, List<String>>(); 289 290 refreshPathMappings.put(UifConstants.ViewPhases.INITIALIZE, refreshPaths); 291 refreshPathMappings.put(UifConstants.ViewPhases.APPLY_MODEL, refreshPaths); 292 refreshPathMappings.put(UifConstants.ViewPhases.FINALIZE, refreshPaths); 293 294 componentPostMetadata.setRefreshPathMappings(refreshPathMappings); 295 296 componentPostMetadata.setDetachedComponent(true); 297 } 298 299 /** 300 * Indicates if the component the phase is being run on is a component being refreshed (if this is a full 301 * lifecycle this method will always return false). 302 * 303 * @return boolean true if the component is being refreshed, false if not 304 */ 305 public static boolean isRefreshComponent(String viewPhase, String viewPath) { 306 if (!ViewLifecycle.isRefreshLifecycle()) { 307 return false; 308 } 309 310 return StringUtils.equals(getRefreshComponentPhasePath(viewPhase), viewPath); 311 } 312 313 /** 314 * When a refresh lifecycle is being processed, returns the phase path (path at the current phase) for 315 * the component being refreshed. 316 * 317 * @return path for refresh component at this phase, or null if this is not a refresh lifecycle 318 */ 319 public static String getRefreshComponentPhasePath(String viewPhase) { 320 if (!ViewLifecycle.isRefreshLifecycle()) { 321 return null; 322 } 323 324 ComponentPostMetadata refreshComponentPostMetadata = ViewLifecycle.getRefreshComponentPostMetadata(); 325 if (refreshComponentPostMetadata == null) { 326 return null; 327 } 328 329 String refreshPath = refreshComponentPostMetadata.getPath(); 330 if (refreshComponentPostMetadata.getPhasePathMapping() != null) { 331 Map<String, String> phasePathMapping = refreshComponentPostMetadata.getPhasePathMapping(); 332 333 // find the path for the element at this phase (if it was the same as the final path it will not be 334 // in the phase path mapping 335 if (phasePathMapping.containsKey(viewPhase)) { 336 refreshPath = phasePathMapping.get(viewPhase); 337 } 338 } 339 340 return refreshPath; 341 } 342 343 /** 344 * Invoked when an event occurs to invoke registered listeners. 345 * 346 * @param event event that has occurred 347 * @param view view instance the lifecycle is being executed for 348 * @param model object containing the model data 349 * @param eventElement component instance the event occurred on/for 350 * @see LifecycleEvent 351 */ 352 public void invokeEventListeners(LifecycleEvent event, View view, Object model, LifecycleElement eventElement) { 353 synchronized (eventRegistrations) { 354 for (EventRegistration registration : eventRegistrations) { 355 if (registration.getEvent().equals(event) && (registration.getEventComponent() == eventElement)) { 356 registration.getEventListener().processEvent(event, view, model, eventElement); 357 } 358 } 359 } 360 } 361 362 /** 363 * Registers the given component as a listener for the lifecycle complete event for the given 364 * event component. 365 * 366 * <p> 367 * The {@link LifecycleEvent#LIFECYCLE_COMPLETE} is thrown immediately after the finalize phase 368 * has been completed for a component. This can be useful if a component needs to set state 369 * after the lifecycle has been completed on another component (for example, it might depend on 370 * properties of that component that are set during the finalize phase of that component) 371 * </p> 372 * 373 * @param eventComponent component the event will occur for 374 * @param listenerComponent component to invoke when the event is thrown 375 * @see LifecycleEvent 376 * @see LifecycleEventListener 377 */ 378 public void registerLifecycleCompleteListener(Component eventComponent, LifecycleEventListener listenerComponent) { 379 EventRegistration eventRegistration = new EventRegistration(LifecycleEvent.LIFECYCLE_COMPLETE, eventComponent, 380 listenerComponent); 381 382 synchronized (eventRegistrations) { 383 eventRegistrations.add(eventRegistration); 384 } 385 } 386 387 /** 388 * Determines whether or not the lifecycle is operating in strict mode. 389 * 390 * <p> 391 * {@link Component#getViewStatus()} is checked at the beginning and end of each lifecycle 392 * phase. When operating in strict mode, when a component is in the wrong status for the current 393 * phase {@link IllegalStateException} will be thrown. When not in strict mode, warning messages 394 * are logged on the console. 395 * </p> 396 * 397 * <p> 398 * This value is controlled by the configuration parameter 399 * "krad.uif.lifecycle.strict". In Rice 2.4, the view lifecycle is *not* strict by 400 * default. 401 * </p> 402 * 403 * @return true for strict operation, false to treat lifecycle violations as warnings 404 */ 405 public static boolean isStrict() { 406 if (strict == null) { 407 strict = ConfigContext.getCurrentContextConfig().getBooleanProperty( 408 KRADConstants.ConfigParameters.KRAD_STRICT_LIFECYCLE, false); 409 } 410 411 return strict; 412 } 413 414 /** 415 * Determines whether or not to enable rendering within the lifecycle. 416 * 417 * <p> 418 * This value is controlled by the configuration parameter 419 * "krad.uif.lifecycle.render". 420 * </p> 421 * 422 * @return true for rendering within the lifecycle, false if all rendering should be deferred 423 * for Spring view processing 424 */ 425 public static boolean isRenderInLifecycle() { 426 if (renderInLifecycle == null) { 427 renderInLifecycle = ConfigContext.getCurrentContextConfig().getBooleanProperty( 428 KRADConstants.ConfigParameters.KRAD_RENDER_IN_LIFECYCLE, false); 429 } 430 431 return renderInLifecycle; 432 } 433 434 /** 435 * Determines whether or not to processing view lifecycle phases asynchronously. 436 * 437 * <p> 438 * This value is controlled by the configuration parameter 439 * "krad.uif.lifecycle.asynchronous". 440 * </p> 441 * 442 * @return true if view lifecycle phases should be performed asynchronously, false for 443 * synchronous operation 444 */ 445 public static boolean isAsynchronousLifecycle() { 446 Config config = ConfigContext.getCurrentContextConfig(); 447 return config != null && config.getBooleanProperty( 448 KRADConstants.ConfigParameters.KRAD_VIEW_LIFECYCLE_ASYNCHRONOUS, false); 449 } 450 451 /** 452 * Determines whether or not to log trace details for troubleshooting lifecycle phases. 453 * 454 * <p> 455 * View lifecycle tracing is very verbose. This feature should only be enabled for 456 * troubleshooting purposes. 457 * </p> 458 * 459 * <p> 460 * This value is controlled by the configuration parameter "krad.uif.lifecycle.trace". 461 * </p> 462 * 463 * @return true if view lifecycle phase processing information should be logged 464 */ 465 public static boolean isTrace() { 466 if (trace == null) { 467 Config config = ConfigContext.getCurrentContextConfig(); 468 if (config == null) { 469 return false; 470 } 471 472 trace = config.getBooleanProperty(KRADConstants.ConfigParameters.KRAD_VIEW_LIFECYCLE_TRACE, false); 473 } 474 475 return trace; 476 } 477 478 /** 479 * Report an illegal state in the view lifecycle. 480 * 481 * <p> 482 * When {@link #isStrict()} returns true, {@link IllegalStateException} will be thrown. 483 * Otherwise, a warning will be logged. 484 * </p> 485 * 486 * @param message The message describing the illegal state. 487 * @throws IllegalStateException If strict mode is enabled. 488 */ 489 public static void reportIllegalState(String message) { 490 reportIllegalState(message, null); 491 } 492 493 /** 494 * Report an illegal state in the view lifecycle. 495 * 496 * <p> 497 * When {@link #isStrict()} returns true, {@link IllegalStateException} will be thrown. 498 * Otherwise, a warning will be logged. 499 * </p> 500 * 501 * @param message The message describing the illegal state. 502 * @param cause The (potential) cause of the illegal state. 503 * @throws IllegalStateException If strict mode is enabled. 504 */ 505 public static void reportIllegalState(String message, Throwable cause) { 506 IllegalStateException illegalState = new IllegalStateException(message + "\nPhase: " + ViewLifecycle.getPhase(), 507 cause); 508 509 if (ViewLifecycle.isStrict()) { 510 throw illegalState; 511 } else { 512 if (LOG.isTraceEnabled()) { 513 LOG.trace(illegalState.getMessage(), illegalState); 514 } 515 } 516 } 517 518 /** 519 * Gets the helper active within a lifecycle on the current thread. 520 * 521 * @return helper active on the current thread 522 */ 523 public static ViewHelperService getHelper() { 524 ViewLifecycle active = getActiveLifecycle(); 525 526 if (active.helper == null) { 527 throw new IllegalStateException("Context view helper is not available"); 528 } 529 530 return active.helper; 531 } 532 533 /** 534 * Gets the view active within a lifecycle on the current thread. 535 * 536 * @return view active on the current thread 537 */ 538 public static View getView() { 539 ViewLifecycle active = getActiveLifecycle(); 540 541 if (active.view == null) { 542 throw new IllegalStateException("Context view is not available"); 543 } 544 545 return active.view; 546 } 547 548 /** 549 * Return an instance of {@link org.kuali.rice.krad.uif.view.ExpressionEvaluator} that can be used for evaluating 550 * expressions contained on the view 551 * 552 * <p> 553 * A ExpressionEvaluator must be initialized with a model for expression evaluation. One instance is 554 * constructed for the view lifecycle and made available to all components/helpers through this method 555 * </p> 556 * 557 * @return instance of ExpressionEvaluator 558 */ 559 public static ExpressionEvaluator getExpressionEvaluator() { 560 ViewLifecycleProcessor processor = PROCESSOR.get(); 561 562 if (processor == null) { 563 ExpressionEvaluatorFactory expressionEvaluatorFactory = 564 KRADServiceLocatorWeb.getExpressionEvaluatorFactory(); 565 566 if (expressionEvaluatorFactory == null) { 567 return new DefaultExpressionEvaluator(); 568 } else { 569 return expressionEvaluatorFactory.createExpressionEvaluator(); 570 } 571 } 572 573 return processor.getExpressionEvaluator(); 574 } 575 576 /** 577 * Gets the model related to the view active within this context. 578 * 579 * @return model related to the view active within this context 580 */ 581 public static Object getModel() { 582 ViewLifecycle active = getActiveLifecycle(); 583 584 if (active.model == null) { 585 throw new IllegalStateException("Model is not available"); 586 } 587 588 return active.model; 589 } 590 591 /** 592 * Gets the set of visited IDs for use during the apply model phase. 593 * 594 * @return The set of visited IDs for use during the apply model phase. 595 */ 596 public static Set<String> getVisitedIds() { 597 ViewLifecycle active = getActiveLifecycle(); 598 599 if (active.visitedIds == null) { 600 synchronized (active) { 601 if (active.visitedIds == null) { 602 active.visitedIds = Collections.synchronizedSet(new HashSet<String>()); 603 } 604 } 605 } 606 607 return active.visitedIds; 608 } 609 610 /** 611 * Returns the view post metadata instance associated with the view and lifecycle. 612 * 613 * @return view post metadata instance 614 */ 615 public static ViewPostMetadata getViewPostMetadata() { 616 ViewLifecycle active = getActiveLifecycle(); 617 618 if (active.model == null) { 619 throw new IllegalStateException("Post Metadata is not available"); 620 } 621 622 return active.viewPostMetadata; 623 } 624 625 /** 626 * When the lifecycle is processing a component refresh, returns a 627 * {@link org.kuali.rice.krad.uif.lifecycle.ComponentPostMetadata} instance for the component being 628 * refresh. 629 * 630 * @return post metadata for the component being refreshed 631 */ 632 public static ComponentPostMetadata getRefreshComponentPostMetadata() { 633 ViewLifecycle active = getActiveLifecycle(); 634 635 if (active == null) { 636 throw new IllegalStateException("No lifecycle is active"); 637 } 638 639 return active.refreshComponentPostMetadata; 640 } 641 642 /** 643 * Indicates whether the lifecycle is processing a component refresh. 644 * 645 * @return boolean true if the lifecycle is refreshing a component, false for the full lifecycle 646 */ 647 public static boolean isRefreshLifecycle() { 648 ViewLifecycle active = getActiveLifecycle(); 649 650 if (active == null) { 651 throw new IllegalStateException("No lifecycle is active"); 652 } 653 654 return (active.refreshComponentPostMetadata != null); 655 } 656 657 /** 658 * Gets the servlet request for this lifecycle. 659 * 660 * @return servlet request for this lifecycle 661 */ 662 public static HttpServletRequest getRequest() { 663 ViewLifecycle active = getActiveLifecycle(); 664 665 if (active.model == null) { 666 throw new IllegalStateException("Request is not available"); 667 } 668 669 return active.request; 670 } 671 672 /** 673 * Gets the current phase of the active lifecycle, or null if no phase is currently active. 674 * 675 * @return current phase of the active lifecycle 676 */ 677 public static ViewLifecyclePhase getPhase() { 678 ViewLifecycleProcessor processor = PROCESSOR.get(); 679 try { 680 return processor == null ? null : processor.getActivePhase(); 681 } catch (IllegalStateException e) { 682 if (LOG.isDebugEnabled()) { 683 LOG.debug("No lifecycle phase is active on the current processor", e); 684 } 685 return null; 686 } 687 } 688 689 /** 690 * Gets the rendering context for this lifecycle. 691 * 692 * @return rendering context for this lifecycle 693 */ 694 public static LifecycleRenderingContext getRenderingContext() { 695 ViewLifecycleProcessor processor = PROCESSOR.get(); 696 return processor == null ? null : processor.getRenderingContext(); 697 } 698 699 /** 700 * Gets the lifecycle processor active on the current thread. 701 * 702 * @return lifecycle processor active on the current thread 703 */ 704 public static ViewLifecycleProcessor getProcessor() { 705 ViewLifecycleProcessor processor = PROCESSOR.get(); 706 707 if (processor == null) { 708 throw new IllegalStateException("No view lifecycle is active on this thread"); 709 } 710 711 return processor; 712 } 713 714 /** 715 * Note a processor as active on the current thread. 716 * 717 * <p> 718 * This method is intended only for use by {@link AsynchronousViewLifecycleProcessor} in setting 719 * the context for worker threads. Use 720 * {@link #encapsulateLifecycle(View, Object, HttpServletRequest, Runnable)} 721 * to populate an appropriate processor for for web request and other transaction threads. 722 * </p> 723 * 724 * @param processor The processor to activate on the current thread. 725 */ 726 static void setProcessor(ViewLifecycleProcessor processor) { 727 ViewLifecycleProcessor active = PROCESSOR.get(); 728 729 if (active != null && processor != null) { 730 throw new IllegalStateException("Another lifecycle processor is already active on this thread"); 731 } 732 733 if (processor == null) { 734 PROCESSOR.remove(); 735 } else { 736 PROCESSOR.set(processor); 737 } 738 } 739 740 /** 741 * Gets the view context active on the current thread. 742 * 743 * @return view context active on the current thread 744 */ 745 public static ViewLifecycle getActiveLifecycle() { 746 return getProcessor().getLifecycle(); 747 } 748 749 /** 750 * Determine if a lifecycle processor is active on the current thread. 751 * 752 * @return True if a lifecycle processor is active on the current thread. 753 */ 754 public static boolean isActive() { 755 return PROCESSOR.get() != null; 756 } 757 758 /** 759 * Id of the component to refresh. 760 */ 761 public static String getRefreshComponentId() { 762 return refreshComponentId; 763 } 764 765 /** 766 * @see ViewLifecycle#getRefreshComponentId() 767 */ 768 public static void setRefreshComponentId(String id) { 769 refreshComponentId = id; 770 } 771 772 /** 773 * Enumerates potential lifecycle events. 774 */ 775 public static enum LifecycleEvent { 776 777 // Indicates that the finalize phase processing has been completed on the component. 778 LIFECYCLE_COMPLETE 779 } 780 781 /** 782 * Registration of an event. 783 */ 784 protected class EventRegistration implements Serializable { 785 786 private static final long serialVersionUID = -5077429381388641016L; 787 788 // the event to listen for. 789 private LifecycleEvent event; 790 791 // the component to notify when the event passes. 792 private Component eventComponent; 793 794 // the event listener. 795 private LifecycleEventListener eventListener; 796 797 /** 798 * Private constructor. 799 * 800 * @param event The event to listen for. 801 * @param eventComponent The component to notify. 802 * @param eventListener The event listener. 803 */ 804 private EventRegistration(LifecycleEvent event, Component eventComponent, 805 LifecycleEventListener eventListener) { 806 this.event = event; 807 this.eventComponent = eventComponent; 808 this.eventListener = eventListener; 809 } 810 811 /** 812 * Event the registration is for. 813 * 814 * @return The event this registration is for. 815 * 816 * @see LifecycleEvent 817 */ 818 public LifecycleEvent getEvent() { 819 return event; 820 } 821 822 /** 823 * Component instance the event should occur for/on. 824 * 825 * @return Component instance for event 826 */ 827 public Component getEventComponent() { 828 return eventComponent; 829 } 830 831 /** 832 * Listener class that should be invoked when the event occurs. 833 * 834 * @return LifecycleEventListener instance 835 */ 836 public LifecycleEventListener getEventListener() { 837 return eventListener; 838 } 839 840 /** 841 * Sets the registered Event. 842 * 843 * @param event The registered event. 844 * @see EventRegistration#getEvent() 845 */ 846 public void setEvent(LifecycleEvent event) { 847 this.event = event; 848 } 849 850 /** 851 * Sets the component. 852 * 853 * @param eventComponent The component. 854 * @see EventRegistration#getEventComponent() 855 */ 856 public void setEventComponent(Component eventComponent) { 857 this.eventComponent = eventComponent; 858 } 859 860 /** 861 * Sets the event listener. 862 * 863 * @param eventListener The event listener. 864 * @see EventRegistration#getEventListener() 865 */ 866 public void setEventListener(LifecycleEventListener eventListener) { 867 this.eventListener = eventListener; 868 } 869 } 870}