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