Coverage report

  %line %branch
org.apache.turbine.services.localization.TurbineLocalizationService
0% 
0% 

 1  
 package org.apache.turbine.services.localization;
 2  
 
 3  
 /*
 4  
  * Copyright 2001-2005 The Apache Software Foundation.
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License")
 7  
  * you may not use this file except in compliance with the License.
 8  
  * 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, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  
  * See the License for the specific language governing permissions and
 16  
  * limitations under the License.
 17  
  */
 18  
 
 19  
 import java.util.HashMap;
 20  
 import java.util.Locale;
 21  
 import java.util.Map;
 22  
 import java.util.MissingResourceException;
 23  
 import java.util.ResourceBundle;
 24  
 
 25  
 import javax.servlet.http.HttpServletRequest;
 26  
 
 27  
 import org.apache.commons.configuration.Configuration;
 28  
 
 29  
 import org.apache.commons.lang.StringUtils;
 30  
 
 31  
 import org.apache.commons.logging.Log;
 32  
 import org.apache.commons.logging.LogFactory;
 33  
 
 34  
 import org.apache.turbine.Turbine;
 35  
 import org.apache.turbine.services.InitializationException;
 36  
 import org.apache.turbine.services.TurbineBaseService;
 37  
 import org.apache.turbine.util.RunData;
 38  
 
 39  
 /**
 40  
  * <p>This class is the single point of access to all localization
 41  
  * resources.  It caches different ResourceBundles for different
 42  
  * Locales.</p>
 43  
  *
 44  
  * <p>Usage example:</p>
 45  
  *
 46  
  * <blockquote><code><pre>
 47  
  * LocalizationService ls = (LocalizationService) TurbineServices
 48  
  *     .getInstance().getService(LocalizationService.SERVICE_NAME);
 49  
  * </pre></code></blockquote>
 50  
  *
 51  
  * <p>Then call one of four methods to retrieve a ResourceBundle:
 52  
  *
 53  
  * <ul>
 54  
  * <li>getBundle("MyBundleName")</li>
 55  
  * <li>getBundle("MyBundleName", httpAcceptLanguageHeader)</li>
 56  
  * <li>etBundle("MyBundleName", HttpServletRequest)</li>
 57  
  * <li>getBundle("MyBundleName", Locale)</li>
 58  
  * <li>etc.</li>
 59  
  * </ul></p>
 60  
  *
 61  
  * @author <a href="mailto:jm@mediaphil.de">Jonas Maurus</a>
 62  
  * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
 63  
  * @author <a href="mailto:novalidemail@foo.com">Frank Y. Kim</a>
 64  
  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
 65  
  * @author <a href="mailto:leonardr@collab.net">Leonard Richardson</a>
 66  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 67  
  * @version $Id: TurbineLocalizationService.java 264152 2005-08-29 14:50:22Z henning $
 68  
  */
 69  
 public class TurbineLocalizationService
 70  
         extends TurbineBaseService
 71  
         implements LocalizationService
 72  
 {
 73  
     /** Logging */
 74  0
     private static Log log = LogFactory.getLog(TurbineLocalizationService.class);
 75  
 
 76  
     /**
 77  
      * The value to pass to <code>MessageFormat</code> if a
 78  
      * <code>null</code> reference is passed to <code>format()</code>.
 79  
      */
 80  0
     private static final Object[] NO_ARGS = new Object[0];
 81  
 
 82  
     /**
 83  
      * Bundle name keys a Map of the ResourceBundles in this
 84  
      * service (which is in turn keyed by Locale).
 85  
      * Key=bundle name
 86  
      * Value=Hashtable containing ResourceBundles keyed by Locale.
 87  
      */
 88  0
     private Map bundles = null;
 89  
 
 90  
     /**
 91  
      * The list of default bundles to search.
 92  
      */
 93  0
     private String[] bundleNames = null;
 94  
 
 95  
     /**
 96  
      * The name of the default locale to use (includes language and
 97  
      * country).
 98  
      */
 99  0
     private Locale defaultLocale = null;
 100  
 
 101  
     /** The name of the default language to use. */
 102  0
     private String defaultLanguage = null;
 103  
 
 104  
     /** The name of the default country to use. */
 105  0
     private String defaultCountry = null;
 106  
 
 107  
     /**
 108  
      * Constructor.
 109  
      */
 110  
     public TurbineLocalizationService()
 111  0
     {
 112  0
         bundles = new HashMap();
 113  0
     }
 114  
 
 115  
     /**
 116  
      * Called the first time the Service is used.
 117  
      */
 118  
     public void init()
 119  
             throws InitializationException
 120  
     {
 121  0
         Configuration conf = Turbine.getConfiguration();
 122  
 
 123  0
         initBundleNames(null);
 124  
 
 125  0
         Locale jvmDefault = Locale.getDefault();
 126  
 
 127  0
         defaultLanguage = conf.getString("locale.default.language",
 128  
                 jvmDefault.getLanguage()).trim();
 129  0
         defaultCountry = conf.getString("locale.default.country",
 130  
                 jvmDefault.getCountry()).trim();
 131  
 
 132  0
         defaultLocale = new Locale(defaultLanguage, defaultCountry);
 133  0
         setInit(true);
 134  0
     }
 135  
 
 136  
     /**
 137  
      * Initialize list of default bundle names.
 138  
      *
 139  
      * @param ignored Ignored.
 140  
      */
 141  
     protected void initBundleNames(String[] ignored)
 142  
     {
 143  0
         Configuration conf = Turbine.getConfiguration();
 144  0
         bundleNames = conf.getStringArray("locale.default.bundles");
 145  0
         String name = conf.getString("locale.default.bundle");
 146  
 
 147  0
         if (name != null && name.length() > 0)
 148  
         {
 149  
             // Using old-style single bundle name property.
 150  0
             if (bundleNames == null || bundleNames.length <= 0)
 151  
             {
 152  0
                 bundleNames = new String[] {name};
 153  
             }
 154  
             else
 155  
             {
 156  
                 // Prepend "default" bundle name.
 157  0
                 String[] array = new String[bundleNames.length + 1];
 158  0
                 array[0] = name;
 159  0
                 System.arraycopy(bundleNames, 0, array, 1, bundleNames.length);
 160  0
                 bundleNames = array;
 161  
             }
 162  
         }
 163  0
         if (bundleNames == null)
 164  
         {
 165  0
             bundleNames = new String[0];
 166  
         }
 167  0
     }
 168  
 
 169  
     /**
 170  
      * Retrieves the default language (specified in the config file).
 171  
      */
 172  
     public String getDefaultLanguage()
 173  
     {
 174  0
         return defaultLanguage;
 175  
     }
 176  
 
 177  
     /**
 178  
      * Retrieves the default country (specified in the config file).
 179  
      */
 180  
     public String getDefaultCountry()
 181  
     {
 182  0
         return defaultCountry;
 183  
     }
 184  
 
 185  
     /**
 186  
      * Retrieves the name of the default bundle (as specified in the
 187  
      * config file).
 188  
      * @see org.apache.turbine.services.localization.LocalizationService#getDefaultBundleName()
 189  
      */
 190  
     public String getDefaultBundleName()
 191  
     {
 192  0
         return (bundleNames.length > 0 ? bundleNames[0] : "");
 193  
     }
 194  
 
 195  
     /**
 196  
      * @see org.apache.turbine.services.localization.LocalizationService#getBundleNames()
 197  
      */
 198  
     public String[] getBundleNames()
 199  
     {
 200  0
         return (String []) bundleNames.clone();
 201  
     }
 202  
 
 203  
     /**
 204  
      * This method returns a ResourceBundle given the bundle name
 205  
      * "DEFAULT" and the default Locale information supplied in
 206  
      * TurbineProperties.
 207  
      *
 208  
      * @return A localized ResourceBundle.
 209  
      */
 210  
     public ResourceBundle getBundle()
 211  
     {
 212  0
         return getBundle(getDefaultBundleName(), (Locale) null);
 213  
     }
 214  
 
 215  
     /**
 216  
      * This method returns a ResourceBundle given the bundle name and
 217  
      * the default Locale information supplied in TurbineProperties.
 218  
      *
 219  
      * @param bundleName Name of bundle.
 220  
      * @return A localized ResourceBundle.
 221  
      */
 222  
     public ResourceBundle getBundle(String bundleName)
 223  
     {
 224  0
         return getBundle(bundleName, (Locale) null);
 225  
     }
 226  
 
 227  
     /**
 228  
      * This method returns a ResourceBundle given the bundle name and
 229  
      * the Locale information supplied in the HTTP "Accept-Language"
 230  
      * header.
 231  
      *
 232  
      * @param bundleName Name of bundle.
 233  
      * @param languageHeader A String with the language header.
 234  
      * @return A localized ResourceBundle.
 235  
      */
 236  
     public ResourceBundle getBundle(String bundleName, String languageHeader)
 237  
     {
 238  0
         return getBundle(bundleName, getLocale(languageHeader));
 239  
     }
 240  
 
 241  
     /**
 242  
      * This method returns a ResourceBundle given the Locale
 243  
      * information supplied in the HTTP "Accept-Language" header which
 244  
      * is stored in HttpServletRequest.
 245  
      *
 246  
      * @param req HttpServletRequest.
 247  
      * @return A localized ResourceBundle.
 248  
      */
 249  
     public ResourceBundle getBundle(HttpServletRequest req)
 250  
     {
 251  0
         return getBundle(getDefaultBundleName(), getLocale(req));
 252  
     }
 253  
 
 254  
     /**
 255  
      * This method returns a ResourceBundle given the bundle name and
 256  
      * the Locale information supplied in the HTTP "Accept-Language"
 257  
      * header which is stored in HttpServletRequest.
 258  
      *
 259  
      * @param bundleName Name of the bundle to use if the request's
 260  
      * locale cannot be resolved.
 261  
      * @param req HttpServletRequest.
 262  
      * @return A localized ResourceBundle.
 263  
      */
 264  
     public ResourceBundle getBundle(String bundleName, HttpServletRequest req)
 265  
     {
 266  0
         return getBundle(bundleName, getLocale(req));
 267  
     }
 268  
 
 269  
     /**
 270  
      * This method returns a ResourceBundle given the Locale
 271  
      * information supplied in the HTTP "Accept-Language" header which
 272  
      * is stored in RunData.
 273  
      *
 274  
      * @param data Turbine information.
 275  
      * @return A localized ResourceBundle.
 276  
      */
 277  
     public ResourceBundle getBundle(RunData data)
 278  
     {
 279  0
         return getBundle(getDefaultBundleName(), getLocale(data.getRequest()));
 280  
     }
 281  
 
 282  
     /**
 283  
      * This method returns a ResourceBundle given the bundle name and
 284  
      * the Locale information supplied in the HTTP "Accept-Language"
 285  
      * header which is stored in RunData.
 286  
      *
 287  
      * @param bundleName Name of bundle.
 288  
      * @param data Turbine information.
 289  
      * @return A localized ResourceBundle.
 290  
      */
 291  
     public ResourceBundle getBundle(String bundleName, RunData data)
 292  
     {
 293  0
         return getBundle(bundleName, getLocale(data.getRequest()));
 294  
     }
 295  
 
 296  
     /**
 297  
      * This method returns a ResourceBundle for the given bundle name
 298  
      * and the given Locale.
 299  
      *
 300  
      * @param bundleName Name of bundle (or <code>null</code> for the
 301  
      * default bundle).
 302  
      * @param locale The locale (or <code>null</code> for the locale
 303  
      * indicated by the default language and country).
 304  
      * @return A localized ResourceBundle.
 305  
      */
 306  
     public ResourceBundle getBundle(String bundleName, Locale locale)
 307  
     {
 308  
         // Assure usable inputs.
 309  0
         bundleName = (StringUtils.isEmpty(bundleName) ? getDefaultBundleName() : bundleName.trim());
 310  0
         if (locale == null)
 311  
         {
 312  0
             locale = getLocale((String) null);
 313  
         }
 314  
 
 315  
         // Find/retrieve/cache bundle.
 316  0
         ResourceBundle rb = null;
 317  0
         Map bundlesByLocale = (Map) bundles.get(bundleName);
 318  0
         if (bundlesByLocale != null)
 319  
         {
 320  
             // Cache of bundles by locale for the named bundle exists.
 321  
             // Check the cache for a bundle corresponding to locale.
 322  0
             rb = (ResourceBundle) bundlesByLocale.get(locale);
 323  
 
 324  0
             if (rb == null)
 325  
             {
 326  
                 // Not yet cached.
 327  0
                 rb = cacheBundle(bundleName, locale);
 328  
             }
 329  
         }
 330  
         else
 331  
         {
 332  0
             rb = cacheBundle(bundleName, locale);
 333  
         }
 334  0
         return rb;
 335  
     }
 336  
 
 337  
     /**
 338  
      * Caches the named bundle for fast lookups.  This operation is
 339  
      * relatively expesive in terms of memory use, but is optimized
 340  
      * for run-time speed in the usual case.
 341  
      *
 342  
      * @exception MissingResourceException Bundle not found.
 343  
      */
 344  
     private synchronized ResourceBundle cacheBundle(String bundleName,
 345  
                                                     Locale locale)
 346  
         throws MissingResourceException
 347  
     {
 348  0
         Map bundlesByLocale = (HashMap) bundles.get(bundleName);
 349  0
         ResourceBundle rb = (bundlesByLocale == null ? class="keyword">null :
 350  
                              (ResourceBundle) bundlesByLocale.get(locale));
 351  
 
 352  0
         if (rb == null)
 353  
         {
 354  0
             bundlesByLocale = (bundlesByLocale == null ? new HashMap(3) :
 355  
                                new HashMap(bundlesByLocale));
 356  
             try
 357  
             {
 358  0
                 rb = ResourceBundle.getBundle(bundleName, locale);
 359  
             }
 360  0
             catch (MissingResourceException e)
 361  
             {
 362  0
                 rb = findBundleByLocale(bundleName, locale, bundlesByLocale);
 363  0
                 if (rb == null)
 364  
                 {
 365  0
                     throw (MissingResourceException) e.fillInStackTrace();
 366  
                 }
 367  0
             }
 368  
 
 369  0
             if (rb != null)
 370  
             {
 371  
                 // Cache bundle.
 372  0
                 bundlesByLocale.put(rb.getLocale(), rb);
 373  
 
 374  0
                 Map bundlesByName = new HashMap(bundles);
 375  0
                 bundlesByName.put(bundleName, bundlesByLocale);
 376  0
                 this.bundles = bundlesByName;
 377  
             }
 378  
         }
 379  0
         return rb;
 380  
     }
 381  
 
 382  
     /**
 383  
      * <p>Retrieves the bundle most closely matching first against the
 384  
      * supplied inputs, then against the defaults.</p>
 385  
      *
 386  
      * <p>Use case: some clients send a HTTP Accept-Language header
 387  
      * with a value of only the language to use
 388  
      * (i.e. "Accept-Language: en"), and neglect to include a country.
 389  
      * When there is no bundle for the requested language, this method
 390  
      * can be called to try the default country (checking internally
 391  
      * to assure the requested criteria matches the default to avoid
 392  
      * disconnects between language and country).</p>
 393  
      *
 394  
      * <p>Since we're really just guessing at possible bundles to use,
 395  
      * we don't ever throw <code>MissingResourceException</code>.</p>
 396  
      */
 397  
     private ResourceBundle findBundleByLocale(String bundleName, Locale locale,
 398  
                                               Map bundlesByLocale)
 399  
     {
 400  0
         ResourceBundle rb = null;
 401  0
         if (!StringUtils.isNotEmpty(locale.getCountry()) &&
 402  
              defaultLanguage.equals(locale.getLanguage()))
 403  
         {
 404  
             /*
 405  
              *              log.debug("Requested language '" + locale.getLanguage() +
 406  
              *              "' matches default: Attempting to guess bundle " +
 407  
              *              "using default country '" + defaultCountry + '\'');
 408  
              */
 409  0
             Locale withDefaultCountry = new Locale(locale.getLanguage(),
 410  
                                                    defaultCountry);
 411  0
             rb = (ResourceBundle) bundlesByLocale.get(withDefaultCountry);
 412  0
             if (rb == null)
 413  
             {
 414  0
                 rb = getBundleIgnoreException(bundleName, withDefaultCountry);
 415  
             }
 416  
         }
 417  0
         else if (!StringUtils.isNotEmpty(locale.getLanguage()) &&
 418  
                   defaultCountry.equals(locale.getCountry()))
 419  
         {
 420  0
             Locale withDefaultLanguage = new Locale(defaultLanguage,
 421  
                                                     locale.getCountry());
 422  0
             rb = (ResourceBundle) bundlesByLocale.get(withDefaultLanguage);
 423  0
             if (rb == null)
 424  
             {
 425  0
                 rb = getBundleIgnoreException(bundleName, withDefaultLanguage);
 426  
             }
 427  
         }
 428  
 
 429  0
         if (rb == null && !defaultLocale.equals(locale))
 430  
         {
 431  0
             rb = getBundleIgnoreException(bundleName, defaultLocale);
 432  
         }
 433  
 
 434  0
         return rb;
 435  
     }
 436  
 
 437  
     /**
 438  
      * Retrieves the bundle using the
 439  
      * <code>ResourceBundle.getBundle(String, Locale)</code> method,
 440  
      * returning <code>null</code> instead of throwing
 441  
      * <code>MissingResourceException</code>.
 442  
      */
 443  
     private ResourceBundle getBundleIgnoreException(String bundleName,
 444  
                                                           Locale locale)
 445  
     {
 446  
         try
 447  
         {
 448  0
             return ResourceBundle.getBundle(bundleName, locale);
 449  
         }
 450  0
         catch (MissingResourceException ignored)
 451  
         {
 452  0
             return null;
 453  
         }
 454  
     }
 455  
 
 456  
     /**
 457  
      * This method sets the name of the first bundle in the search
 458  
      * list (the "default" bundle).
 459  
      *
 460  
      * @param defaultBundle Name of default bundle.
 461  
      */
 462  
     public void setBundle(String defaultBundle)
 463  
     {
 464  0
         if (bundleNames.length > 0)
 465  
         {
 466  0
             bundleNames[0] = defaultBundle;
 467  
         }
 468  
         else
 469  
         {
 470  0
             synchronized (this)
 471  
             {
 472  0
                 if (bundleNames.length <= 0)
 473  
                 {
 474  0
                     bundleNames = new String[] {defaultBundle};
 475  
                 }
 476  0
             }
 477  
         }
 478  0
     }
 479  
 
 480  
     /**
 481  
      * @see org.apache.turbine.services.localization.LocalizationService#getLocale(HttpServletRequest)
 482  
      */
 483  
     public final Locale getLocale(HttpServletRequest req)
 484  
     {
 485  0
         return getLocale(req.getHeader(ACCEPT_LANGUAGE));
 486  
     }
 487  
 
 488  
     /**
 489  
      * @see org.apache.turbine.services.localization.LocalizationService#getLocale(String)
 490  
      */
 491  
     public Locale getLocale(String header)
 492  
     {
 493  0
         if (!StringUtils.isEmpty(header))
 494  
         {
 495  0
             LocaleTokenizer tok = new LocaleTokenizer(header);
 496  0
             if (tok.hasNext())
 497  
             {
 498  0
                 return (Locale) tok.next();
 499  
             }
 500  
         }
 501  
 
 502  
         // Couldn't parse locale.
 503  0
         return defaultLocale;
 504  
     }
 505  
 
 506  
     /**
 507  
      * @exception MissingResourceException Specified key cannot be matched.
 508  
      * @see org.apache.turbine.services.localization.LocalizationService#getString(String, Locale, String)
 509  
      */
 510  
     public String getString(String bundleName, Locale locale, String key)
 511  
     {
 512  0
         String value = null;
 513  
 
 514  0
         if (locale == null)
 515  
         {
 516  0
             locale = getLocale((String) null);
 517  
         }
 518  
 
 519  
         // Look for text in requested bundle.
 520  0
         ResourceBundle rb = getBundle(bundleName, locale);
 521  0
         value = getStringOrNull(rb, key);
 522  
 
 523  
         // Look for text in list of default bundles.
 524  0
         if (value == null && bundleNames.length > 0)
 525  
         {
 526  
             String name;
 527  0
             for (int i = 0; i < bundleNames.length; i++)
 528  
             {
 529  0
                 name = bundleNames[i];
 530  
                 //System.out.println("getString(): name=" + name +
 531  
                 //                   ", locale=" + locale + ", i=" + i);
 532  0
                 if (!name.equals(bundleName))
 533  
                 {
 534  0
                     rb = getBundle(name, locale);
 535  0
                     value = getStringOrNull(rb, key);
 536  0
                     if (value != null)
 537  
                     {
 538  0
                         locale = rb.getLocale();
 539  0
                         break;
 540  
                     }
 541  
                 }
 542  
             }
 543  
         }
 544  
 
 545  0
         if (value == null)
 546  
         {
 547  0
             String loc = locale.toString();
 548  0
             String mesg = LocalizationService.SERVICE_NAME +
 549  
                 " noticed missing resource: " +
 550  
                 "bundleName=" + bundleName + ", locale=" + loc +
 551  
                 ", key=" + key;
 552  0
             log.debug(mesg);
 553  
             // Text not found in requested or default bundles.
 554  0
             throw new MissingResourceException(mesg, bundleName, key);
 555  
         }
 556  
 
 557  0
         return value;
 558  
     }
 559  
 
 560  
     /**
 561  
      * Gets localized text from a bundle if it's there.  Otherwise,
 562  
      * returns <code>null</code> (ignoring a possible
 563  
      * <code>MissingResourceException</code>).
 564  
      */
 565  
     protected final String getStringOrNull(ResourceBundle rb, String key)
 566  
     {
 567  0
         if (rb != null)
 568  
         {
 569  
             try
 570  
             {
 571  0
                 return rb.getString(key);
 572  
             }
 573  0
             catch (MissingResourceException ignored)
 574  
             {
 575  
             }
 576  
         }
 577  0
         return null;
 578  
     }
 579  
 
 580  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.