1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
package org.apache.tapestry.services.impl; |
16 | |
|
17 | |
import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap; |
18 | |
import org.apache.commons.io.IOUtils; |
19 | |
import org.apache.hivemind.ApplicationRuntimeException; |
20 | |
import org.apache.hivemind.Messages; |
21 | |
import org.apache.hivemind.Resource; |
22 | |
import org.apache.hivemind.util.Defense; |
23 | |
import org.apache.hivemind.util.LocalizedNameGenerator; |
24 | |
import org.apache.tapestry.IComponent; |
25 | |
import org.apache.tapestry.INamespace; |
26 | |
import org.apache.tapestry.event.ResetEventListener; |
27 | |
import org.apache.tapestry.resolver.IComponentResourceResolver; |
28 | |
import org.apache.tapestry.services.ClasspathResourceFactory; |
29 | |
import org.apache.tapestry.services.ComponentMessagesSource; |
30 | |
import org.apache.tapestry.services.ComponentPropertySource; |
31 | |
import org.apache.tapestry.util.text.LocalizedProperties; |
32 | |
|
33 | |
import java.io.BufferedInputStream; |
34 | |
import java.io.IOException; |
35 | |
import java.io.InputStream; |
36 | |
import java.net.URL; |
37 | |
import java.util.*; |
38 | |
|
39 | |
|
40 | |
|
41 | |
|
42 | |
|
43 | |
|
44 | |
|
45 | |
|
46 | 0 | public class ComponentMessagesSourceImpl implements ComponentMessagesSource, ResetEventListener |
47 | |
{ |
48 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
public static final String MESSAGES_ENCODING_PROPERTY_NAME = "org.apache.tapestry.messages-encoding"; |
54 | |
|
55 | |
|
56 | |
|
57 | |
|
58 | |
|
59 | |
|
60 | |
public static final String NAMESPACE_PROPERTIES_NAME = "org.apache.tapestry.namespace-properties-name"; |
61 | |
|
62 | |
private static final String SUFFIX = ".properties"; |
63 | |
|
64 | 0 | private Properties _emptyProperties = new Properties(); |
65 | |
|
66 | |
|
67 | |
|
68 | |
|
69 | |
|
70 | |
|
71 | |
|
72 | 0 | private Map _componentCache = new ConcurrentHashMap(); |
73 | |
|
74 | |
private ComponentPropertySource _componentPropertySource; |
75 | |
|
76 | |
|
77 | |
|
78 | |
|
79 | |
private ClasspathResourceFactory _classpathResourceFactory; |
80 | |
|
81 | |
private IComponentResourceResolver _resourceResolver; |
82 | |
|
83 | |
|
84 | |
|
85 | |
|
86 | |
|
87 | |
|
88 | |
|
89 | |
|
90 | |
|
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
protected Properties getLocalizedProperties(IComponent component) |
96 | |
{ |
97 | 0 | Defense.notNull(component, "component"); |
98 | |
|
99 | 0 | Resource specificationLocation = component.getSpecification().getSpecificationLocation(); |
100 | |
|
101 | 0 | Locale locale = component.getPage().getLocale(); |
102 | |
|
103 | 0 | Map propertiesMap = findPropertiesMapForResource(specificationLocation); |
104 | |
|
105 | 0 | Properties result = (Properties) propertiesMap.get(locale); |
106 | |
|
107 | 0 | if (result == null) |
108 | |
{ |
109 | |
|
110 | |
|
111 | 0 | result = assembleComponentProperties(component, specificationLocation, |
112 | |
propertiesMap, locale); |
113 | |
|
114 | 0 | propertiesMap.put(locale, result); |
115 | |
} |
116 | |
|
117 | 0 | return result; |
118 | |
} |
119 | |
|
120 | |
private Map findPropertiesMapForResource(Resource resource) |
121 | |
{ |
122 | 0 | Map result = (Map) _componentCache.get(resource); |
123 | |
|
124 | 0 | if (result == null) |
125 | |
{ |
126 | 0 | result = new HashMap(); |
127 | |
|
128 | 0 | _componentCache.put(resource, result); |
129 | |
} |
130 | |
|
131 | 0 | return result; |
132 | |
} |
133 | |
|
134 | |
private Properties getNamespaceProperties(IComponent component, Locale locale) |
135 | |
{ |
136 | 0 | INamespace namespace = component.getNamespace(); |
137 | |
|
138 | 0 | Resource namespaceLocation = namespace.getSpecificationLocation(); |
139 | |
|
140 | 0 | Map propertiesMap = findPropertiesMapForResource(namespaceLocation); |
141 | |
|
142 | 0 | Properties result = (Properties) propertiesMap.get(locale); |
143 | |
|
144 | 0 | if (result == null) |
145 | |
{ |
146 | 0 | result = new Properties(); |
147 | |
|
148 | |
|
149 | |
|
150 | 0 | List spaceList = new ArrayList(); |
151 | 0 | spaceList.add(namespace); |
152 | |
|
153 | 0 | INamespace parent = namespace; |
154 | 0 | while (parent.getParentNamespace() != null) |
155 | |
{ |
156 | 0 | parent = parent.getParentNamespace(); |
157 | |
|
158 | 0 | spaceList.add(parent); |
159 | |
} |
160 | |
|
161 | |
|
162 | |
|
163 | 0 | for (int i=spaceList.size() - 1; i > -1; i--) |
164 | |
{ |
165 | 0 | INamespace space = (INamespace)spaceList.get(i); |
166 | |
|
167 | 0 | result.putAll(assembleNamespaceProperties(space, findPropertiesMapForResource(space.getSpecificationLocation()), locale)); |
168 | |
} |
169 | |
|
170 | 0 | propertiesMap.put(locale, result); |
171 | |
} |
172 | |
|
173 | 0 | return result; |
174 | |
} |
175 | |
|
176 | |
private Properties assembleComponentProperties(IComponent component, Resource baseResourceLocation, |
177 | |
Map propertiesMap, Locale locale) |
178 | |
{ |
179 | 0 | List localizations = findLocalizationsForResource(component, baseResourceLocation, locale, |
180 | |
component.getSpecification().getProperty(NAMESPACE_PROPERTIES_NAME)); |
181 | |
|
182 | 0 | Properties parent = null; |
183 | 0 | Properties assembledProperties = null; |
184 | |
|
185 | 0 | Iterator i = localizations.iterator(); |
186 | |
|
187 | 0 | while(i.hasNext()) |
188 | |
{ |
189 | 0 | ResourceLocalization rl = (ResourceLocalization) i.next(); |
190 | 0 | Locale l = rl.getLocale(); |
191 | |
|
192 | |
|
193 | |
|
194 | |
|
195 | 0 | Properties namespaceProperties = getNamespaceProperties(component, l); |
196 | |
|
197 | |
|
198 | |
|
199 | 0 | assembledProperties = new Properties(namespaceProperties); |
200 | |
|
201 | |
|
202 | |
|
203 | 0 | Properties properties = readComponentProperties(component, l, rl.getResource(), null); |
204 | |
|
205 | |
|
206 | |
|
207 | 0 | if (parent != null) |
208 | |
{ |
209 | 0 | if (properties != null) |
210 | 0 | parent.putAll(properties); |
211 | |
} |
212 | |
else |
213 | 0 | parent = properties; |
214 | |
|
215 | |
|
216 | 0 | if (parent != null) |
217 | 0 | assembledProperties.putAll(parent); |
218 | |
|
219 | |
|
220 | 0 | propertiesMap.put(l, assembledProperties); |
221 | 0 | } |
222 | |
|
223 | 0 | if (assembledProperties == null) |
224 | 0 | assembledProperties = new Properties(); |
225 | |
|
226 | 0 | return assembledProperties; |
227 | |
} |
228 | |
|
229 | |
private Properties assembleNamespaceProperties(INamespace namespace, Map propertiesMap, Locale locale) |
230 | |
{ |
231 | 0 | List localizations = findLocalizationsForResource(namespace.getSpecificationLocation(), locale, |
232 | |
namespace.getPropertyValue(NAMESPACE_PROPERTIES_NAME)); |
233 | |
|
234 | |
|
235 | |
|
236 | 0 | Properties parent = _emptyProperties; |
237 | |
|
238 | 0 | Iterator i = localizations.iterator(); |
239 | |
|
240 | 0 | while(i.hasNext()) |
241 | |
{ |
242 | 0 | ResourceLocalization rl = (ResourceLocalization) i.next(); |
243 | |
|
244 | 0 | Locale l = rl.getLocale(); |
245 | |
|
246 | 0 | Properties properties = (Properties) propertiesMap.get(l); |
247 | |
|
248 | 0 | if (properties == null) |
249 | |
{ |
250 | 0 | properties = readNamespaceProperties(namespace, l, rl.getResource(), parent); |
251 | |
|
252 | 0 | propertiesMap.put(l, properties); |
253 | |
} |
254 | |
|
255 | 0 | parent = properties; |
256 | 0 | } |
257 | |
|
258 | 0 | return parent; |
259 | |
|
260 | |
} |
261 | |
|
262 | |
|
263 | |
|
264 | |
|
265 | |
|
266 | |
|
267 | |
|
268 | |
|
269 | |
private List findLocalizationsForResource(Resource resource, Locale locale, String alternateName) |
270 | |
{ |
271 | 0 | List result = new ArrayList(); |
272 | |
|
273 | 0 | String baseName = null; |
274 | 0 | if (alternateName != null) { |
275 | |
|
276 | 0 | baseName = alternateName.replace('.', '/'); |
277 | |
} else { |
278 | |
|
279 | 0 | baseName = extractBaseName(resource); |
280 | |
} |
281 | |
|
282 | 0 | LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, SUFFIX); |
283 | |
|
284 | 0 | while(g.more()) |
285 | |
{ |
286 | 0 | String localizedName = g.next(); |
287 | 0 | Locale l = g.getCurrentLocale(); |
288 | |
|
289 | 0 | Resource localizedResource = resource.getRelativeResource(localizedName); |
290 | |
|
291 | 0 | if (localizedResource.getResourceURL() == null) |
292 | |
{ |
293 | 0 | localizedResource = _classpathResourceFactory.newResource(baseName + SUFFIX); |
294 | |
} |
295 | |
|
296 | 0 | result.add(new ResourceLocalization(l, localizedResource)); |
297 | 0 | } |
298 | |
|
299 | 0 | Collections.reverse(result); |
300 | |
|
301 | 0 | return result; |
302 | |
} |
303 | |
|
304 | |
private List findLocalizationsForResource(IComponent component, Resource resource, Locale locale, String alternateName) |
305 | |
{ |
306 | 0 | List result = new ArrayList(); |
307 | |
|
308 | 0 | String baseName = null; |
309 | 0 | if (alternateName != null) { |
310 | |
|
311 | 0 | baseName = alternateName.replace('.', '/'); |
312 | |
} else { |
313 | |
|
314 | 0 | baseName = extractBaseName(resource); |
315 | |
} |
316 | |
|
317 | 0 | LocalizedNameGenerator g = new LocalizedNameGenerator(baseName, locale, ""); |
318 | |
|
319 | 0 | while(g.more()) |
320 | |
{ |
321 | 0 | String localizedName = g.next(); |
322 | 0 | Locale l = g.getCurrentLocale(); |
323 | |
|
324 | 0 | Resource localizedResource = _resourceResolver.findComponentResource(component, null, localizedName, SUFFIX, null); |
325 | |
|
326 | 0 | if (localizedResource == null) |
327 | 0 | continue; |
328 | |
|
329 | 0 | result.add(new ResourceLocalization(l, localizedResource)); |
330 | 0 | } |
331 | |
|
332 | 0 | Collections.reverse(result); |
333 | |
|
334 | 0 | return result; |
335 | |
} |
336 | |
|
337 | |
private String extractBaseName(Resource baseResourceLocation) |
338 | |
{ |
339 | 0 | String fileName = baseResourceLocation.getName(); |
340 | 0 | int dotx = fileName.lastIndexOf('.'); |
341 | |
|
342 | 0 | return dotx > -1 ? fileName.substring(0, dotx) : fileName; |
343 | |
} |
344 | |
|
345 | |
private Properties readComponentProperties(IComponent component, |
346 | |
Locale locale, Resource propertiesResource, Properties parent) |
347 | |
{ |
348 | 0 | String encoding = getComponentMessagesEncoding(component, locale); |
349 | |
|
350 | 0 | return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent); |
351 | |
} |
352 | |
|
353 | |
private Properties readNamespaceProperties(INamespace namespace, |
354 | |
Locale locale, Resource propertiesResource, Properties parent) |
355 | |
{ |
356 | 0 | String encoding = getNamespaceMessagesEncoding(namespace, locale); |
357 | |
|
358 | 0 | return readPropertiesResource(propertiesResource.getResourceURL(), encoding, parent); |
359 | |
} |
360 | |
|
361 | |
private Properties readPropertiesResource(URL resourceURL, String encoding, Properties parent) |
362 | |
{ |
363 | 0 | if (resourceURL == null) |
364 | 0 | return parent; |
365 | |
|
366 | 0 | Properties result = new Properties(parent); |
367 | |
|
368 | 0 | LocalizedProperties wrapper = new LocalizedProperties(result); |
369 | |
|
370 | 0 | InputStream input = null; |
371 | |
|
372 | |
try |
373 | |
{ |
374 | 0 | input = new BufferedInputStream(resourceURL.openStream()); |
375 | |
|
376 | 0 | if (encoding == null) |
377 | 0 | wrapper.load(input); |
378 | |
else |
379 | 0 | wrapper.load(input, encoding); |
380 | |
|
381 | 0 | input.close(); |
382 | |
} |
383 | 0 | catch (IOException ex) |
384 | |
{ |
385 | 0 | throw new ApplicationRuntimeException(ImplMessages.unableToLoadProperties(resourceURL, ex), ex); |
386 | |
} |
387 | |
finally |
388 | |
{ |
389 | 0 | IOUtils.closeQuietly(input); |
390 | 0 | } |
391 | |
|
392 | 0 | return result; |
393 | |
} |
394 | |
|
395 | |
|
396 | |
|
397 | |
|
398 | |
|
399 | |
public void resetEventDidOccur() |
400 | |
{ |
401 | 0 | _componentCache.clear(); |
402 | 0 | } |
403 | |
|
404 | |
public Messages getMessages(IComponent component) |
405 | |
{ |
406 | 0 | return new ComponentMessages(component.getPage().getLocale(), |
407 | |
getLocalizedProperties(component)); |
408 | |
} |
409 | |
|
410 | |
private String getComponentMessagesEncoding(IComponent component, Locale locale) |
411 | |
{ |
412 | 0 | String encoding = _componentPropertySource.getLocalizedComponentProperty(component, locale, |
413 | |
MESSAGES_ENCODING_PROPERTY_NAME); |
414 | |
|
415 | 0 | if (encoding == null) |
416 | 0 | encoding = _componentPropertySource. |
417 | |
getLocalizedComponentProperty(component, locale, TemplateSourceImpl.TEMPLATE_ENCODING_PROPERTY_NAME); |
418 | |
|
419 | 0 | return encoding; |
420 | |
} |
421 | |
|
422 | |
private String getNamespaceMessagesEncoding(INamespace namespace, Locale locale) |
423 | |
{ |
424 | 0 | return _componentPropertySource. |
425 | |
getLocalizedNamespaceProperty(namespace, locale, MESSAGES_ENCODING_PROPERTY_NAME); |
426 | |
} |
427 | |
|
428 | |
public void setComponentPropertySource(ComponentPropertySource componentPropertySource) |
429 | |
{ |
430 | 0 | _componentPropertySource = componentPropertySource; |
431 | 0 | } |
432 | |
|
433 | |
public void setClasspathResourceFactory(ClasspathResourceFactory factory) |
434 | |
{ |
435 | 0 | _classpathResourceFactory = factory; |
436 | 0 | } |
437 | |
|
438 | |
public void setComponentResourceResolver(IComponentResourceResolver resourceResolver) |
439 | |
{ |
440 | 0 | _resourceResolver = resourceResolver; |
441 | 0 | } |
442 | |
} |