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 }