001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Portions Copyright Apache Software Foundation.
007: *
008: * The contents of this file are subject to the terms of either the GNU
009: * General Public License Version 2 only ("GPL") or the Common Development
010: * and Distribution License("CDDL") (collectively, the "License"). You
011: * may not use this file except in compliance with the License. You can obtain
012: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
013: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
014: * language governing permissions and limitations under the License.
015: *
016: * When distributing the software, include this License Header Notice in each
017: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
018: * Sun designates this particular file as subject to the "Classpath" exception
019: * as provided by Sun in the GPL Version 2 section of the License file that
020: * accompanied this code. If applicable, add the following below the License
021: * Header, with the fields enclosed by brackets [] replaced by your own
022: * identifying information: "Portions Copyrighted [year]
023: * [name of copyright owner]"
024: *
025: * Contributor(s):
026: *
027: * If you wish your version of this file to be governed by only the CDDL or
028: * only the GPL Version 2, indicate your decision by adding "[Contributor]
029: * elects to include this software in this distribution under the [CDDL or GPL
030: * Version 2] license." If you don't indicate a single choice of license, a
031: * recipient has the option to distribute your version of this file under
032: * either the CDDL, the GPL Version 2 or to extend the choice of license to
033: * its licensees as provided above. However, if you add GPL Version 2 code
034: * and therefore, elected the GPL Version 2 license, then the option applies
035: * only if the new code is made subject to such option by the copyright
036: * holder.
037: */
038:
039: package org.apache.taglibs.standard.tag.common.fmt;
040:
041: import java.text.DateFormat;
042: import java.text.NumberFormat;
043: import java.util.Enumeration;
044: import java.util.Locale;
045: import java.util.Vector;
046:
047: import javax.servlet.ServletResponse;
048: import javax.servlet.http.HttpServletRequest;
049: import javax.servlet.jsp.JspException;
050: import javax.servlet.jsp.PageContext;
051: import javax.servlet.jsp.jstl.core.Config;
052: import javax.servlet.jsp.jstl.fmt.LocalizationContext;
053: import javax.servlet.jsp.tagext.Tag;
054: import javax.servlet.jsp.tagext.TagSupport;
055:
056: import org.apache.taglibs.standard.resources.Resources;
057: import org.apache.taglibs.standard.tag.common.core.Util;
058:
059: /**
060: * Support for tag handlers for <setLocale>, the locale setting tag in
061: * JSTL 1.0.
062: *
063: * @author Jan Luehe
064: */
065:
066: public abstract class SetLocaleSupport extends TagSupport {
067:
068: //*********************************************************************
069: // Private constants
070:
071: private static final char HYPHEN = '-';
072: private static final char UNDERSCORE = '_';
073: private static Locale[] availableFormattingLocales;
074: private static final Locale[] dateLocales = DateFormat
075: .getAvailableLocales();
076: private static final Locale[] numberLocales = NumberFormat
077: .getAvailableLocales();
078:
079: //*********************************************************************
080: // Protected state
081:
082: protected Object value; // 'value' attribute
083: protected String variant; // 'variant' attribute
084:
085: //*********************************************************************
086: // Private state
087:
088: private int scope; // 'scope' attribute
089:
090: //*********************************************************************
091: // Constructor and initialization
092:
093: /**
094: * Sets up the available formatting locales that will be used
095: * by getFormattingLocale(PageContext).
096: */
097: static {
098: Vector vec = new Vector(dateLocales.length);
099: for (int i = 0; i < dateLocales.length; i++) {
100: for (int j = 0; j < numberLocales.length; j++) {
101: if (dateLocales[i].equals(numberLocales[j])) {
102: vec.add(dateLocales[i]);
103: break;
104: }
105: }
106: }
107: availableFormattingLocales = new Locale[vec.size()];
108: availableFormattingLocales = (Locale[]) vec
109: .toArray(availableFormattingLocales);
110: }
111:
112: public SetLocaleSupport() {
113: super ();
114: init();
115: }
116:
117: private void init() {
118: value = variant = null;
119: scope = PageContext.PAGE_SCOPE;
120: }
121:
122: //*********************************************************************
123: // Tag attributes known at translation time
124:
125: public void setScope(String scope) {
126: this .scope = Util.getScope(scope);
127: }
128:
129: //*********************************************************************
130: // Tag logic
131:
132: public int doEndTag() throws JspException {
133: Locale locale = null;
134:
135: if (value == null) {
136: locale = Locale.getDefault();
137: } else if (value instanceof String) {
138: if (((String) value).trim().equals("")) {
139: locale = Locale.getDefault();
140: } else {
141: locale = parseLocale((String) value, variant);
142: }
143: } else {
144: locale = (Locale) value;
145: }
146:
147: Config.set(pageContext, Config.FMT_LOCALE, locale, scope);
148: setResponseLocale(pageContext, locale);
149:
150: return EVAL_PAGE;
151: }
152:
153: // Releases any resources we may have (or inherit)
154: public void release() {
155: init();
156: }
157:
158: //*********************************************************************
159: // Public utility methods
160:
161: /**
162: * See parseLocale(String, String) for details.
163: */
164: public static Locale parseLocale(String locale) {
165: return parseLocale(locale, null);
166: }
167:
168: /**
169: * Parses the given locale string into its language and (optionally)
170: * country components, and returns the corresponding
171: * <tt>java.util.Locale</tt> object.
172: *
173: * If the given locale string is null or empty, the runtime's default
174: * locale is returned.
175: *
176: * @param locale the locale string to parse
177: * @param variant the variant
178: *
179: * @return <tt>java.util.Locale</tt> object corresponding to the given
180: * locale string, or the runtime's default locale if the locale string is
181: * null or empty
182: *
183: * @throws IllegalArgumentException if the given locale does not have a
184: * language component or has an empty country component
185: */
186: public static Locale parseLocale(String locale, String variant) {
187:
188: Locale ret = null;
189: String language = locale;
190: String country = null;
191: int index = -1;
192:
193: if (((index = locale.indexOf(HYPHEN)) > -1)
194: || ((index = locale.indexOf(UNDERSCORE)) > -1)) {
195: language = locale.substring(0, index);
196: country = locale.substring(index + 1);
197: }
198:
199: if ((language == null) || (language.length() == 0)) {
200: throw new IllegalArgumentException(Resources
201: .getMessage("LOCALE_NO_LANGUAGE"));
202: }
203:
204: if (country == null) {
205: if (variant != null)
206: ret = new Locale(language, "", variant);
207: else
208: ret = new Locale(language, "");
209: } else if (country.length() > 0) {
210: if (variant != null)
211: ret = new Locale(language, country, variant);
212: else
213: ret = new Locale(language, country);
214: } else {
215: throw new IllegalArgumentException(Resources
216: .getMessage("LOCALE_EMPTY_COUNTRY"));
217: }
218:
219: return ret;
220: }
221:
222: //*********************************************************************
223: // Package-scoped utility methods
224:
225: /*
226: * Stores the given locale in the response object of the given page
227: * context, and stores the locale's associated charset in the
228: * javax.servlet.jsp.jstl.fmt.request.charset session attribute, which
229: * may be used by the <requestEncoding> action in a page invoked by a
230: * form included in the response to set the request charset to the same as
231: * the response charset (this makes it possible for the container to
232: * decode the form parameter values properly, since browsers typically
233: * encode form field values using the response's charset).
234: *
235: * @param pageContext the page context whose response object is assigned
236: * the given locale
237: * @param locale the response locale
238: */
239: static void setResponseLocale(PageContext pc, Locale locale) {
240: // set response locale
241: ServletResponse response = pc.getResponse();
242: response.setLocale(locale);
243:
244: // get response character encoding and store it in session attribute
245: if (pc.getSession() != null) {
246: try {
247: pc.setAttribute(
248: RequestEncodingSupport.REQUEST_CHAR_SET,
249: response.getCharacterEncoding(),
250: PageContext.SESSION_SCOPE);
251: } catch (IllegalStateException ex) {
252: } // invalidated session ignored
253: }
254: }
255:
256: /*
257: * Returns the formatting locale to use with the given formatting action
258: * in the given page.
259: *
260: * @param pc The page context containing the formatting action
261: * @param fromTag The formatting action
262: * @param isDate true if the locale is needed for date formatting, false
263: * otherwise (i.e., the locale is needed for number formatting)
264: * @param format <tt>true</tt> if the formatting action is of type
265: * <formatXXX> (as opposed to <parseXXX>), and <tt>false</tt> otherwise
266: * (if set to <tt>true</tt>, the formatting locale that is returned by
267: * this method is used to set the response locale).
268: *
269: * @return the formatting locale to use
270: */
271: static Locale getFormattingLocale(PageContext pc, Tag fromTag,
272: boolean isDate, boolean format) {
273:
274: LocalizationContext locCtxt = null;
275:
276: // Get formatting locale from enclosing <fmt:bundle>
277: Tag parent = findAncestorWithClass(fromTag, BundleSupport.class);
278: if (parent != null) {
279: /*
280: * use locale from localization context established by parent
281: * <fmt:bundle> action, unless that locale is null
282: */
283: locCtxt = ((BundleSupport) parent).getLocalizationContext();
284: if (locCtxt.getLocale() != null) {
285: if (format) {
286: setResponseLocale(pc, locCtxt.getLocale());
287: }
288: return locCtxt.getLocale();
289: }
290: }
291:
292: // Use locale from default I18N localization context, unless it is null
293: if ((locCtxt = BundleSupport.getLocalizationContext(pc)) != null) {
294: if (locCtxt.getLocale() != null) {
295: if (format) {
296: setResponseLocale(pc, locCtxt.getLocale());
297: }
298: return locCtxt.getLocale();
299: }
300: }
301:
302: /*
303: * Establish formatting locale by comparing the preferred locales
304: * (in order of preference) against the available formatting
305: * locales, and determining the best matching locale.
306: */
307: Locale match = null;
308: Locale pref = getLocale(pc, Config.FMT_LOCALE);
309: Locale[] avail = null;
310: if (isDate) {
311: avail = dateLocales;
312: } else {
313: avail = numberLocales;
314: }
315: if (pref != null) {
316: // Preferred locale is application-based
317: match = findFormattingMatch(pref, avail);
318: } else {
319: // Preferred locales are browser-based
320: match = findFormattingMatch(pc, avail);
321: }
322: if (match == null) {
323: //Use fallback locale.
324: pref = getLocale(pc, Config.FMT_FALLBACK_LOCALE);
325: if (pref != null) {
326: match = findFormattingMatch(pref, avail);
327: }
328: }
329: if (format && (match != null)) {
330: setResponseLocale(pc, match);
331: }
332:
333: return match;
334: }
335:
336: /*
337: * Returns the formatting locale to use when <fmt:message> is used
338: * with a locale-less localization context.
339: *
340: * @param pc The page context containing the formatting action
341: * @return the formatting locale to use
342: */
343: static Locale getFormattingLocale(PageContext pc) {
344: /*
345: * Establish formatting locale by comparing the preferred locales
346: * (in order of preference) against the available formatting
347: * locales, and determining the best matching locale.
348: */
349: Locale match = null;
350: Locale pref = getLocale(pc, Config.FMT_LOCALE);
351: if (pref != null) {
352: // Preferred locale is application-based
353: match = findFormattingMatch(pref,
354: availableFormattingLocales);
355: } else {
356: // Preferred locales are browser-based
357: match = findFormattingMatch(pc, availableFormattingLocales);
358: }
359: if (match == null) {
360: //Use fallback locale.
361: pref = getLocale(pc, Config.FMT_FALLBACK_LOCALE);
362: if (pref != null) {
363: match = findFormattingMatch(pref,
364: availableFormattingLocales);
365: }
366: }
367: if (match != null) {
368: setResponseLocale(pc, match);
369: }
370:
371: return match;
372: }
373:
374: /*
375: * Returns the locale specified by the named scoped attribute or context
376: * configuration parameter.
377: *
378: * <p> The named scoped attribute is searched in the page, request,
379: * session (if valid), and application scope(s) (in this order). If no such
380: * attribute exists in any of the scopes, the locale is taken from the
381: * named context configuration parameter.
382: *
383: * @param pageContext the page in which to search for the named scoped
384: * attribute or context configuration parameter
385: * @param name the name of the scoped attribute or context configuration
386: * parameter
387: *
388: * @return the locale specified by the named scoped attribute or context
389: * configuration parameter, or <tt>null</tt> if no scoped attribute or
390: * configuration parameter with the given name exists
391: */
392: static Locale getLocale(PageContext pageContext, String name) {
393: Locale loc = null;
394:
395: Object obj = Config.find(pageContext, name);
396: if (obj != null) {
397: if (obj instanceof Locale) {
398: loc = (Locale) obj;
399: } else {
400: loc = parseLocale((String) obj);
401: }
402: }
403:
404: return loc;
405: }
406:
407: //*********************************************************************
408: // Private utility methods
409:
410: /*
411: * Determines the client's preferred locales from the request, and compares
412: * each of the locales (in order of preference) against the available
413: * locales in order to determine the best matching locale.
414: *
415: * @param pageContext Page containing the formatting action
416: * @param avail Available formatting locales
417: *
418: * @return Best matching locale, or <tt>null</tt> if no match was found
419: */
420: private static Locale findFormattingMatch(PageContext pageContext,
421: Locale[] avail) {
422: Locale match = null;
423: for (Enumeration enum_ = Util
424: .getRequestLocales((HttpServletRequest) pageContext
425: .getRequest()); enum_.hasMoreElements();) {
426: Locale locale = (Locale) enum_.nextElement();
427: match = findFormattingMatch(locale, avail);
428: if (match != null) {
429: break;
430: }
431: }
432:
433: return match;
434: }
435:
436: /*
437: * Returns the best match between the given preferred locale and the
438: * given available locales.
439: *
440: * The best match is given as the first available locale that exactly
441: * matches the given preferred locale ("exact match"). If no exact match
442: * exists, the best match is given to an available locale that meets
443: * the following criteria (in order of priority):
444: * - available locale's variant is empty and exact match for both
445: * language and country
446: * - available locale's variant and country are empty, and exact match
447: * for language.
448: *
449: * @param pref the preferred locale
450: * @param avail the available formatting locales
451: *
452: * @return Available locale that best matches the given preferred locale,
453: * or <tt>null</tt> if no match exists
454: */
455: private static Locale findFormattingMatch(Locale pref,
456: Locale[] avail) {
457: Locale match = null;
458: boolean langAndCountryMatch = false;
459: for (int i = 0; i < avail.length; i++) {
460: if (pref.equals(avail[i])) {
461: // Exact match
462: match = avail[i];
463: break;
464: } else if (!"".equals(pref.getVariant())
465: && "".equals(avail[i].getVariant())
466: && pref.getLanguage()
467: .equals(avail[i].getLanguage())
468: && pref.getCountry().equals(avail[i].getCountry())) {
469: // Language and country match; different variant
470: match = avail[i];
471: langAndCountryMatch = true;
472: } else if (!langAndCountryMatch
473: && pref.getLanguage()
474: .equals(avail[i].getLanguage())
475: && ("".equals(avail[i].getCountry()))) {
476: // Language match
477: if (match == null) {
478: match = avail[i];
479: }
480: }
481: }
482: return match;
483: }
484: }
|