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