View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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.kuali.rice.krad.messages.providers;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.krad.messages.Message;
22  import org.kuali.rice.krad.messages.MessageProvider;
23  import org.kuali.rice.krad.messages.MessageService;
24  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
25  import org.kuali.rice.krad.service.ModuleService;
26  
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.Enumeration;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Map;
35  import java.util.ResourceBundle;
36  
37  /**
38   * Implementation of {@link MessageProvider} that stores messages in resource files
39   *
40   * @author Kuali Rice Team (rice.collab@kuali.org)
41   */
42  public class ResourceMessageProvider implements MessageProvider {
43  
44      private static final String COMPONENT_PLACEHOLDER_BEGIN = "@cmp{";
45      private static final String COMPONENT_PLACEHOLDER_END = "}";
46  
47      protected Map<String, List<ResourceBundle>> cachedResourceBundles;
48  
49      public ResourceMessageProvider() {
50          cachedResourceBundles = new HashMap<String, List<ResourceBundle>>();
51      }
52  
53      /**
54       * Iterates through the resource bundles for the give namespace (or the application if namespace is not given)
55       * and finds the message that matches the given key
56       *
57       * <p>
58       * If the message is found in more than one bundle, the text from the bundle that is loaded last will be used
59       * </p>
60       *
61       * <p>
62       * If the given component code is the default component, resource keys that do not have a component defined
63       * and match the given key will also be considered matches
64       * </p>
65       *
66       * @see org.kuali.rice.krad.messages.MessageProvider#getMessage(java.lang.String, java.lang.String,
67       *      java.lang.String, java.lang.String)
68       */
69      public Message getMessage(String namespace, String component, String key, String locale) {
70          Message message = null;
71  
72          List<ResourceBundle> bundles = getCachedResourceBundles(namespace, locale);
73  
74          // iterate through bundles and find message text, if more than one bundle contains the message
75          // the last one iterated over will be used
76          String messageText = null;
77          for (ResourceBundle bundle : bundles) {
78              // check for key with component first
79              String resourceKey = COMPONENT_PLACEHOLDER_BEGIN + component + COMPONENT_PLACEHOLDER_END + key;
80              if (bundle.containsKey(resourceKey)) {
81                  messageText = bundle.getString(resourceKey);
82              }
83              // if component is default then check for key without component code
84              else if (MessageService.DEFAULT_COMPONENT_CODE.equals(component) && bundle.containsKey(key)) {
85                  messageText = bundle.getString(key);
86              }
87          }
88  
89          // if message text was found build message object
90          if (StringUtils.isNotBlank(messageText)) {
91              message = buildMessage(namespace, component, key, messageText, locale);
92          }
93  
94          return message;
95      }
96  
97      /**
98       * Iterates through the resource bundles for the give namespace (or the application if namespace is not given)
99       * and finds all messages that match the given namespace
100      *
101      * <p>
102      * If the same resource key is found in more than one bundle, the text from the bundle that is
103      * loaded last will be used
104      * </p>
105      *
106      * <p>
107      * If the given component code is the default component, resource keys that do not have a component defined
108      * and match the given key will also be considered matches
109      * </p>
110      *
111      * @see org.kuali.rice.krad.messages.MessageProvider#getAllMessagesForComponent(java.lang.String, java.lang.String,
112      *      java.lang.String)
113      */
114     public Collection<Message> getAllMessagesForComponent(String namespace, String component, String locale) {
115         List<ResourceBundle> bundles = getCachedResourceBundles(namespace, locale);
116 
117         Map<String, Message> messagesByKey = new HashMap<String, Message>();
118         for (ResourceBundle bundle : bundles) {
119             Enumeration<String> resourceKeys = bundle.getKeys();
120             while (resourceKeys.hasMoreElements()) {
121                 String resourceKey = resourceKeys.nextElement();
122 
123                 boolean match = false;
124                 if (StringUtils.contains(resourceKey,
125                         COMPONENT_PLACEHOLDER_BEGIN + component + COMPONENT_PLACEHOLDER_END)) {
126                     match = true;
127                 } else if (MessageService.DEFAULT_COMPONENT_CODE.equals(component) && !StringUtils.contains(resourceKey,
128                         COMPONENT_PLACEHOLDER_BEGIN)) {
129                     match = true;
130                 }
131 
132                 if (match) {
133                     String messageText = bundle.getString(resourceKey);
134 
135                     resourceKey = cleanResourceKey(resourceKey);
136                     Message message = buildMessage(namespace, component, resourceKey, messageText, locale);
137                     messagesByKey.put(resourceKey, message);
138                 }
139             }
140         }
141 
142         return messagesByKey.values();
143     }
144 
145     /**
146      * Removes any component declaration within the given resource key
147      *
148      * @param resourceKey resource key to clean
149      * @return String cleaned resource key
150      */
151     protected String cleanResourceKey(String resourceKey) {
152         String cleanedKey = resourceKey;
153 
154         String component = StringUtils.substringBetween(cleanedKey, COMPONENT_PLACEHOLDER_BEGIN,
155                 COMPONENT_PLACEHOLDER_END);
156         if (StringUtils.isNotBlank(component)) {
157             cleanedKey = StringUtils.remove(cleanedKey,
158                     COMPONENT_PLACEHOLDER_BEGIN + component + COMPONENT_PLACEHOLDER_END);
159         }
160 
161         return cleanedKey;
162     }
163 
164     /**
165      * Helper method to build a {@link Message} object from the given parameters
166      *
167      * @param namespace namespace for the message
168      * @param component component code for the message
169      * @param key message key
170      * @param messageText text for the message
171      * @param locale locale of the message
172      * @return Message instance populated with parameters
173      */
174     protected Message buildMessage(String namespace, String component, String key, String messageText, String locale) {
175         Message message = new Message();
176 
177         message.setNamespaceCode(namespace);
178         message.setComponentCode(component);
179 
180         key = cleanResourceKey(key);
181         message.setKey(key);
182 
183         message.setText(messageText);
184         message.setLocale(locale);
185 
186         return message;
187     }
188 
189     /**
190      * Retrieves the list of resource bundles for the given namespace or locale from cache if present, otherwise
191      * the list is retrieved and then stored in cache for subsequent calls
192      *
193      * @param namespace namespace to retrieve bundles for
194      * @param localeCode locale code to use in selecting bundles
195      * @return List<ResourceBundle> list of resource bundles for the namespace or empty list if none were found
196      */
197     protected List<ResourceBundle> getCachedResourceBundles(String namespace, String localeCode) {
198         if (StringUtils.isBlank(namespace)) {
199             namespace = MessageService.DEFAULT_NAMESPACE_CODE;
200         }
201 
202         String cacheKey = namespace + "|" + localeCode;
203         if (cachedResourceBundles.containsKey(cacheKey)) {
204             return cachedResourceBundles.get(cacheKey);
205         }
206 
207         List<ResourceBundle> bundles = null;
208         if (StringUtils.isBlank(namespace) || MessageService.DEFAULT_NAMESPACE_CODE.equals(namespace)) {
209             bundles = getResourceBundlesForApplication(localeCode);
210         } else {
211             bundles = getResourceBundlesForNamespace(namespace, localeCode);
212         }
213 
214         cachedResourceBundles.put(cacheKey, bundles);
215 
216         return bundles;
217     }
218 
219     /**
220      * Retrieves the configured {@link ResourceBundle} instances for the given namespace and locale
221      *
222      * @param namespace namespace to retrieve bundles for
223      * @param localeCode locale code to use in selecting bundles
224      * @return List<ResourceBundle> list of resource bundles for the namespace or empty list if none were found
225      */
226     protected List<ResourceBundle> getResourceBundlesForNamespace(String namespace, String localeCode) {
227         List<String> resourceBundleNames = getResourceBundleNamesForNamespace(namespace);
228 
229         return getResourceBundles(resourceBundleNames, localeCode);
230     }
231 
232     /**
233      * Retrieves the configured {@link ResourceBundle} instances for the application using the given locale code
234      *
235      * @param localeCode locale code to use in selecting bundles
236      * @return List<ResourceBundle> list of resource bundles for the application or empty list if none were found
237      */
238     protected List<ResourceBundle> getResourceBundlesForApplication(String localeCode) {
239         List<String> resourceBundleNames = getResourceBundleNamesForApplication();
240 
241         return getResourceBundles(resourceBundleNames, localeCode);
242     }
243 
244     /**
245      * Helper method to build a list of resource bundles for the given list of bundle names and locale code
246      *
247      * <p>
248      * For details on how resource bundles are selected given a bundle name and locale code see
249      * {@link ResourceBundle}
250      * </p>
251      *
252      * @param resourceBundleNames list of bundle names to get bundles for
253      * @param localeCode locale code to use when selecting bundles
254      * @return List<ResourceBundle> list of resource bundles (one for each bundle name if found)
255      */
256     protected List<ResourceBundle> getResourceBundles(List<String> resourceBundleNames, String localeCode) {
257         List<ResourceBundle> resourceBundles = new ArrayList<ResourceBundle>();
258 
259         String[] localeIdentifiers = StringUtils.split(localeCode, "-");
260         if ((localeIdentifiers == null) || (localeIdentifiers.length != 2)) {
261             throw new RiceRuntimeException("Invalid locale code: " + (localeCode == null ? "Null" : localeCode));
262         }
263 
264         Locale locale = new Locale(localeIdentifiers[0], localeIdentifiers[1]);
265 
266         if (resourceBundleNames != null) {
267             for (String bundleName : resourceBundleNames) {
268                 ResourceBundle bundle = ResourceBundle.getBundle(bundleName, locale);
269                 if (bundle != null) {
270                     resourceBundles.add(bundle);
271                 }
272             }
273         }
274 
275         return resourceBundles;
276     }
277 
278     /**
279      * Retrieves the list of configured bundle names for the namespace
280      *
281      * <p>
282      * Resource bundle names are configured for a namespace using the property <code>resourceBundleNames</code>
283      * on the corresponding {@link org.kuali.rice.krad.bo.ModuleConfiguration}
284      * </p>
285      *
286      * @param namespace namespace to retrieve configured bundle names for
287      * @return List<String> list of bundle names or null if module was not found for given namespace
288      */
289     protected List<String> getResourceBundleNamesForNamespace(String namespace) {
290         ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getModuleServiceByNamespaceCode(
291                 namespace);
292         if (moduleService != null) {
293             return moduleService.getModuleConfiguration().getResourceBundleNames();
294         }
295 
296         return null;
297     }
298 
299     /**
300      * Retrieves the list of configured bundle names for the application
301      *
302      * <p>
303      * Resource bundle names are configured for the application using the configuration property
304      * <code>resourceBundleNames</code>
305      * </p>
306      *
307      * @return List<String> list of bundle names configured for the application
308      */
309     protected List<String> getResourceBundleNamesForApplication() {
310         String resourceBundleNamesConfig = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
311                 "resourceBundleNames");
312         if (StringUtils.isNotBlank(resourceBundleNamesConfig)) {
313             String[] resourceBundleNames = StringUtils.split(resourceBundleNamesConfig, ",");
314 
315             return Arrays.asList(resourceBundleNames);
316         }
317 
318         return null;
319     }
320 }