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.io.IOException;
042: import java.util.Enumeration;
043: import java.util.Locale;
044: import java.util.MissingResourceException;
045: import java.util.ResourceBundle;
046:
047: import javax.servlet.http.HttpServletRequest;
048: import javax.servlet.jsp.JspException;
049: import javax.servlet.jsp.JspTagException;
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.BodyTagSupport;
054:
055: import org.apache.taglibs.standard.tag.common.core.Util;
056:
057: /**
058: * Support for tag handlers for <bundle>, the resource bundle loading
059: * tag in JSTL 1.0.
060: *
061: * @author Jan Luehe
062: */
063:
064: public abstract class BundleSupport extends BodyTagSupport {
065:
066: //*********************************************************************
067: // Private constants
068:
069: private static final Locale EMPTY_LOCALE = new Locale("", "");
070:
071: //*********************************************************************
072: // Protected state
073:
074: protected String basename; // 'basename' attribute
075: protected String prefix; // 'prefix' attribute
076:
077: //*********************************************************************
078: // Private state
079:
080: private LocalizationContext locCtxt;
081:
082: //*********************************************************************
083: // Constructor and initialization
084:
085: public BundleSupport() {
086: super ();
087: init();
088: }
089:
090: private void init() {
091: basename = prefix = null;
092: locCtxt = null;
093: }
094:
095: //*********************************************************************
096: // Collaboration with subtags
097:
098: public LocalizationContext getLocalizationContext() {
099: return locCtxt;
100: }
101:
102: public String getPrefix() {
103: return prefix;
104: }
105:
106: //*********************************************************************
107: // Tag logic
108:
109: public int doStartTag() throws JspException {
110: locCtxt = getLocalizationContext(pageContext, basename);
111: return EVAL_BODY_BUFFERED;
112: }
113:
114: public int doEndTag() throws JspException {
115: if (bodyContent != null) {
116: try {
117: pageContext.getOut().print(bodyContent.getString());
118: } catch (IOException ioe) {
119: throw new JspTagException(ioe.toString(), ioe);
120: }
121: }
122:
123: return EVAL_PAGE;
124: }
125:
126: // Releases any resources we may have (or inherit)
127: public void release() {
128: init();
129: }
130:
131: //*********************************************************************
132: // Public utility methods
133:
134: /**
135: * Gets the default I18N localization context.
136: *
137: * @param pc Page in which to look up the default I18N localization context
138: */
139: public static LocalizationContext getLocalizationContext(
140: PageContext pc) {
141: LocalizationContext locCtxt = null;
142:
143: Object obj = Config.find(pc, Config.FMT_LOCALIZATION_CONTEXT);
144: if (obj == null) {
145: return null;
146: }
147:
148: if (obj instanceof LocalizationContext) {
149: locCtxt = (LocalizationContext) obj;
150: } else {
151: // localization context is a bundle basename
152: locCtxt = getLocalizationContext(pc, (String) obj);
153: }
154:
155: return locCtxt;
156: }
157:
158: /**
159: * Gets the resource bundle with the given base name, whose locale is
160: * determined as follows:
161: *
162: * Check if a match exists between the ordered set of preferred
163: * locales and the available locales, for the given base name.
164: * The set of preferred locales consists of a single locale
165: * (if the <tt>javax.servlet.jsp.jstl.fmt.locale</tt> configuration
166: * setting is present) or is equal to the client's preferred locales
167: * determined from the client's browser settings.
168: *
169: * <p> If no match was found in the previous step, check if a match
170: * exists between the fallback locale (given by the
171: * <tt>javax.servlet.jsp.jstl.fmt.fallbackLocale</tt> configuration
172: * setting) and the available locales, for the given base name.
173: *
174: * @param pageContext Page in which the resource bundle with the
175: * given base name is requested
176: * @param basename Resource bundle base name
177: *
178: * @return Localization context containing the resource bundle with the
179: * given base name and the locale that led to the resource bundle match,
180: * or the empty localization context if no resource bundle match was found
181: */
182: public static LocalizationContext getLocalizationContext(
183: PageContext pc, String basename) {
184: LocalizationContext locCtxt = null;
185: ResourceBundle bundle = null;
186:
187: if ((basename == null) || basename.equals("")) {
188: return new LocalizationContext();
189: }
190:
191: // Try preferred locales
192: Locale pref = SetLocaleSupport.getLocale(pc, Config.FMT_LOCALE);
193: if (pref != null) {
194: // Preferred locale is application-based
195: bundle = findMatch(basename, pref);
196: if (bundle != null) {
197: locCtxt = new LocalizationContext(bundle, pref);
198: }
199: } else {
200: // Preferred locales are browser-based
201: locCtxt = findMatch(pc, basename);
202: }
203:
204: if (locCtxt == null) {
205: // No match found with preferred locales, try using fallback locale
206: pref = SetLocaleSupport.getLocale(pc,
207: Config.FMT_FALLBACK_LOCALE);
208: if (pref != null) {
209: bundle = findMatch(basename, pref);
210: if (bundle != null) {
211: locCtxt = new LocalizationContext(bundle, pref);
212: }
213: }
214: }
215:
216: if (locCtxt == null) {
217: // try using the root resource bundle with the given basename
218: try {
219: bundle = ResourceBundle.getBundle(basename,
220: EMPTY_LOCALE, Thread.currentThread()
221: .getContextClassLoader());
222: if (bundle != null) {
223: locCtxt = new LocalizationContext(bundle, null);
224: }
225: } catch (MissingResourceException mre) {
226: // do nothing
227: }
228: }
229:
230: if (locCtxt != null) {
231: // set response locale
232: if (locCtxt.getLocale() != null) {
233: SetLocaleSupport.setResponseLocale(pc, locCtxt
234: .getLocale());
235: }
236: } else {
237: // create empty localization context
238: locCtxt = new LocalizationContext();
239: }
240:
241: return locCtxt;
242: }
243:
244: //*********************************************************************
245: // Private utility methods
246:
247: /*
248: * Determines the client's preferred locales from the request, and compares
249: * each of the locales (in order of preference) against the available
250: * locales in order to determine the best matching locale.
251: *
252: * @param pageContext the page in which the resource bundle with the
253: * given base name is requested
254: * @param basename the resource bundle's base name
255: *
256: * @return the localization context containing the resource bundle with
257: * the given base name and best matching locale, or <tt>null</tt> if no
258: * resource bundle match was found
259: */
260: private static LocalizationContext findMatch(
261: PageContext pageContext, String basename) {
262: LocalizationContext locCtxt = null;
263:
264: // Determine locale from client's browser settings.
265:
266: for (Enumeration enum_ = Util
267: .getRequestLocales((HttpServletRequest) pageContext
268: .getRequest()); enum_.hasMoreElements();) {
269: Locale pref = (Locale) enum_.nextElement();
270: ResourceBundle match = findMatch(basename, pref);
271: if (match != null) {
272: locCtxt = new LocalizationContext(match, pref);
273: break;
274: }
275: }
276:
277: return locCtxt;
278: }
279:
280: /*
281: * Gets the resource bundle with the given base name and preferred locale.
282: *
283: * This method calls java.util.ResourceBundle.getBundle(), but ignores
284: * its return value unless its locale represents an exact or language match
285: * with the given preferred locale.
286: *
287: * @param basename the resource bundle base name
288: * @param pref the preferred locale
289: *
290: * @return the requested resource bundle, or <tt>null</tt> if no resource
291: * bundle with the given base name exists or if there is no exact- or
292: * language-match between the preferred locale and the locale of
293: * the bundle returned by java.util.ResourceBundle.getBundle().
294: */
295: private static ResourceBundle findMatch(String basename, Locale pref) {
296: ResourceBundle match = null;
297:
298: try {
299: ResourceBundle bundle = ResourceBundle.getBundle(basename,
300: pref, Thread.currentThread()
301: .getContextClassLoader());
302: Locale avail = bundle.getLocale();
303: if (pref.equals(avail)) {
304: // Exact match
305: match = bundle;
306: } else {
307: /*
308: * We have to make sure that the match we got is for
309: * the specified locale. The way ResourceBundle.getBundle()
310: * works, if a match is not found with (1) the specified locale,
311: * it tries to match with (2) the current default locale as
312: * returned by Locale.getDefault() or (3) the root resource
313: * bundle (basename).
314: * We must ignore any match that could have worked with (2) or (3).
315: * So if an exact match is not found, we make the following extra
316: * tests:
317: * - avail locale must be equal to preferred locale
318: * - avail country must be empty or equal to preferred country
319: * (the equality match might have failed on the variant)
320: */
321: if (pref.getLanguage().equals(avail.getLanguage())
322: && ("".equals(avail.getCountry()) || pref
323: .getCountry()
324: .equals(avail.getCountry()))) {
325: /*
326: * Language match.
327: * By making sure the available locale does not have a
328: * country and matches the preferred locale's language, we
329: * rule out "matches" based on the container's default
330: * locale. For example, if the preferred locale is
331: * "en-US", the container's default locale is "en-UK", and
332: * there is a resource bundle (with the requested base
333: * name) available for "en-UK", ResourceBundle.getBundle()
334: * will return it, but even though its language matches
335: * that of the preferred locale, we must ignore it,
336: * because matches based on the container's default locale
337: * are not portable across different containers with
338: * different default locales.
339: */
340: match = bundle;
341: }
342: }
343: } catch (MissingResourceException mre) {
344: }
345:
346: return match;
347: }
348: }
|