View Javadoc

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  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.TimeZone;
26  
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  
30  import javax.faces.component.UIComponent;
31  import javax.faces.component.UIViewRoot;
32  import javax.faces.context.FacesContext;
33  
34  import org.apache.myfaces.trinidad.change.ChangeManager;
35  import org.apache.myfaces.trinidad.config.RegionManager;
36  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
37  import org.apache.myfaces.trinidad.util.ComponentUtils;
38  import org.apache.myfaces.trinidad.webapp.UploadedFileProcessor;
39  
40  /**
41   * Context class for all per-request and per-webapp information
42   * required by Trinidad.  A <code>RequestContext</code> object can be
43   * retrieved with the static {@link #getCurrentInstance} method.
44   * There is one and only one <code>RequestContext</code> object
45   * active in any one thread.
46   * <p>
47   * This class does not extend <code>FacesContext</code>;  this is intentional,
48   * as extending <code>FacesContext</code> requires taking over the
49   * <code>FacesContextFactory</code>.
50   * <p>
51   */
52  // TODO Refactor this class after everything gets added to it.
53  // TODO There's some values in here that seem to affect only output (e.g.,
54  //  right-to-left); that's not great, since ideally that detail would be
55  //  buried in something more renderer-specific.
56  abstract public class RequestContext
57  {
58    /**
59     * Name of the EL implicit variable ("requestContext") that is used to
60     * expose this context object.
61     */
62    static public final String VARIABLE_NAME =
63      "requestContext";
64  
65    // Omitted APIs:
66    //
67    // LocaleContext
68    // =============
69    // Locale getTranslationLocale
70    //
71    // DateFormatContext:
72    // =================
73    // int getTwoDigitYearStart
74    // boolean isLenient (very lame API, definitely gone)
75  
76  
77    /**
78     * Retrieves the RequestContext active for the current thread.
79     */
80    static public RequestContext getCurrentInstance()
81    {
82      return _CURRENT_CONTEXT.get();
83    }
84  
85    
86  
87    /**
88     * Creates an RequestContext.  RequestContext is abstract
89     * and may not be instantiated directly.
90     * @see RequestContextFactory
91     */
92    protected RequestContext()
93    {
94    }
95  
96    //
97    // State related APIs
98    //
99  
100   /**
101    * Returns a Map of objects at "pageFlow" scope.
102    */
103   public abstract Map<String, Object> getPageFlowScope();
104 
105   /**
106    * @deprecated use getPageFlowScope()
107    */
108   @Deprecated
109   final public Map<String, Object> getProcessScope()
110   {
111     return getPageFlowScope();
112   }
113 
114 
115   //
116   // Dialog APIs
117   //
118 
119   /**
120    * Returns from a dialog raised by a
121    * {@link org.apache.myfaces.trinidad.component.UIXCommand UIXCommand} component,
122    * or any component implementing
123    * {@link org.apache.myfaces.trinidad.component.DialogSource DialogSource},
124    * or any direct calls to {@link #launchDialog launchDialog()}.
125    * <p>
126    * @see org.apache.myfaces.trinidad.event.ReturnEvent
127    * @param returnValue the value to be delivered in the the ReturnEvent
128    */
129   // TODO Do we need an explicit "cancelled" concept, or
130   // is a null returnValue good enough?
131   public abstract void returnFromDialog(
132     Object returnValue,
133     Map<Object, Object> returnParameters);
134 
135 
136   /**
137    * Returns an DialogService, which exposes a number
138    * of APIs needed by component and framework developers.  This
139    * will only rarely be needed by page authors.
140    */
141   public abstract DialogService getDialogService();
142 
143   /**
144    * Launch a dialog, optionally raising it in a new dialog window.
145    * <p>
146    * The dialog will receive a new <code>pageFlowScope</code> map,
147    * which includes all the values of the currently available pageFlowScope
148    * as well as a set of properties passed to this function in
149    * the <code>dialogParameters</code> map.  Changes to this newly
150    * created scope will not be visible once the dialog returns.
151    * <p>
152    * @param dialogRoot the UIViewRoot for the page being launched
153    * @param dialogParameters a set of parameters to populate the
154    *   newly created pageFlowScope
155    * @param source the UIComponent that launched the dialog and
156    *   should receive the {@link org.apache.myfaces.trinidad.event.ReturnEvent}
157    *   when the dialog is complete.
158    * @param useWindow if true, use a popup window for the dialog
159    *    if available on the current user agent device
160    * @param windowProperties the set of UI parameters used to
161    *   modify the window, if one is used.  The set of properties that\
162    *   are supported will depend on the <code>RenderKit</code>, but
163    *   common examples include "width", "height", "top" and "left".
164    */
165   public abstract void launchDialog(
166     UIViewRoot  dialogRoot,
167     Map<String, Object> dialogParameters,
168     UIComponent source,
169     boolean     useWindow,
170     Map<String, Object> windowProperties);
171 
172   //
173   // General Apache Trinidad
174   //
175 
176   /**
177    * Returns true if JSF is currently processing a postback request.
178    * <code>isPostback()</code> will return false if this is a request
179    * for an initial render of a page (that is, if Apply Request Values
180    * never executes), or if during the request the user is navigated
181    * to a different page (because of a navigation rule, etc).  For
182    * example, during a request that results in a navigation to a new
183    * page, <code>isPostback()</code> will return true from Apply
184    * Request Values to Invoke Application, then false afterwards;
185    * whereas if there was no navigation, it would return true
186    * <p>
187    * The value of this method is undefined during (or before)
188    * the Restore View phase, but can be used in the afterPhase()
189    * method of a PhaseListener for Restore View.
190    * </p>
191    */
192   public abstract boolean isPostback();
193   
194   /**
195    * Method to indicate if this current HTTP request is a
196    * partial page rendering request.
197    * 
198    * @param context the <code>FacesContext</code> object for
199    * the request we are processing
200    * @return is this request a PPR request?
201    */
202   public abstract boolean isPartialRequest(FacesContext context);
203   /**
204    * Returns true if output should contain debugging information.
205    */
206   public abstract boolean isDebugOutput();
207 
208   /**
209    * Returns true if client-side validation should be disabled.
210    */
211   public abstract boolean isClientValidationDisabled();
212 
213   /**
214    * Returns the "output mode" - printable, etc.
215    */
216   public abstract String getOutputMode();
217 
218 
219   /**
220    * Returns the name of the preferred skin family.
221    */
222   public abstract String getSkinFamily();
223 
224   public enum Accessibility
225   {
226     /**
227      * Output supports accessibility features
228      */
229     DEFAULT("default"),
230     
231     /**
232      * Accessibility-specific constructs are stripped out to optimize output size
233      */
234     INACCESSIBLE("inaccessible"),
235     
236     /**
237      * Accessibility-specific constructs are added to improve behavior under a screen reader 
238      * (but may affect other users negatively)
239      */
240     SCREEN_READER("screenReader");
241     
242     Accessibility(String name)
243     {
244       _name = name;
245     }
246 
247     @Override
248     public String toString()
249     {
250       return _name;
251     }
252 
253     private final String _name;
254   };
255 
256 
257   public enum ClientValidation
258   {
259     ALERT("alert"),
260     INLINE("inline"),
261     DISABLED("disabled");
262     
263     ClientValidation(String name)
264     {
265       _name = name;
266     }
267 
268     @Override
269     public String toString()
270     {
271       return _name;
272     }
273 
274     private final String _name;
275   };
276 
277   /**
278    * Returns the name of the current accessibility mode.
279    */
280   public abstract Accessibility getAccessibilityMode();
281 
282   /**
283    * Returns the accessibility profile for the current request.
284    */
285   public abstract AccessibilityProfile getAccessibilityProfile();
286 
287   /**
288    * Returns the name of the current client validation mode.
289    */
290   public abstract ClientValidation getClientValidation();
291 
292   /**
293    * Returns the system wide setting to turn animation on/off.
294    */
295   public abstract boolean isAnimationEnabled();
296 
297   //
298   //  General localization
299   //
300 
301   /**
302    * Returns true if the user should be shown output in right-to-left.
303    */
304   public abstract boolean isRightToLeft();
305 
306   /**
307    * Returns the formatting locale.  Converters without an explicit locale
308    * should use this to format values.  If not set, converters should
309    * default to the value of FacesContext.getViewRoot().getLocale().
310    * This will, by default, simply return null.
311    */
312   public abstract Locale getFormattingLocale();
313 
314   //
315   //  Number formatting
316   //
317 
318   /**
319    * Return the separator used for groups of numbers.  If NUL (zero),
320    * use the default separator for the current language.
321    */
322   public abstract char getNumberGroupingSeparator();
323 
324   /**
325    * Return the separator used as the decimal point.  If NUL (zero),
326    * use the default separator for the current language.
327    */
328   public abstract char getDecimalSeparator();
329 
330   /**
331    * Return the ISO 4217 currency code used by default for formatting
332    * currency fields when those fields do not specify an explicit
333    * currency field via their converter.  If this returns null, the default
334    * code for the current locale will be used.
335    */
336   // TODO do we need to provide getCurrencySymbol() as well?
337   public abstract String getCurrencyCode();
338 
339   //
340   // DateFormating API
341   //
342   /**
343    * Returns the year offset for parsing years with only two digits.
344    * If not set this is defaulted to <code>1950</code>
345    * This is used by @link{org.apache.myfaces.trinidad.faces.view.converter.DateTimeConverter}
346    * while converting strings to Date object.
347    */
348   public abstract int getTwoDigitYearStart();
349 
350   //
351   // Help APIs
352   //
353 
354   /**
355    * Return the URL to an Oracle Help for the Web servlet.
356    */
357   // TODO Add support for non-OHW help systems
358   public abstract String getOracleHelpServletUrl();
359 
360   /**
361    * Returns a Map that will accept topic names as keys, and return
362    * an URL as a result.
363    */
364   public abstract Map<String, Object> getHelpTopic();
365 
366   /**
367    * Returns a Map that will accept help system properties as keys, and return
368    * an URL as a result.
369    */
370   public abstract Map<String, Object> getHelpSystem();
371 
372   //
373   // Date formatting
374   //
375 
376   /**
377    * Returns the default TimeZone used for interpreting and formatting
378    * date values.
379    */
380   public abstract TimeZone getTimeZone();
381 
382   /**
383    * Gets the ChangeManager for the current application.
384    */
385   public abstract ChangeManager getChangeManager();
386 
387   /**
388    * Gets a per application concurrent map. There is no synchronization
389    * with ServletContext attributes.
390    */
391    public ConcurrentMap<String, Object> getApplicationScopedConcurrentMap()
392    {
393      ClassLoader cl = _getClassLoader();
394 
395      ConcurrentMap<String, Object> classMap = _APPLICATION_MAPS.get(cl);
396 
397      if (classMap == null)
398      {
399        ConcurrentMap<String, Object> newClassMap = new ConcurrentHashMap<String, Object>();
400        ConcurrentMap<String, Object> oldClassMap = _APPLICATION_MAPS.putIfAbsent(cl, newClassMap);
401 
402        classMap = ((oldClassMap != null)? oldClassMap : newClassMap);
403 
404        assert(classMap != null);
405      }
406 
407      return classMap;
408    }
409 
410   /**
411     * Gets the PageFlowScopeProvider for the current application.
412     */
413   public abstract PageFlowScopeProvider getPageFlowScopeProvider();
414 
415   /**
416     * Gets the PageResolver for the current application.
417     */
418   public abstract PageResolver getPageResolver();
419 
420   /**
421    * Gets the RegionManager for the current application.
422    */
423   public abstract RegionManager getRegionManager();
424 
425   //
426   // Partial Page Rendering support
427   //
428   /**
429    * Add a component as a partial target. In response to a partial event, only
430    * components registered as partial targets are re-rendered.  For
431    * a component to be successfully re-rendered when it is manually
432    * added with this API, it should have an explictly set "id".  If
433    * not, partial re-rendering may or may not work depending on the
434    * component.
435    */
436   public abstract void addPartialTarget(UIComponent newTarget);
437 
438   /**
439    * Add components relative to the given component as partial targets.
440    * <p> 
441    * See {@link #addPartialTarget(UIComponent)} for more information.
442    * </p>
443    * @param from the component to use as a relative reference for any
444    * relative IDs in the list of targets
445    * @param targets array of targets relative to the from component that
446    * should be added as targets.
447    * @see ComponentUtils#findRelativeComponent(UIComponent, String)
448    */
449   public abstract void addPartialTargets(UIComponent from, String... targets);
450   
451   /**
452    * Adds a listener on a set of particular triggering components. If one of
453    * the named components gets updated in response to a partial event, then
454    * this listener component will be rerendered during the render phase (i.e.
455    * it will be added as a partialTarget). The list should consist of names
456    * suitable for use with the findComponent method on UIComponent.
457    */
458   public abstract void addPartialTriggerListeners(UIComponent listener,
459                                                   String[] trigger);
460 
461   /**
462    * Called when any component gets updated. Any partial target components
463    * listening on this component will be added to the partialTargets list in
464    * the render phase.
465    */
466   public abstract void partialUpdateNotify(UIComponent updated);
467 
468   //
469   // Miscellaneous functionality
470   //
471 
472   public abstract UploadedFileProcessor getUploadedFileProcessor();
473 
474   /**
475    * Returns a Map that takes color palette names as keys, and returns
476    * the color palette as a result.
477    */
478   public abstract Map<String, List<Color>> getColorPalette();
479 
480   /**
481    * Returns a Map that performs message formatting with a recursive Map
482    * structure.  The first key must be the message formatting mask, and the
483    * second the first parameter into the message. (The formatter Map supports
484    * only a single parameter at this time.)
485    */
486   public abstract Map<Object, Map<Object,String>> getFormatter();
487 
488   /**
489    * Returns the Agent information for the current context
490    */
491   public abstract Agent getAgent();
492 
493   /**
494    * Saves the state of a UIComponent tree into an Object.  The
495    * Object will be serializable, unless a UIComponent
496    * in this tree contains a non-serializable property.  This
497    * method does not check that condition.
498    * @param component the component 
499    * @return an Object that can be passed to restoreComponent()
500    *  to reinstantiate the state
501    */
502   public abstract Object saveComponent(UIComponent component);
503 
504   /**
505    * Restores the state of a component.
506    * @param state an Object created by a prior call to saveComponent().
507    * @return the component
508    */
509   public abstract UIComponent restoreComponent(Object state)
510                             throws ClassNotFoundException,
511                                    InstantiationException,
512                                    IllegalAccessException;
513 
514   /**
515    * Releases the RequestContext object.  This method must only
516    * be called by the code that created the RequestContext.
517    * @exception IllegalStateException if no RequestContext is attached
518    * to the thread, or the attached context is not this object
519    */
520   public void release()
521   {
522     if (_LOG.isFinest())
523     {
524       _LOG.finest("RequestContext released.", 
525                   new RuntimeException("This is not an error. This trace is for debugging."));
526     }
527     
528     Object o = _CURRENT_CONTEXT.get();
529     if (o == null)
530       throw new IllegalStateException(
531               _addHelp("RequestContext was already released or " +
532                        "had never been attached."));
533     if (o != this)
534       throw new IllegalStateException(_LOG.getMessage(
535         "RELEASE_DIFFERENT_REQUESTCONTEXT_THAN_CURRENT_ONE"));
536     
537     _CURRENT_CONTEXT.remove();
538   }
539 
540   /**
541    * Attaches a RequestContext to the current thread.  This method 
542    * should only be called by a RequestContext object itself.
543    * @exception IllegalStateException if an RequestContext is already
544    * attached to the thread
545    */
546   public void attach()
547   {
548     if (_LOG.isFinest())
549     {
550       _LOG.finest("RequestContext attached.", 
551                   new RuntimeException(_LOG.getMessage(
552                     "DEBUGGING_TRACE_NOT_ERROR")));
553     }
554 
555     Object o = _CURRENT_CONTEXT.get();
556     if (o != null)
557     {
558       throw new IllegalStateException(
559               _addHelp("Trying to attach RequestContext to a " +
560                        "thread that already had one."));
561     }
562     _CURRENT_CONTEXT.set(this);
563   }
564   
565   private static String _addHelp(String error)
566   {
567     if (!_LOG.isFinest())
568     {
569       error += " To enable stack traces of each RequestContext attach/release call," +
570         " enable Level.FINEST logging for the "+RequestContext.class;
571     }
572     return error;
573   }
574 
575   //
576   // Pick a ClassLoader
577   //
578   private ClassLoader _getClassLoader()
579   {
580     return Thread.currentThread().getContextClassLoader();
581   }
582 
583   @SuppressWarnings({"CollectionWithoutInitialCapacity"})
584   private static final ConcurrentMap<ClassLoader, ConcurrentMap<String, Object>> _APPLICATION_MAPS =
585        new ConcurrentHashMap<ClassLoader, ConcurrentMap<String, Object>>();
586   static private final ThreadLocal<RequestContext> _CURRENT_CONTEXT = 
587     new ThreadLocal<RequestContext>();
588   static private final TrinidadLogger _LOG =
589     TrinidadLogger.createTrinidadLogger(RequestContext.class);
590 }