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