View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.jelly.tags.fmt;
17  
18  import org.apache.commons.jelly.JellyContext;
19  import org.apache.commons.jelly.JellyTagException;
20  import org.apache.commons.jelly.XMLOutput;
21  import org.apache.commons.jelly.TagSupport;
22  import org.apache.commons.jelly.expression.Expression;
23  import java.util.Enumeration;
24  import java.util.Locale;
25  import java.util.ResourceBundle;
26  import java.util.MissingResourceException;
27  
28  /***
29   * Support for tag handlers for <bundle>, the resource bundle loading
30   * tag in JSTL.
31   *
32   * @author <a href="mailto:willievu@yahoo.com">Willie Vu</a>
33   * @version 1.1
34   *
35   * @todo decide how to implement setResponseLocale
36   */
37  public class BundleTag extends TagSupport {
38  
39  
40      //**********************************************************************
41      // Private constants
42  
43      private static final Locale EMPTY_LOCALE = new Locale("", "");
44  
45  
46      //*********************************************************************
47      // Protected state
48  
49      private Expression basename;                  // 'basename' attribute
50      private Expression prefix;                    // 'prefix' attribute
51      /** evaluated basename */
52      private String ebasename;
53      /*** evaluated prefix */
54      private String eprefix;
55  
56  
57      //**********************************************************************
58      // Private state
59  
60      private LocalizationContext locCtxt;
61  
62  
63      //*********************************************************************
64      // Constructor and initialization
65  
66      public BundleTag() {
67      }
68  
69      //*********************************************************************
70      // Collaboration with subtags
71  
72      public LocalizationContext getLocalizationContext() {
73          return locCtxt;
74      }
75  
76      public String getPrefixAsString() {
77          return eprefix;
78      }
79  
80  
81      //*********************************************************************
82      // Tag logic
83  
84      /**
85       * Evaluates this tag after all the tags properties have been initialized.
86       *
87       */
88      public void doTag(XMLOutput output) throws JellyTagException {
89          Object basenameInput = null;
90          if (this.basename != null) {
91              basenameInput = this.basename.evaluate(context);
92          }
93          if (basenameInput != null) {
94              ebasename = basenameInput.toString();
95          }
96  
97          Object prefixInput = null;
98          if (this.prefix != null) {
99              prefixInput = this.prefix.evaluate(context);
100         }
101         if (prefixInput != null) {
102             eprefix = prefixInput.toString();
103         }
104 
105         this.locCtxt = this.getLocalizationContext(context, ebasename);
106         invokeBody(output);
107     }
108 
109 
110     //**********************************************************************
111     // Public utility methods
112 
113     /**
114      * Gets the default I18N localization context.
115      *
116      * @param jc Page in which to look up the default I18N localization context
117      */
118     public static LocalizationContext getLocalizationContext(JellyContext jc) {
119         LocalizationContext locCtxt = null;
120 
121         Object obj = jc.getVariable(Config.FMT_LOCALIZATION_CONTEXT);
122         if (obj == null) {
123             return null;
124         }
125 
126         if (obj instanceof LocalizationContext) {
127             locCtxt = (LocalizationContext) obj;
128         } else {
129             // localization context is a bundle basename
130             locCtxt = getLocalizationContext(jc, (String) obj);
131         }
132 
133         return locCtxt;
134     }
135 
136     /***
137      * Gets the resource bundle with the given base name, whose locale is
138      * determined as follows:
139      *
140      * Check if a match exists between the ordered set of preferred
141      * locales and the available locales, for the given base name.
142      * The set of preferred locales consists of a single locale
143      * (if the <tt>org.apache.commons.jelly.tags.fmt.locale</tt> configuration
144      * setting is present).
145      *
146      * <p> If no match was found in the previous step, check if a match
147      * exists between the fallback locale (given by the
148      * <tt>org.apache.commons.jelly.tags.fmt.fallbackLocale</tt> configuration
149      * setting) and the available locales, for the given base name.
150      *
151      * @param pageContext Page in which the resource bundle with the
152      * given base name is requested
153      * @param basename Resource bundle base name
154      *
155      * @return Localization context containing the resource bundle with the
156      * given base name and the locale that led to the resource bundle match,
157      * or the empty localization context if no resource bundle match was found
158      */
159     public static LocalizationContext getLocalizationContext(JellyContext jc,
160     String basename) {
161         LocalizationContext locCtxt = null;
162         ResourceBundle bundle = null;
163 
164         if ((basename == null) || basename.equals("")) {
165             return new LocalizationContext();
166         }
167 
168 
169         // Try preferred locales
170         Locale pref = null; {
171             Object tmp = jc.getVariable(Config.FMT_LOCALE);
172             if (tmp != null && tmp instanceof Locale) {
173                 pref = (Locale) tmp;
174             }
175         }
176         if (pref != null) {
177             // Preferred locale is application-based
178             bundle = findMatch(basename, pref, jc.getClassLoader());
179             if (bundle != null) {
180                 locCtxt = new LocalizationContext(bundle, pref);
181             }
182         }
183 
184         if (locCtxt == null) {
185             // No match found with preferred locales, try using fallback locale
186             {
187                 Object tmp = jc.getVariable(Config.FMT_FALLBACK_LOCALE);
188                 if (tmp != null && tmp instanceof Locale) {
189                     pref = (Locale) tmp;
190                 }
191             }
192             if (pref != null) {
193                 bundle = findMatch(basename, pref, jc.getClassLoader());
194                 if (bundle != null) {
195                     locCtxt = new LocalizationContext(bundle, pref);
196                 }
197             }
198         }
199 
200         if (locCtxt == null) {
201             // try using the root resource bundle with the given basename
202             try {
203                 bundle = ResourceBundle.getBundle(basename, EMPTY_LOCALE,
204                 jc.getClassLoader());
205                 if (bundle != null) {
206                     locCtxt = new LocalizationContext(bundle, null);
207                 }
208             } catch (MissingResourceException mre) {
209                 // do nothing
210             }
211         }
212 
213         if (locCtxt != null) {
214             // set response locale
215             if (locCtxt.getLocale() != null) {
216                 // TODO
217                 // SetLocaleSupport.setResponseLocale(jc, locCtxt.getLocale());
218             }
219         } else {
220             // create empty localization context
221             locCtxt = new LocalizationContext();
222         }
223 
224         return locCtxt;
225     }
226 
227 
228 
229     /*
230      * Gets the resource bundle with the given base name and preferred locale.
231      *
232      * This method calls java.util.ResourceBundle.getBundle(), but ignores
233      * its return value unless its locale represents an exact or language match
234      * with the given preferred locale.
235      *
236      * @param basename the resource bundle base name
237      * @param pref the preferred locale
238      * @param cl   classloader used to find resource bundle
239      *
240      * @return the requested resource bundle, or <tt>null</tt> if no resource
241      * bundle with the given base name exists or if there is no exact- or
242      * language-match between the preferred locale and the locale of
243      * the bundle returned by java.util.ResourceBundle.getBundle().
244      */
245     private static ResourceBundle findMatch(String basename, Locale pref, ClassLoader cl) {
246         ResourceBundle match = null;
247 
248         try {
249             ResourceBundle bundle =
250             ResourceBundle.getBundle(basename, pref, cl);
251             Locale avail = bundle.getLocale();
252             if (pref.equals(avail)) {
253                 // Exact match
254                 match = bundle;
255             } else {
256                 if (pref.getLanguage().equals(avail.getLanguage())
257                 && ("".equals(avail.getCountry()))) {
258 
259                     // Language match.
260                     // By making sure the available locale does not have a
261                     // country and matches the preferred locale's language, we
262                     // rule out "matches" based on the container's default
263                     // locale. For example, if the preferred locale is
264                     // "en-US", the container's default locale is "en-UK", and
265                     // there is a resource bundle (with the requested base
266                     // name) available for "en-UK", ResourceBundle.getBundle()
267                     // will return it, but even though its language matches
268                     // that of the preferred locale, we must ignore it,
269                     // because matches based on the container's default locale
270                     // are not portable across different containers with
271                     // different default locales.
272 
273                     match = bundle;
274                 }
275             }
276         } catch (MissingResourceException mre) {
277         }
278 
279         return match;
280     }
281 
282     /*** Setter for property basename.
283      * @param basename New value of property basename.
284      *
285      */
286     public void setBasename(Expression basename) {
287         this.basename = basename;
288     }
289 
290     /*** Setter for property prefix.
291      * @param prefix New value of property prefix.
292      *
293      */
294     public void setPrefix(Expression prefix) {
295         this.prefix = prefix;
296     }
297 
298 }