View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.lifecycle;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.log4j.Logger;
22  import org.kuali.rice.core.api.CoreApiServiceLocator;
23  import org.kuali.rice.core.api.config.property.Config;
24  import org.kuali.rice.core.api.config.property.ConfigContext;
25  import org.kuali.rice.krad.datadictionary.validator.ValidationController;
26  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
27  import org.kuali.rice.krad.uif.UifConstants;
28  import org.kuali.rice.krad.uif.UifPropertyPaths;
29  import org.kuali.rice.krad.uif.component.Component;
30  import org.kuali.rice.krad.uif.container.Group;
31  import org.kuali.rice.krad.uif.freemarker.LifecycleRenderingContext;
32  import org.kuali.rice.krad.uif.service.ViewHelperService;
33  import org.kuali.rice.krad.uif.util.LifecycleElement;
34  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
35  import org.kuali.rice.krad.uif.view.DefaultExpressionEvaluator;
36  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
37  import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
38  import org.kuali.rice.krad.uif.view.View;
39  import org.kuali.rice.krad.util.KRADConstants;
40  import org.kuali.rice.krad.web.form.UifFormBase;
41  
42  import javax.servlet.http.HttpServletRequest;
43  import java.io.Serializable;
44  import java.util.ArrayList;
45  import java.util.Collections;
46  import java.util.HashMap;
47  import java.util.HashSet;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.Set;
51  
52  /**
53   * Lifecycle object created during the view processing to hold event registrations.
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   * @see LifecycleEventListener
57   */
58  public class ViewLifecycle implements Serializable {
59  
60      private static Logger LOG = Logger.getLogger(ViewLifecycle.class);
61      private static final long serialVersionUID = -4767600614111642241L;
62  
63      private static final ThreadLocal<ViewLifecycleProcessor> PROCESSOR = new ThreadLocal<ViewLifecycleProcessor>();
64  
65      private static Boolean strict;
66      private static Boolean renderInLifecycle;
67      private static Boolean trace;
68  
69      private final List<EventRegistration> eventRegistrations;
70      private final View view;
71  
72      private final ComponentPostMetadata refreshComponentPostMetadata;
73  
74      final ViewHelperService helper;
75  
76      final Object model;
77  
78      final HttpServletRequest request;
79      private ViewPostMetadata viewPostMetadata;
80  
81      private Set<String> visitedIds;
82  
83      private static String refreshComponentId;
84  
85      /**
86       * Private constructor, for spawning a lifecycle context.
87       *
88       * @param view The view to process with the lifecycle
89       * @param model The model to use in processing the lifecycle
90       * @param refreshComponentPostMetadata when a refresh lifecycle is requested, post metadata for the component
91       * that is being refreshed
92       * @param request The active servlet request
93       * @see #getActiveLifecycle() For access to a thread-local instance.
94       */
95      private ViewLifecycle(View view, Object model, ComponentPostMetadata refreshComponentPostMetadata,
96              HttpServletRequest request) {
97          this.view = view;
98          this.model = model;
99          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 }