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 }