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         * &quot;krad.uif.lifecycle.strict&quot;. 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         * &quot;krad.uif.lifecycle.render&quot;.
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         * &quot;krad.uif.lifecycle.asynchronous&quot;.
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 &quot;krad.uif.lifecycle.trace&quot;.
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    }