1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.trinidad.context;
20
21 import java.awt.Color;
22
23 import java.io.IOException;
24
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.TimeZone;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35
36 import javax.faces.component.UIComponent;
37 import javax.faces.component.UIViewRoot;
38 import javax.faces.component.visit.VisitContext;
39 import javax.faces.component.visit.VisitHint;
40 import javax.faces.context.ExternalContext;
41 import javax.faces.context.FacesContext;
42 import javax.faces.event.PhaseId;
43
44 import org.apache.myfaces.trinidad.change.ChangeManager;
45 import org.apache.myfaces.trinidad.config.RegionManager;
46 import org.apache.myfaces.trinidad.event.WindowLifecycleListener;
47 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
48 import org.apache.myfaces.trinidad.util.ArrayMap;
49 import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
50 import org.apache.myfaces.trinidad.webapp.UploadedFileProcessor;
51
52
53 /**
54 * Context class for all per-request and per-webapp information
55 * required by Trinidad. A <code>RequestContext</code> object can be
56 * retrieved with the static {@link #getCurrentInstance} method.
57 * There is one and only one <code>RequestContext</code> object
58 * active in any one thread.
59 * <p>
60 * This class does not extend <code>FacesContext</code>; this is intentional,
61 * as extending <code>FacesContext</code> requires taking over the
62 * <code>FacesContextFactory</code>.
63 * <p>
64 */
65 // TODO Refactor this class after everything gets added to it.
66 // TODO There's some values in here that seem to affect only output (e.g.,
67 // right-to-left); that's not great, since ideally that detail would be
68 // buried in something more renderer-specific.
69 abstract public class RequestContext
70 {
71 /**
72 * Name of the EL implicit variable ("requestContext") that is used to
73 * expose this context object.
74 */
75 static public final String VARIABLE_NAME =
76 "requestContext";
77
78 // Omitted APIs:
79 //
80 // LocaleContext
81 // =============
82 // Locale getTranslationLocale
83 //
84 // DateFormatContext:
85 // =================
86 // int getTwoDigitYearStart
87 // boolean isLenient (very lame API, definitely gone)
88
89
90 /**
91 * Retrieves the RequestContext active for the current thread.
92 */
93 static public RequestContext getCurrentInstance()
94 {
95 return _CURRENT_CONTEXT.get();
96 }
97
98
99
100 /**
101 * Creates an RequestContext. RequestContext is abstract
102 * and may not be instantiated directly.
103 * @see RequestContextFactory
104 */
105 protected RequestContext()
106 {
107 }
108
109 //
110 // State related APIs
111 //
112
113 /**
114 * Returns a Map of objects at "pageFlow" scope.
115 */
116 public abstract Map<String, Object> getPageFlowScope();
117
118 /**
119 * @deprecated use getPageFlowScope()
120 */
121 @Deprecated
122 final public Map<String, Object> getProcessScope()
123 {
124 return getPageFlowScope();
125 }
126
127 /**
128 * Method to obtain a Map stored on the view.
129 * <p>This calls {@link #getViewMap(boolean)} with a true value for create.</p>
130 * <p>This is a pre-cursor implementation to the JSF 2.0 UIViewRoot.getViewMap() function.
131 * The implementation is taken from the initial UIViewRoot implementation in JSF but without
132 * the event notification support.</p>
133 */
134 public abstract Map<String, Object> getViewMap();
135
136 /**
137 * Method to obtain a Map stored on the view.
138 * <p>This is a pre-cursor implementation to the JSF 2.0 UIViewRoot.getViewMap() function.
139 * The implementation is taken from the initial UIViewRoot implementation in JSF but without
140 * the event notification support.</p>
141 *
142 * @param create if the map should be created if it already does not exist.
143 */
144 public abstract Map<String, Object> getViewMap(boolean create);
145
146 /**
147 * Returns a Map of objects associated with the current window if any. If there is no
148 * current window, the Session Map is returned.
149 * @return Map for storing objects associated with the current window.
150 * @see org.apache.myfaces.trinidad.context.Window#getWindowMap
151 */
152 public Map<String, Object> getWindowMap()
153 {
154 WindowManager wm = getWindowManager();
155
156 ExternalContext extContext = FacesContext.getCurrentInstance().getExternalContext();
157
158 Window window = wm.getCurrentWindow(extContext);
159
160 if (window != null)
161 {
162
163 return window.getWindowMap();
164 }
165 else
166 {
167 return extContext.getSessionMap();
168 }
169 }
170
171 //
172 // Dialog APIs
173 //
174
175 /**
176 * Returns from a dialog raised by a
177 * {@link org.apache.myfaces.trinidad.component.UIXCommand UIXCommand} component,
178 * or any component implementing
179 * {@link org.apache.myfaces.trinidad.component.DialogSource DialogSource},
180 * or any direct calls to {@link #launchDialog launchDialog()}.
181 * <p>
182 * @see org.apache.myfaces.trinidad.event.ReturnEvent
183 * @param returnValue the value to be delivered in the the ReturnEvent
184 */
185 // TODO Do we need an explicit "cancelled" concept, or
186 // is a null returnValue good enough?
187 public abstract void returnFromDialog(
188 Object returnValue,
189 Map<Object, Object> returnParameters);
190
191
192 /**
193 * Returns an DialogService, which exposes a number
194 * of APIs needed by component and framework developers. This
195 * will only rarely be needed by page authors.
196 */
197 public abstract DialogService getDialogService();
198
199 /**
200 * Launch a dialog, optionally raising it in a new dialog window.
201 * <p>
202 * The dialog will receive a new <code>pageFlowScope</code> map,
203 * which includes all the values of the currently available pageFlowScope
204 * as well as a set of properties passed to this function in
205 * the <code>dialogParameters</code> map. Changes to this newly
206 * created scope will not be visible once the dialog returns.
207 * <p>
208 * @param dialogRoot the UIViewRoot for the page being launched
209 * @param dialogParameters a set of parameters to populate the
210 * newly created pageFlowScope
211 * @param source the UIComponent that launched the dialog and
212 * should receive the {@link org.apache.myfaces.trinidad.event.ReturnEvent}
213 * when the dialog is complete.
214 * @param useWindow if true, use a popup window for the dialog
215 * if available on the current user agent device
216 * @param windowProperties the set of UI parameters used to
217 * modify the window, if one is used. The set of properties that\
218 * are supported will depend on the <code>RenderKit</code>, but
219 * common examples include "width", "height", "top" and "left".
220 */
221 public abstract void launchDialog(
222 UIViewRoot dialogRoot,
223 Map<String, Object> dialogParameters,
224 UIComponent source,
225 boolean useWindow,
226 Map<String, Object> windowProperties);
227
228 //
229 // General Apache Trinidad
230 //
231
232 /**
233 * Returns true if JSF is currently processing a postback request.
234 * <code>isPostback()</code> will return false if this is a request
235 * for an initial render of a page (that is, if Apply Request Values
236 * never executes), or if during the request the user is navigated
237 * to a different page (because of a navigation rule, etc). For
238 * example, during a request that results in a navigation to a new
239 * page, <code>isPostback()</code> will return true from Apply
240 * Request Values to Invoke Application, then false afterwards;
241 * whereas if there was no navigation, it would return true
242 * <p>
243 * The value of this method is undefined during (or before)
244 * the Restore View phase, but can be used in the afterPhase()
245 * method of a PhaseListener for Restore View.
246 * </p>
247 */
248 public abstract boolean isPostback();
249
250 /**
251 * Method to indicate if this current HTTP request is a
252 * partial page rendering request.
253 *
254 * @param context the <code>FacesContext</code> object for
255 * the request we are processing
256 * @return is this request a PPR request?
257 */
258 public abstract boolean isPartialRequest(FacesContext context);
259 /**
260 * Returns true if output should contain debugging information.
261 */
262 public abstract boolean isDebugOutput();
263
264 /**
265 * Returns true if client-side validation should be disabled.
266 */
267 public abstract boolean isClientValidationDisabled();
268
269 /**
270 * Returns the "output mode" - printable, etc.
271 */
272 public abstract String getOutputMode();
273
274 /**
275 * Returns the OutputMode enumeration
276 * @return OutputMode
277 */
278 public OutputMode getOutputModeEnum()
279 {
280 return OutputMode.fromId(getOutputMode());
281 }
282
283 /**
284 * Returns the name of the preferred skin family.
285 */
286 public abstract String getSkinFamily();
287
288 /**
289 * Returns the name of the skin version that goes with the skin-family.
290 */
291 public String getSkinVersion()
292 {
293 return null;
294 }
295
296 /**
297 * Determines whether the current View Root is an internal view
298 * @param context Faces context
299 * @return true if the current View Root is an internal view, false otherwise
300 */
301 public abstract boolean isInternalViewRequest(FacesContext context);
302
303 /**
304 * Enumeration representing OutputModes
305 */
306 public enum OutputMode
307 {
308 DEFAULT("default"), PORTLET("portlet"), PRINTABLE("printable"), EMAIL("email"),
309 ATTACHMENT("attachment"), WEB_CRAWLER("webcrawler");
310
311 private OutputMode(String id)
312 {
313 if (id == null) throw new NullPointerException();
314
315 _id = id;
316 }
317
318 /**
319 * @return the id of this OutputMode.
320 */
321 public String id()
322 {
323 return _id;
324 }
325
326 @Override
327 public String toString()
328 {
329 return _id;
330 }
331
332 /**
333 * Returns the OutputMode isntance of <code>null</code> if no id matches.
334 * @param id of OutputMode to return
335 * @return The OutputMode with the specified id
336 * @throws NullPointerException if <code>id</code> is null.
337 * @throws IllegalArgumentException if there is no enum with the specified name.
338 */
339 public static OutputMode fromId(String id)
340 {
341 if (id == null)
342 throw new NullPointerException();
343
344 OutputMode outputMode = ID_TO_OUTPUT_MODE.get(id);
345
346 if (outputMode == null)
347 throw new IllegalArgumentException();
348
349 return outputMode;
350 }
351
352 private static final Map<String, OutputMode> ID_TO_OUTPUT_MODE = new HashMap<String, OutputMode>();
353
354 static
355 {
356 OutputMode[] instances = OutputMode.class.getEnumConstants();
357
358 for (int i = 0; i < instances.length; i++)
359 {
360 ID_TO_OUTPUT_MODE.put(instances[i].toString(), instances[i]);
361 }
362 }
363
364 private final String _id;
365 }
366
367 public enum Accessibility
368 {
369 /**
370 * Output supports accessibility features
371 */
372 DEFAULT("default"),
373
374 /**
375 * Accessibility-specific constructs are stripped out to optimize output size
376 */
377 INACCESSIBLE("inaccessible"),
378
379 /**
380 * Accessibility-specific constructs are added to improve behavior under a screen reader
381 * (but may affect other users negatively)
382 */
383 SCREEN_READER("screenReader");
384
385 /**
386 * Creates an Accessibility constant.
387 * @param displayName a user-friendly display name for the constant.
388 */
389 Accessibility(String displayName)
390 {
391 _displayName = displayName;
392 }
393
394 @Override
395 public String toString()
396 {
397 return displayName();
398 }
399
400 /**
401 * Returns the display name for this enum constant.
402 */
403 public String displayName()
404 {
405 return _displayName;
406 }
407
408 /**
409 * Performs a reverse lookup of an Accessibilty constant based on
410 * its display name.
411 *
412 * @param displayName the display name of the Accessibility constant to return.
413 * @return the non-null Accessibility constant associated with the display name.
414 * @throws IllegalArgumentException if displayName does not correspond
415 * to some Accessibility constant.
416 * @throws NullPointerException if displayName is null.
417 */
418 public static Accessibility valueOfDisplayName(String displayName)
419 {
420 if (displayName == null)
421 {
422 throw new NullPointerException();
423 }
424
425 Accessibility accessibility = _displayNameMap.get(displayName);
426
427 if (accessibility == null)
428 {
429 String message = _LOG.getMessage("ILLEGAL_ENUM_VALUE",
430 new Object[] { Accessibility.class.getName(), displayName });
431 throw new IllegalArgumentException(message);
432 }
433
434 return accessibility;
435 }
436
437 private final String _displayName;
438
439 private static final Map<String, Accessibility> _displayNameMap;
440
441 static
442 {
443 Map<String, Accessibility> displayNameMap = new ArrayMap<String, Accessibility>(3);
444 for (Accessibility accessibility : Accessibility.values())
445 {
446 displayNameMap.put(accessibility.displayName(), accessibility);
447 }
448
449 _displayNameMap = Collections.unmodifiableMap(displayNameMap);
450 }
451 };
452
453
454 public enum ClientValidation
455 {
456 ALERT("alert"),
457 INLINE("inline"),
458 DISABLED("disabled");
459
460 ClientValidation(String name)
461 {
462 _name = name;
463 }
464
465 @Override
466 public String toString()
467 {
468 return _name;
469 }
470
471 private final String _name;
472 };
473
474 /**
475 * Returns the name of the current accessibility mode.
476 */
477 public abstract Accessibility getAccessibilityMode();
478
479 /**
480 * Returns the accessibility profile for the current request.
481 */
482 public abstract AccessibilityProfile getAccessibilityProfile();
483
484 /**
485 * Returns the name of the current client validation mode.
486 */
487 public abstract ClientValidation getClientValidation();
488
489 /**
490 * Returns the system wide setting to turn animation on/off.
491 */
492 public abstract boolean isAnimationEnabled();
493
494 //
495 // General localization
496 //
497
498 /**
499 * Returns true if the user should be shown output in right-to-left.
500 */
501 public abstract boolean isRightToLeft();
502
503 /**
504 * Returns the formatting locale. Converters without an explicit locale
505 * should use this to format values. If not set, converters should
506 * default to the value of FacesContext.getViewRoot().getLocale().
507 * This will, by default, simply return null.
508 */
509 public abstract Locale getFormattingLocale();
510
511 //
512 // Number formatting
513 //
514
515 /**
516 * Return the separator used for groups of numbers. If NUL (zero),
517 * use the default separator for the current language.
518 */
519 public abstract char getNumberGroupingSeparator();
520
521 /**
522 * Return the separator used as the decimal point. If NUL (zero),
523 * use the default separator for the current language.
524 */
525 public abstract char getDecimalSeparator();
526
527 /**
528 * Return the ISO 4217 currency code used by default for formatting
529 * currency fields when those fields do not specify an explicit
530 * currency field via their converter. If this returns null, the default
531 * code for the current locale will be used.
532 */
533 // TODO do we need to provide getCurrencySymbol() as well?
534 public abstract String getCurrencyCode();
535
536 //
537 // DateFormating API
538 //
539 /**
540 * Returns the year offset for parsing years with only two digits.
541 * If not set this is defaulted to <code>1950</code>
542 * This is used by @link{org.apache.myfaces.trinidad.faces.view.converter.DateTimeConverter}
543 * while converting strings to Date object.
544 */
545 public abstract int getTwoDigitYearStart();
546
547 //
548 // Help APIs
549 //
550
551 /**
552 * Return the URL to an Oracle Help for the Web servlet.
553 */
554 // TODO Add support for non-OHW help systems
555 public abstract String getOracleHelpServletUrl();
556
557 /**
558 * Returns a Map that will accept topic names as keys, and return
559 * an URL as a result.
560 */
561 public abstract Map<String, Object> getHelpTopic();
562
563 /**
564 * Returns a Map that will accept help system properties as keys, and return
565 * an URL as a result.
566 */
567 public abstract Map<String, Object> getHelpSystem();
568
569 //
570 // Date formatting
571 //
572
573 /**
574 * Returns the default TimeZone used for interpreting and formatting
575 * date values.
576 */
577 public abstract TimeZone getTimeZone();
578
579 /**
580 * Gets the ChangeManager for the current application.
581 */
582 public abstract ChangeManager getChangeManager();
583
584 /**
585 * Gets a per application concurrent map. There is no synchronization
586 * with ServletContext attributes.
587 */
588 public ConcurrentMap<String, Object> getApplicationScopedConcurrentMap()
589 {
590 ClassLoader cl = _getClassLoader();
591
592 ConcurrentMap<String, Object> classMap = _APPLICATION_MAPS.get(cl);
593
594 if (classMap == null)
595 {
596 ConcurrentMap<String, Object> newClassMap = new ConcurrentHashMap<String, Object>();
597 ConcurrentMap<String, Object> oldClassMap = _APPLICATION_MAPS.putIfAbsent(cl, newClassMap);
598
599 classMap = ((oldClassMap != null)? oldClassMap : newClassMap);
600
601 assert(classMap != null);
602 }
603
604 return classMap;
605 }
606
607 /**
608 * Gets the PageFlowScopeProvider for the current application.
609 */
610 public abstract PageFlowScopeProvider getPageFlowScopeProvider();
611
612 /**
613 * Gets the PageResolver for the current application.
614 */
615 public abstract PageResolver getPageResolver();
616
617 /**
618 * Gets the RegionManager for the current application.
619 */
620 public abstract RegionManager getRegionManager();
621
622 //
623 // Partial Page Rendering support
624 //
625 /**
626 * Add a component as a partial target. In response to a partial event, only
627 * components registered as partial targets are re-rendered. For
628 * a component to be successfully re-rendered when it is manually
629 * added with this API, it should have an explictly set "id". If
630 * not, partial re-rendering may or may not work depending on the
631 * component.
632 */
633 public abstract void addPartialTarget(UIComponent newTarget);
634
635 /**
636 * Add components relative to the given component as partial targets.
637 * <p>
638 * See {@link #addPartialTarget(UIComponent)} for more information.
639 * </p>
640 * @param from the component to use as a relative reference for any
641 * relative IDs in the list of targets
642 * @param targets array of targets relative to the from component that
643 * should be added as targets.
644 * @see ComponentUtils#findRelativeComponent(UIComponent, String)
645 */
646 public abstract void addPartialTargets(UIComponent from, String... targets);
647
648 /**
649 * Returns the set of partial targets related to a given UIComponent.
650 */
651 public abstract Set<UIComponent> getPartialTargets(UIComponent newTarget);
652
653 /**
654 * Adds a listener on a set of particular triggering components. If one of
655 * the named components gets updated in response to a partial event, then
656 * this listener component will be rerendered during the render phase (i.e.
657 * it will be added as a partialTarget). The list should consist of names
658 * suitable for use with the findComponent method on UIComponent.
659 */
660 public abstract void addPartialTriggerListeners(UIComponent listener,
661 String[] trigger);
662
663 /**
664 * Called when any component gets updated. Any partial target components
665 * listening on this component will be added to the partialTargets list in
666 * the render phase.
667 */
668 public abstract void partialUpdateNotify(UIComponent updated);
669
670 //
671 // Miscellaneous functionality
672 //
673
674 /**
675 * <p>Creates a VisitContext instance for use with
676 * {@link UIComponent#visitTree}.</p>
677 *
678 * @param context the FacesContext for the current request
679 * @param ids the client ids of the components to visit. If null,
680 * all components will be visited.
681 * @param hints the VisitHints to apply to the visit
682 * @param phaseId. ignored.
683 * @return a VisitContext instance that is initialized with the
684 * specified ids and hints.
685 * @deprecated use {@link VisitContext#createVisitContext(FacesContext, Collection<String>, Set<VisitHint>)} instead
686 */
687 public final VisitContext createVisitContext(
688 FacesContext context,
689 Collection<String> ids,
690 Set<VisitHint> hints,
691 PhaseId phaseId)
692 {
693 return VisitContext.createVisitContext(context, ids, hints);
694 }
695
696 public abstract UploadedFileProcessor getUploadedFileProcessor();
697
698 public abstract Long getUploadedFileMaxMemory();
699
700 public abstract Long getUploadedFileMaxDiskSpace();
701
702 public abstract String getUploadedFileTempDir();
703
704 /**
705 * Returns a Map that takes color palette names as keys, and returns
706 * the color palette as a result.
707 */
708 public abstract Map<String, List<Color>> getColorPalette();
709
710 /**
711 * Returns a Map that performs message formatting with a recursive Map
712 * structure. The first key must be the message formatting mask, and the
713 * second the first parameter into the message. (The formatter Map supports
714 * only a single parameter at this time.)
715 */
716 public abstract Map<Object, Map<Object,String>> getFormatter();
717
718 /**
719 * Returns the Agent information for the current context
720 */
721 public abstract Agent getAgent();
722
723 /**
724 * Saves the state of a UIComponent tree into an Object. The
725 * Object will be serializable, unless a UIComponent
726 * in this tree contains a non-serializable property. This
727 * method does not check that condition.
728 * @param component the component
729 * @return an Object that can be passed to restoreComponent()
730 * to reinstantiate the state
731 */
732 public abstract Object saveComponent(UIComponent component);
733
734 /**
735 * Restores the state of a component.
736 * @param state an Object created by a prior call to saveComponent().
737 * @return the component
738 */
739 public abstract UIComponent restoreComponent(Object state)
740 throws ClassNotFoundException,
741 InstantiationException,
742 IllegalAccessException;
743
744 /**
745 * <p>
746 * Returns the WindowManager for this request. A non-null WindowManager
747 * will always be returned.
748 * </p><p>
749 * The default implementation uses the first WindowManagerFactory specified
750 * implementation class in a file named
751 * <code>org.apache.myfaces.trinidad.context.WindowManagerFactory</code>
752 * in the <code>META-INF/services</code> directory and uses the WindowManagerFactory
753 * to create the WindowManager for this Session. If no WindowManagerFactory is
754 * found, a default WindowManager that never returns any Windows is used.
755 * </p>
756 * @return the WindowManager used for this Session.
757 */
758 public WindowManager getWindowManager()
759 {
760 // implement getWindowManager() in RequestContext for backwards compatibility
761
762 // check if we have cached it for the request
763 WindowManager windowManager = _windowManager;
764
765 // get instance using the WindowManagerFactory
766 if (windowManager == null)
767 {
768 FacesContext context = FacesContext.getCurrentInstance();
769
770 // just in case we're called before the real JSF lifecycle starts
771 if (context != null)
772 {
773 // check if we have cached it per session
774 ExternalContext extContext = context.getExternalContext();
775
776 // create a new instance using the WindowManagerFactory
777 ConcurrentMap<String, Object> concurrentAppMap = getApplicationScopedConcurrentMap();
778
779 WindowManagerFactory windowManagerFactory = (WindowManagerFactory)concurrentAppMap.get(
780 _WINDOW_MANAGER_FACTORY_CLASS_NAME);
781
782 if (windowManagerFactory == null)
783 {
784 // we haven't registered a WindowManagerFactory yet, so use the services api to see
785 // if a factory has been registered
786 List<WindowManagerFactory> windowManagerFactories =
787 ClassLoaderUtils.getServices(_WINDOW_MANAGER_FACTORY_CLASS_NAME);
788
789 if (windowManagerFactories.isEmpty())
790 {
791 // no factory registered so use the factory that returns dummy stub WindowManagers
792 windowManagerFactory = _STUB_WINDOW_MANAGER_FACTORY;
793 }
794 else
795 {
796 // only one WindowManager is allowed, use it
797 windowManagerFactory = windowManagerFactories.get(0);
798 }
799
800 // save the WindowManagerFactory to the application if it hasn't already been saved
801 // if it has been saved, use the previously registered WindowManagerFactory
802 WindowManagerFactory oldWindowManagerFactory = (WindowManagerFactory)
803 concurrentAppMap.putIfAbsent(_WINDOW_MANAGER_FACTORY_CLASS_NAME,
804 windowManagerFactory);
805
806 if (oldWindowManagerFactory != null)
807 windowManagerFactory = oldWindowManagerFactory;
808 } // create WindowManagerFactory
809
810 // get the WindowManager from the factory. The factory will create a new instance
811 // for this session if necessary
812 windowManager = windowManagerFactory.getWindowManager(extContext);
813
814 // remember for the next call on this thread
815 _windowManager = windowManager;
816 }
817 }
818
819 return windowManager;
820 }
821
822 /**
823 * Get the component context manager.
824 * @return The manager of component context changes to be used to suspend and resume
825 * component specific context changes.
826 */
827 public ComponentContextManager getComponentContextManager()
828 {
829 if (_componentContextManager == null)
830 {
831 _componentContextManager = new ComponentContextManagerImpl();
832 }
833
834 return _componentContextManager;
835 }
836
837 /**
838 * Releases the RequestContext object. This method must only
839 * be called by the code that created the RequestContext.
840 * @exception IllegalStateException if no RequestContext is attached
841 * to the thread, or the attached context is not this object
842 */
843 public void release()
844 {
845 if (_LOG.isFinest())
846 {
847 _LOG.finest("RequestContext released.",
848 new RuntimeException("This is not an error. This trace is for debugging."));
849 }
850
851 Object o = _CURRENT_CONTEXT.get();
852 if (o == null)
853 throw new IllegalStateException(
854 _addHelp("RequestContext was already released or " +
855 "had never been attached."));
856 if (o != this)
857 throw new IllegalStateException(_LOG.getMessage(
858 "RELEASE_DIFFERENT_REQUESTCONTEXT_THAN_CURRENT_ONE"));
859
860 _CURRENT_CONTEXT.remove();
861 }
862
863 /**
864 * Attaches a RequestContext to the current thread. This method
865 * should only be called by a RequestContext object itself.
866 * @exception IllegalStateException if an RequestContext is already
867 * attached to the thread
868 */
869 public void attach()
870 {
871 if (_LOG.isFinest())
872 {
873 _LOG.finest("RequestContext attached.",
874 new RuntimeException(_LOG.getMessage(
875 "DEBUGGING_TRACE_NOT_ERROR")));
876 }
877
878 Object o = _CURRENT_CONTEXT.get();
879 if (o != null)
880 {
881 throw new IllegalStateException(
882 _addHelp("Trying to attach RequestContext to a " +
883 "thread that already had one."));
884 }
885 _CURRENT_CONTEXT.set(this);
886 }
887
888 private static String _addHelp(String error)
889 {
890 if (!_LOG.isFinest())
891 {
892 error += " To enable stack traces of each RequestContext attach/release call," +
893 " enable Level.FINEST logging for the "+RequestContext.class;
894 }
895 return error;
896 }
897
898 //
899 // Pick a ClassLoader
900 //
901 private ClassLoader _getClassLoader()
902 {
903 return Thread.currentThread().getContextClassLoader();
904 }
905
906 /**
907 * Default WindowManagerFactory implementation that returns the StubWindowManager
908 */
909 private static final class StubWindowManagerFactory extends WindowManagerFactory
910 {
911 public WindowManager getWindowManager(ExternalContext extContext)
912 {
913 return _STUB_WINDOW_MANAGER;
914 }
915
916 private static final WindowManager _STUB_WINDOW_MANAGER = new StubWindowManager();
917 }
918
919 /**
920 * Default WindowManager implementation that returns no Windows
921 */
922 private static final class StubWindowManager extends WindowManager
923 {
924 @Override
925 public Window getCurrentWindow(ExternalContext extContext)
926 {
927 return null;
928 }
929
930 @Override
931 public Map<String, Window> getWindows(ExternalContext extContext)
932 {
933 return Collections.emptyMap();
934 }
935
936 @Override
937 public void addWindowLifecycleListener(
938 ExternalContext extContext,
939 WindowLifecycleListener windowListener)
940 {
941 // do nothing
942 }
943
944 @Override
945 public void removeWindowLifecycleListener(
946 ExternalContext extContext,
947 WindowLifecycleListener windowListener)
948 {
949 // do nothing
950 }
951
952 @Override
953 public void writeState(FacesContext context) throws IOException
954 {
955 // do nothing
956 }
957 }
958
959 /* singleton for WindowManagerFactory that returns WindowManagers that don't do anything */
960 private static final WindowManagerFactory _STUB_WINDOW_MANAGER_FACTORY =
961 new StubWindowManagerFactory();
962
963 private static final String _WINDOW_MANAGER_FACTORY_CLASS_NAME =
964 WindowManagerFactory.class.getName();
965
966 @SuppressWarnings({"CollectionWithoutInitialCapacity"})
967 private static final ConcurrentMap<ClassLoader, ConcurrentMap<String, Object>> _APPLICATION_MAPS =
968 new ConcurrentHashMap<ClassLoader, ConcurrentMap<String, Object>>();
969 static private final ThreadLocal<RequestContext> _CURRENT_CONTEXT =
970 new ThreadLocal<RequestContext>();
971 static private final TrinidadLogger _LOG =
972 TrinidadLogger.createTrinidadLogger(RequestContext.class);
973
974 private ComponentContextManager _componentContextManager;
975
976 // window manager for this request
977 private WindowManager _windowManager;
978 }