001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.services.impl;
016
017 import java.io.BufferedInputStream;
018 import java.io.IOException;
019 import java.io.InputStream;
020 import java.net.URL;
021 import java.util.ArrayList;
022 import java.util.Collections;
023 import java.util.HashMap;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Locale;
027 import java.util.Map;
028 import java.util.Properties;
029
030 import org.apache.hivemind.ApplicationRuntimeException;
031 import org.apache.hivemind.Messages;
032 import org.apache.hivemind.Resource;
033 import org.apache.hivemind.util.Defense;
034 import org.apache.hivemind.util.LocalizedNameGenerator;
035 import org.apache.tapestry.IComponent;
036 import org.apache.tapestry.INamespace;
037 import org.apache.tapestry.event.ResetEventListener;
038 import org.apache.tapestry.services.ComponentMessagesSource;
039 import org.apache.tapestry.services.ComponentPropertySource;
040 import org.apache.tapestry.util.text.LocalizedProperties;
041
042 /**
043 * Service used to access localized properties for a component.
044 *
045 * @author Howard Lewis Ship
046 * @since 2.0.4
047 */
048
049 public class ComponentMessagesSourceImpl implements ComponentMessagesSource, ResetEventListener
050 {
051 private Properties _emptyProperties = new Properties();
052
053 private static final String SUFFIX = ".properties";
054
055 /**
056 * The name of the component/application/etc property that will be used to determine the
057 * encoding to use when loading the messages
058 */
059
060 public static final String MESSAGES_ENCODING_PROPERTY_NAME = "org.apache.tapestry.messages-encoding";
061
062 /**
063 * Map of Maps. The outer map is keyed on component specification location (a{@link Resource}.
064 * This inner map is keyed on locale and the value is a {@link Properties}.
065 */
066
067 private Map _componentCache = new HashMap();
068
069 private ComponentPropertySource _componentPropertySource;
070
071 /**
072 * Returns an instance of {@link Properties}containing the properly localized messages for the
073 * component, in the {@link Locale}identified by the component's containing page.
074 */
075
076 protected synchronized Properties getLocalizedProperties(IComponent component)
077 {
078 Defense.notNull(component, "component");
079
080 Resource specificationLocation = component.getSpecification().getSpecificationLocation();
081 Locale locale = component.getPage().getLocale();
082
083 Map propertiesMap = findPropertiesMapForResource(specificationLocation);
084
085 Properties result = (Properties) propertiesMap.get(locale);
086
087 if (result == null)
088 {
089
090 // Not found, create it now.
091
092 result = assembleComponentProperties(
093 component,
094 specificationLocation,
095 propertiesMap,
096 locale);
097
098 propertiesMap.put(locale, result);
099 }
100
101 return result;
102 }
103
104 private Map findPropertiesMapForResource(Resource resource)
105 {
106 Map result = (Map) _componentCache.get(resource);
107
108 if (result == null)
109 {
110 result = new HashMap();
111 _componentCache.put(resource, result);
112 }
113
114 return result;
115 }
116
117 private Properties getNamespaceProperties(IComponent component, Locale locale)
118 {
119 INamespace namespace = component.getNamespace();
120
121 Resource namespaceLocation = namespace.getSpecificationLocation();
122
123 Map propertiesMap = findPropertiesMapForResource(namespaceLocation);
124
125 Properties result = (Properties) propertiesMap.get(locale);
126
127 if (result == null)
128 {
129 result = assembleNamespaceProperties(namespace, propertiesMap, locale);
130
131 propertiesMap.put(locale, result);
132 }
133
134 return result;
135 }
136
137 private Properties assembleComponentProperties(IComponent component,
138 Resource baseResourceLocation, Map propertiesMap, Locale locale)
139 {
140 List localizations = findLocalizationsForResource(baseResourceLocation, locale);
141
142 Properties parent = null;
143 Properties assembledProperties = null;
144
145 Iterator i = localizations.iterator();
146
147 while (i.hasNext())
148 {
149 ResourceLocalization rl = (ResourceLocalization) i.next();
150
151 Locale l = rl.getLocale();
152
153 // Retrieve namespace properties for current locale (and parent locales)
154 Properties namespaceProperties = getNamespaceProperties(component, l);
155
156 // Use the namespace properties as default for assembled properties
157 assembledProperties = new Properties(namespaceProperties);
158
159 // Read localized properties for component
160 Properties properties = readComponentProperties(component, l, rl.getResource(), null);
161
162 // Override parent properties with current locale
163 if (parent != null) {
164 if (properties != null)
165 parent.putAll(properties);
166 }
167 else
168 parent = properties;
169
170 // Add to assembled properties
171 if (parent != null)
172 assembledProperties.putAll(parent);
173
174 // Save result in cache
175 propertiesMap.put(l, assembledProperties);
176 }
177
178 return assembledProperties;
179 }
180
181 private Properties assembleNamespaceProperties(INamespace namespace, Map propertiesMap,
182 Locale locale)
183 {
184 List localizations = findLocalizationsForResource(
185 namespace.getSpecificationLocation(),
186 locale);
187
188 // Build them back up in reverse order.
189
190 Properties parent = _emptyProperties;
191
192 Iterator i = localizations.iterator();
193
194 while (i.hasNext())
195 {
196 ResourceLocalization rl = (ResourceLocalization) i.next();
197
198 Locale l = rl.getLocale();
199
200 Properties properties = (Properties) propertiesMap.get(l);
201
202 if (properties == null)
203 {
204 properties = readNamespaceProperties(namespace, l, rl.getResource(), parent);
205
206 propertiesMap.put(l, properties);
207 }
208
209 parent = properties;
210 }
211
212 return parent;
213
214 }
215
216 /**
217 * Finds the localizations of the provided resource. Returns a List of
218 * {@link ResourceLocalization}(each pairing a locale with a localized resource). The list is
219 * ordered from most general (i.e., "foo.properties") to most specific (i.e.,
220 * "foo_en_US_yokel.properties").
221 */
222
223 private List findLocalizationsForResource(Resource resource, Locale locale)
224 {
225 List result = new ArrayList();
226
227 String baseName = extractBaseName(resource);
228
229 LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, SUFFIX);
230
231 while (g.more())
232 {
233 String localizedName = g.next();
234 Locale l = g.getCurrentLocale();
235 Resource localizedResource = resource.getRelativeResource(localizedName);
236
237 result.add(new ResourceLocalization(l, localizedResource));
238 }
239
240 Collections.reverse(result);
241
242 return result;
243 }
244
245 private String extractBaseName(Resource baseResourceLocation)
246 {
247 String fileName = baseResourceLocation.getName();
248 int dotx = fileName.lastIndexOf('.');
249
250 return fileName.substring(0, dotx);
251 }
252
253 private Properties readComponentProperties(IComponent component, Locale locale,
254 Resource propertiesResource, Properties parent)
255 {
256 String encoding = getComponentMessagesEncoding(component, locale);
257
258 return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
259 }
260
261 private Properties readNamespaceProperties(INamespace namespace, Locale locale,
262 Resource propertiesResource, Properties parent)
263 {
264 String encoding = getNamespaceMessagesEncoding(namespace, locale);
265
266 return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent);
267 }
268
269 private Properties readPropertiesResource(URL resourceURL, String encoding, Properties parent)
270 {
271 if (resourceURL == null)
272 return parent;
273
274 Properties result = new Properties(parent);
275
276 LocalizedProperties wrapper = new LocalizedProperties(result);
277
278 InputStream input = null;
279
280 try
281 {
282 input = new BufferedInputStream(resourceURL.openStream());
283
284 if (encoding == null)
285 wrapper.load(input);
286 else
287 wrapper.load(input, encoding);
288
289 input.close();
290 }
291 catch (IOException ex)
292 {
293 throw new ApplicationRuntimeException(ImplMessages.unableToLoadProperties(
294 resourceURL,
295 ex), ex);
296 }
297 finally
298 {
299 close(input);
300 }
301
302 return result;
303 }
304
305 private void close(InputStream is)
306 {
307 if (is != null)
308 try
309 {
310 is.close();
311 }
312 catch (IOException ex)
313 {
314 // Ignore.
315 }
316 }
317
318 /**
319 * Clears the cache of read properties files.
320 */
321
322 public synchronized void resetEventDidOccur()
323 {
324 _componentCache.clear();
325 }
326
327 public Messages getMessages(IComponent component)
328 {
329 return new ComponentMessages(component.getPage().getLocale(),
330 getLocalizedProperties(component));
331 }
332
333 private String getComponentMessagesEncoding(IComponent component, Locale locale)
334 {
335 String encoding = _componentPropertySource.getLocalizedComponentProperty(
336 component,
337 locale,
338 MESSAGES_ENCODING_PROPERTY_NAME);
339
340 if (encoding == null)
341 encoding = _componentPropertySource.getLocalizedComponentProperty(
342 component,
343 locale,
344 TemplateSourceImpl.TEMPLATE_ENCODING_PROPERTY_NAME);
345
346 return encoding;
347 }
348
349 private String getNamespaceMessagesEncoding(INamespace namespace, Locale locale)
350 {
351 return _componentPropertySource.getLocalizedNamespaceProperty(
352 namespace,
353 locale,
354 MESSAGES_ENCODING_PROPERTY_NAME);
355 }
356
357 public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
358 {
359 _componentPropertySource = componentPropertySource;
360 }
361 }