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