1 16 17 package org.springframework.context.support; 18 19 import java.io.File ; 20 import java.io.IOException ; 21 import java.io.InputStream ; 22 import java.io.InputStreamReader ; 23 import java.text.MessageFormat ; 24 import java.util.ArrayList ; 25 import java.util.HashMap ; 26 import java.util.Iterator ; 27 import java.util.List ; 28 import java.util.Locale ; 29 import java.util.Map ; 30 import java.util.Properties ; 31 32 import org.springframework.context.ResourceLoaderAware; 33 import org.springframework.core.io.DefaultResourceLoader; 34 import org.springframework.core.io.Resource; 35 import org.springframework.core.io.ResourceLoader; 36 import org.springframework.util.Assert; 37 import org.springframework.util.DefaultPropertiesPersister; 38 import org.springframework.util.PropertiesPersister; 39 import org.springframework.util.StringUtils; 40 41 93 public class ReloadableResourceBundleMessageSource extends AbstractMessageSource 94 implements ResourceLoaderAware { 95 96 private static final String PROPERTIES_SUFFIX = ".properties"; 97 98 private static final String XML_SUFFIX = ".xml"; 99 100 101 private String [] basenames = new String [0]; 102 103 private String defaultEncoding; 104 105 private Properties fileEncodings; 106 107 private boolean fallbackToSystemLocale = true; 108 109 private long cacheMillis = -1; 110 111 private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); 112 113 private ResourceLoader resourceLoader = new DefaultResourceLoader(); 114 115 116 private final Map cachedFilenames = new HashMap (); 117 118 119 private final Map cachedProperties = new HashMap (); 120 121 122 private final Map cachedMergedProperties = new HashMap (); 123 124 125 140 public void setBasename(String basename) { 141 setBasenames(new String [] {basename}); 142 } 143 144 162 public void setBasenames(String [] basenames) { 163 if (basenames != null) { 164 this.basenames = new String [basenames.length]; 165 for (int i = 0; i < basenames.length; i++) { 166 String basename = basenames[i]; 167 Assert.hasText(basename, "Basename must not be empty"); 168 this.basenames[i] = basename.trim(); 169 } 170 } 171 else { 172 this.basenames = new String [0]; 173 } 174 } 175 176 186 public void setDefaultEncoding(String defaultEncoding) { 187 this.defaultEncoding = defaultEncoding; 188 } 189 190 200 public void setFileEncodings(Properties fileEncodings) { 201 this.fileEncodings = fileEncodings; 202 } 203 204 215 public void setFallbackToSystemLocale(boolean fallbackToSystemLocale) { 216 this.fallbackToSystemLocale = fallbackToSystemLocale; 217 } 218 219 233 public void setCacheSeconds(int cacheSeconds) { 234 this.cacheMillis = (cacheSeconds * 1000); 235 } 236 237 242 public void setPropertiesPersister(PropertiesPersister propertiesPersister) { 243 this.propertiesPersister = 244 (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister()); 245 } 246 247 256 public void setResourceLoader(ResourceLoader resourceLoader) { 257 this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); 258 } 259 260 261 265 protected String resolveCodeWithoutArguments(String code, Locale locale) { 266 if (this.cacheMillis < 0) { 267 PropertiesHolder propHolder = getMergedProperties(locale); 268 String result = propHolder.getProperty(code); 269 if (result != null) { 270 return result; 271 } 272 } 273 else { 274 for (int i = 0; i < this.basenames.length; i++) { 275 List filenames = calculateAllFilenames(this.basenames[i], locale); 276 for (int j = 0; j < filenames.size(); j++) { 277 String filename = (String ) filenames.get(j); 278 PropertiesHolder propHolder = getProperties(filename); 279 String result = propHolder.getProperty(code); 280 if (result != null) { 281 return result; 282 } 283 } 284 } 285 } 286 return null; 287 } 288 289 293 protected MessageFormat resolveCode(String code, Locale locale) { 294 if (this.cacheMillis < 0) { 295 PropertiesHolder propHolder = getMergedProperties(locale); 296 MessageFormat result = propHolder.getMessageFormat(code, locale); 297 if (result != null) { 298 return result; 299 } 300 } 301 else { 302 for (int i = 0; i < this.basenames.length; i++) { 303 List filenames = calculateAllFilenames(this.basenames[i], locale); 304 for (int j = 0; j < filenames.size(); j++) { 305 String filename = (String ) filenames.get(j); 306 PropertiesHolder propHolder = getProperties(filename); 307 MessageFormat result = propHolder.getMessageFormat(code, locale); 308 if (result != null) { 309 return result; 310 } 311 } 312 } 313 } 314 return null; 315 } 316 317 318 326 protected PropertiesHolder getMergedProperties(Locale locale) { 327 synchronized (this.cachedMergedProperties) { 328 PropertiesHolder mergedHolder = (PropertiesHolder) this.cachedMergedProperties.get(locale); 329 if (mergedHolder != null) { 330 return mergedHolder; 331 } 332 Properties mergedProps = new Properties (); 333 mergedHolder = new PropertiesHolder(mergedProps, -1); 334 for (int i = this.basenames.length - 1; i >= 0; i--) { 335 List filenames = calculateAllFilenames(this.basenames[i], locale); 336 for (int j = filenames.size() - 1; j >= 0; j--) { 337 String filename = (String ) filenames.get(j); 338 PropertiesHolder propHolder = getProperties(filename); 339 if (propHolder.getProperties() != null) { 340 mergedProps.putAll(propHolder.getProperties()); 341 } 342 } 343 } 344 this.cachedMergedProperties.put(locale, mergedHolder); 345 return mergedHolder; 346 } 347 } 348 349 359 protected List calculateAllFilenames(String basename, Locale locale) { 360 synchronized (this.cachedFilenames) { 361 Map localeMap = (Map ) this.cachedFilenames.get(basename); 362 if (localeMap != null) { 363 List filenames = (List ) localeMap.get(locale); 364 if (filenames != null) { 365 return filenames; 366 } 367 } 368 List filenames = new ArrayList (7); 369 filenames.addAll(calculateFilenamesForLocale(basename, locale)); 370 if (this.fallbackToSystemLocale && !locale.equals(Locale.getDefault())) { 371 List fallbackFilenames = calculateFilenamesForLocale(basename, Locale.getDefault()); 372 for (Iterator it = fallbackFilenames.iterator(); it.hasNext();) { 373 String fallbackFilename = (String ) it.next(); 374 if (!filenames.contains(fallbackFilename)) { 375 filenames.add(fallbackFilename); 377 } 378 } 379 } 380 filenames.add(basename); 381 if (localeMap != null) { 382 localeMap.put(locale, filenames); 383 } 384 else { 385 localeMap = new HashMap (); 386 localeMap.put(locale, filenames); 387 this.cachedFilenames.put(basename, localeMap); 388 } 389 return filenames; 390 } 391 } 392 393 402 protected List calculateFilenamesForLocale(String basename, Locale locale) { 403 List result = new ArrayList (3); 404 String language = locale.getLanguage(); 405 String country = locale.getCountry(); 406 String variant = locale.getVariant(); 407 StringBuffer temp = new StringBuffer (basename); 408 409 if (language.length() > 0) { 410 temp.append('_').append(language); 411 result.add(0, temp.toString()); 412 } 413 414 if (country.length() > 0) { 415 temp.append('_').append(country); 416 result.add(0, temp.toString()); 417 } 418 419 if (variant.length() > 0) { 420 temp.append('_').append(variant); 421 result.add(0, temp.toString()); 422 } 423 424 return result; 425 } 426 427 428 434 protected PropertiesHolder getProperties(String filename) { 435 synchronized (this.cachedProperties) { 436 PropertiesHolder propHolder = (PropertiesHolder) this.cachedProperties.get(filename); 437 if (propHolder != null && 438 (propHolder.getRefreshTimestamp() < 0 || 439 propHolder.getRefreshTimestamp() > System.currentTimeMillis() - this.cacheMillis)) { 440 return propHolder; 442 } 443 return refreshProperties(filename, propHolder); 444 } 445 } 446 447 454 protected PropertiesHolder refreshProperties(String filename, PropertiesHolder propHolder) { 455 long refreshTimestamp = (this.cacheMillis < 0) ? -1 : System.currentTimeMillis(); 456 457 Resource resource = this.resourceLoader.getResource(filename + PROPERTIES_SUFFIX); 458 if (!resource.exists()) { 459 resource = this.resourceLoader.getResource(filename + XML_SUFFIX); 460 } 461 462 if (resource.exists()) { 463 try { 464 long fileTimestamp = -1; 465 466 if (this.cacheMillis >= 0) { 467 File file = null; 469 try { 470 file = resource.getFile(); 471 } 472 catch (IOException ex) { 473 if (logger.isDebugEnabled()) { 475 logger.debug( 476 resource + " could not be resolved in the file system - assuming that is hasn't changed", ex); 477 } 478 file = null; 479 } 480 if (file != null) { 481 fileTimestamp = file.lastModified(); 482 if (fileTimestamp == 0) { 483 throw new IOException ("File [" + file.getAbsolutePath() + "] does not exist"); 484 } 485 if (propHolder != null && propHolder.getFileTimestamp() == fileTimestamp) { 486 if (logger.isDebugEnabled()) { 487 logger.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified"); 488 } 489 propHolder.setRefreshTimestamp(refreshTimestamp); 490 return propHolder; 491 } 492 } 493 } 494 495 Properties props = loadProperties(resource, filename); 496 propHolder = new PropertiesHolder(props, fileTimestamp); 497 } 498 499 catch (IOException ex) { 500 if (logger.isWarnEnabled()) { 501 logger.warn( 502 "Could not parse properties file [" + resource.getFilename() + "]: " + ex.getMessage(), ex); 503 } 504 propHolder = new PropertiesHolder(); 506 } 507 } 508 509 else { 510 if (logger.isDebugEnabled()) { 512 logger.debug("No properties file found for [" + filename + "] - neither plain properties nor XML"); 513 } 514 propHolder = new PropertiesHolder(); 516 } 517 518 propHolder.setRefreshTimestamp(refreshTimestamp); 519 this.cachedProperties.put(filename, propHolder); 520 return propHolder; 521 } 522 523 530 protected Properties loadProperties(Resource resource, String filename) throws IOException { 531 InputStream is = resource.getInputStream(); 532 Properties props = new Properties (); 533 try { 534 if (resource.getFilename().endsWith(XML_SUFFIX)) { 535 if (logger.isDebugEnabled()) { 536 logger.debug("Loading properties [" + resource.getFilename() + "]"); 537 } 538 this.propertiesPersister.loadFromXml(props, is); 539 } 540 else { 541 String encoding = null; 542 if (this.fileEncodings != null) { 543 encoding = this.fileEncodings.getProperty(filename); 544 } 545 if (encoding == null) { 546 encoding = this.defaultEncoding; 547 } 548 if (encoding != null) { 549 if (logger.isDebugEnabled()) { 550 logger.debug("Loading properties [" + resource.getFilename() + "] with encoding '" + encoding + "'"); 551 } 552 this.propertiesPersister.load(props, new InputStreamReader (is, encoding)); 553 } 554 else { 555 if (logger.isDebugEnabled()) { 556 logger.debug("Loading properties [" + resource.getFilename() + "]"); 557 } 558 this.propertiesPersister.load(props, is); 559 } 560 } 561 return props; 562 } 563 finally { 564 is.close(); 565 } 566 } 567 568 569 573 public void clearCache() { 574 logger.debug("Clearing entire resource bundle cache"); 575 synchronized (this.cachedProperties) { 576 this.cachedProperties.clear(); 577 } 578 synchronized (this.cachedMergedProperties) { 579 this.cachedMergedProperties.clear(); 580 } 581 } 582 583 587 public void clearCacheIncludingAncestors() { 588 clearCache(); 589 if (getParentMessageSource() instanceof ReloadableResourceBundleMessageSource) { 590 ((ReloadableResourceBundleMessageSource) getParentMessageSource()).clearCacheIncludingAncestors(); 591 } 592 } 593 594 595 public String toString() { 596 return getClass().getName() + ": basenames=[" + StringUtils.arrayToCommaDelimitedString(this.basenames) + "]"; 597 } 598 599 600 606 protected class PropertiesHolder { 607 608 private Properties properties; 609 610 private long fileTimestamp = -1; 611 612 private long refreshTimestamp = -1; 613 614 615 private final Map cachedMessageFormats = new HashMap (); 616 617 public PropertiesHolder(Properties properties, long fileTimestamp) { 618 this.properties = properties; 619 this.fileTimestamp = fileTimestamp; 620 } 621 622 public PropertiesHolder() { 623 } 624 625 public Properties getProperties() { 626 return properties; 627 } 628 629 public long getFileTimestamp() { 630 return fileTimestamp; 631 } 632 633 public void setRefreshTimestamp(long refreshTimestamp) { 634 this.refreshTimestamp = refreshTimestamp; 635 } 636 637 public long getRefreshTimestamp() { 638 return refreshTimestamp; 639 } 640 641 public String getProperty(String code) { 642 if (this.properties == null) { 643 return null; 644 } 645 return this.properties.getProperty(code); 646 } 647 648 public MessageFormat getMessageFormat(String code, Locale locale) { 649 if (this.properties == null) { 650 return null; 651 } 652 synchronized (this.cachedMessageFormats) { 653 Map localeMap = (Map ) this.cachedMessageFormats.get(code); 654 if (localeMap != null) { 655 MessageFormat result = (MessageFormat ) localeMap.get(locale); 656 if (result != null) { 657 return result; 658 } 659 } 660 String msg = this.properties.getProperty(code); 661 if (msg != null) { 662 if (localeMap == null) { 663 localeMap = new HashMap (); 664 this.cachedMessageFormats.put(code, localeMap); 665 } 666 MessageFormat result = createMessageFormat(msg, locale); 667 localeMap.put(locale, result); 668 return result; 669 } 670 return null; 671 } 672 } 673 } 674 675 } 676 | Popular Tags |