1. /*
  2. * Copyright 2002-2004 the original author or authors.
  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.springframework.web.servlet.view;
  17. import java.util.HashMap;
  18. import java.util.Iterator;
  19. import java.util.Locale;
  20. import java.util.Map;
  21. import java.util.MissingResourceException;
  22. import java.util.ResourceBundle;
  23. import org.springframework.beans.BeansException;
  24. import org.springframework.beans.factory.BeanFactory;
  25. import org.springframework.beans.factory.DisposableBean;
  26. import org.springframework.beans.factory.NoSuchBeanDefinitionException;
  27. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  28. import org.springframework.beans.factory.support.DefaultListableBeanFactory;
  29. import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
  30. import org.springframework.core.Ordered;
  31. import org.springframework.core.io.Resource;
  32. import org.springframework.core.io.ResourceEditor;
  33. import org.springframework.web.servlet.View;
  34. /**
  35. * Implementation of ViewResolver that uses bean definitions in a
  36. * ResourceBundle, specified by the bundle basename. The bundle is
  37. * typically defined in a properties file, located in the class path.
  38. * The default bundle basename is "views".
  39. *
  40. * <p>This ViewResolver supports localized view definitions,
  41. * using the default support of java.util.PropertyResourceBundle.
  42. *
  43. * <p>Note: This ViewResolver implements the Ordered interface to allow for
  44. * flexible participation in ViewResolver chaining. For example, some special
  45. * views could be defined via this ViewResolver (giving it 0 as "order" value),
  46. * while all remaining views could be resolved by a UrlBasedViewResolver.
  47. *
  48. * @author Rod Johnson
  49. * @author Juergen Hoeller
  50. * @see java.util.ResourceBundle#getBundle
  51. * @see java.util.PropertyResourceBundle
  52. * @see UrlBasedViewResolver
  53. */
  54. public class ResourceBundleViewResolver extends AbstractCachingViewResolver implements Ordered, DisposableBean {
  55. /** Default if no other basename is supplied */
  56. public final static String DEFAULT_BASENAME = "views";
  57. private int order = Integer.MAX_VALUE; // default: same as non-Ordered
  58. private String[] basenames = new String[] {DEFAULT_BASENAME};
  59. private String defaultParentView;
  60. /** Locale -> BeanFactory */
  61. private final Map cachedFactories = new HashMap();
  62. public void setOrder(int order) {
  63. this.order = order;
  64. }
  65. public int getOrder() {
  66. return order;
  67. }
  68. /**
  69. * Set the basename, as defined in the java.util.ResourceBundle documentation.
  70. * ResourceBundle supports different suffixes. For example, a base name of
  71. * "views" might map to ResourceBundle files "views", "views_en_au" and "views_de".
  72. * <p>The default is "views".
  73. * @param basename the ResourceBundle basename
  74. * @see #setBasenames
  75. * @see java.util.ResourceBundle
  76. */
  77. public void setBasename(String basename) {
  78. setBasenames(new String[] {basename});
  79. }
  80. /**
  81. * Set multiple ResourceBundle basenames.
  82. * @param basenames multiple ResourceBundle basenames
  83. * @see #setBasename
  84. */
  85. public void setBasenames(String[] basenames) {
  86. this.basenames = basenames;
  87. }
  88. /**
  89. * Set the default parent for views defined in the ResourceBundle.
  90. * This avoids repeated "yyy1.parent=xxx", "yyy2.parent=xxx" definitions
  91. * in the bundle, especially if all defined views share the same parent.
  92. * The parent will typically define the view class and common attributes.
  93. * Concrete views might simply consist of an URL definition then:
  94. * a la "yyy1.url=/my.jsp", "yyy2.url=/your.jsp".
  95. * @param defaultParentView the default parent view
  96. */
  97. public void setDefaultParentView(String defaultParentView) {
  98. this.defaultParentView = defaultParentView;
  99. }
  100. protected View loadView(String viewName, Locale locale) throws MissingResourceException, BeansException {
  101. try {
  102. return (View) initFactory(locale).getBean(viewName, View.class);
  103. }
  104. catch (NoSuchBeanDefinitionException ex) {
  105. // to allow for ViewResolver chaining
  106. return null;
  107. }
  108. }
  109. /**
  110. * Initialize the BeanFactory from the ResourceBundle, for the given locale.
  111. * Synchronized because of access by parallel threads.
  112. */
  113. protected synchronized BeanFactory initFactory(Locale locale) throws MissingResourceException, BeansException {
  114. BeanFactory parsedBundle = isCache() ? (BeanFactory) this.cachedFactories.get(locale) : null;
  115. if (parsedBundle != null) {
  116. return parsedBundle;
  117. }
  118. DefaultListableBeanFactory factory = new DefaultListableBeanFactory(getApplicationContext());
  119. PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(factory);
  120. reader.setDefaultParentBean(this.defaultParentView);
  121. for (int i = 0; i < this.basenames.length; i++) {
  122. ResourceBundle bundle = ResourceBundle.getBundle(this.basenames[i], locale,
  123. Thread.currentThread().getContextClassLoader());
  124. reader.registerBeanDefinitions(bundle);
  125. }
  126. factory.registerCustomEditor(Resource.class, new ResourceEditor(getApplicationContext()));
  127. if (isCache()) {
  128. factory.preInstantiateSingletons();
  129. this.cachedFactories.put(locale, factory);
  130. }
  131. return factory;
  132. }
  133. public void destroy() throws BeansException {
  134. for (Iterator it = this.cachedFactories.values().iterator(); it.hasNext();) {
  135. ConfigurableBeanFactory factory = (ConfigurableBeanFactory) it.next();
  136. factory.destroySingletons();
  137. }
  138. this.cachedFactories.clear();
  139. }
  140. }