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