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