1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.web

File XWikiMessageTool.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
41% of files have more coverage

Code metrics

36
91
12
1
400
198
37
0.41
7.58
12
3.08

Classes

Class Line # Actions
XWikiMessageTool 64 91 0% 37 20
0.856115185.6%
 

Contributing tests

This file is covered by 28 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.web;
21   
22    import java.io.IOException;
23    import java.io.StringReader;
24    import java.text.MessageFormat;
25    import java.util.ArrayList;
26    import java.util.Arrays;
27    import java.util.Date;
28    import java.util.HashMap;
29    import java.util.HashSet;
30    import java.util.List;
31    import java.util.Map;
32    import java.util.Properties;
33    import java.util.ResourceBundle;
34    import java.util.Set;
35   
36    import org.apache.commons.lang3.ArrayUtils;
37    import org.slf4j.Logger;
38    import org.slf4j.LoggerFactory;
39    import org.xwiki.localization.ContextualLocalizationManager;
40   
41    import com.xpn.xwiki.XWikiContext;
42    import com.xpn.xwiki.XWikiException;
43    import com.xpn.xwiki.doc.XWikiDocument;
44   
45    /**
46    * Internationalization service based on key/property values. The key is the id of the message being looked for and the
47    * returned value is the message in the language requested. There are 3 sources where properties are looked for (in the
48    * specified order):
49    * <ol>
50    * <li>If there's a "documentBundles" property in the XWiki Preferences page then the XWiki documents listed there
51    * (separated by commas) are considered the source for properties</li>
52    * <li>If there's a "xwiki.documentBundles" property in the XWiki configuration file (xwiki.cfg) then the XWiki
53    * documents listed there (separated by commas) are considered for source for properties</li>
54    * <li>The Resource Bundle passed in the constructor</li>
55    * </ol>
56    * If the property is not found in any of these 3 sources then the key is returned in place of the value. In addition
57    * the property values are cached for better performance but if one of the XWiki documents containing the properties is
58    * modified, its content is cached again next time a key is asked.
59    *
60    * @version $Id: 214d1de32cc4756b93d6c13cfef6c790d35f9ffe $
61    * @deprecated since 4.3M2 use the {@link org.xwiki.localization.LocalizationManager} component instead
62    */
63    @Deprecated
 
64    public class XWikiMessageTool
65    {
66    /**
67    * Log4J logger object to log messages in this class.
68    */
69    private static final Logger LOGGER = LoggerFactory.getLogger(XWikiMessageTool.class);
70   
71    /**
72    * Property name used to defined internationalization document bundles in either XWikiProperties ("documentBundles")
73    * or in the xwiki.cfg configuration file ("xwiki.documentBundles").
74    */
75    private static final String KEY = "documentBundles";
76   
77    /**
78    * Format string for the error message used to log load failures.
79    */
80    private static final String LOAD_ERROR_MSG_FMT = "Failed to load internationalization document bundle [ %s ].";
81   
82    /**
83    * The default Resource Bundle to fall back to if no document bundle is found when trying to get a key.
84    */
85    protected ResourceBundle bundle;
86   
87    /**
88    * The {@link com.xpn.xwiki.XWikiContext} object, used to get access to XWiki primitives for loading documents.
89    */
90    @Deprecated
91    protected XWikiContext context;
92   
93    /**
94    * Cache properties loaded from the document bundles for maximum efficiency. The map is of type (Long, Properties)
95    * where Long is the XWiki document ids.
96    */
97    private Map<Long, Properties> propsCache = new HashMap<Long, Properties>();
98   
99    /**
100    * Cache for saving the last modified dates of document bundles that have been loaded. This is used so that we can
101    * reload them if they've been modified since last time they were cached. The map is of type (Long, Date) where Long
102    * is the XWiki document ids.
103    */
104    private Map<Long, Date> previousDates = new HashMap<Long, Date>();
105   
106    /**
107    * List of document bundles that have been modified since the last time they were cached. The Set contains Long
108    * objects which are the XWiki document ids. TODO: This instance variable should be removed as it's used internally
109    * and its state shouldn't encompass several calls to get().
110    */
111    private Set<Long> docsToRefresh = new HashSet<Long>();
112   
113    /**
114    * The localization manager.
115    */
116    private ContextualLocalizationManager localization;
117   
118    /**
119    * @param localization the localization manager
120    */
 
121  39479 toggle public XWikiMessageTool(ContextualLocalizationManager localization)
122    {
123  39506 this.localization = localization;
124    }
125   
126    /**
127    * @param bundle the default Resource Bundle to fall back to if no document bundle is found when trying to get a key
128    * @param context the {@link com.xpn.xwiki.XWikiContext} object, used to get access to XWiki primitives for loading
129    * documents
130    */
 
131  33 toggle public XWikiMessageTool(ResourceBundle bundle, XWikiContext context)
132    {
133  33 this.bundle = bundle;
134  33 this.context = context;
135    }
136   
 
137  47 toggle protected XWikiContext getXWikiContext()
138    {
139  47 return this.context;
140    }
141   
142    /**
143    * @param key the key identifying the message to look for
144    * @return the message in the defined language. The message should be a simple string without any parameters. If you
145    * need to pass parameters see {@link #get(String, java.util.List)}
146    * @see com.xpn.xwiki.web.XWikiMessageTool for more details on the algorithm used to find the message
147    */
 
148  2476 toggle public String get(String key)
149    {
150  2476 String translation;
151  2476 if (this.localization != null) {
152  2460 translation = get(key, ArrayUtils.EMPTY_OBJECT_ARRAY);
153    } else {
154  16 translation = getTranslation(key);
155  16 if (translation == null) {
156  4 try {
157  4 translation = this.bundle.getString(key);
158    } catch (Exception e) {
159  3 translation = key;
160    }
161    }
162    }
163   
164  2476 return translation;
165    }
166   
167    /**
168    * Find a translation and then replace any parameters found in the translation by the passed params parameters. The
169    * format is the one used by {@link java.text.MessageFormat}.
170    * <p>
171    * Note: The reason we're using a List instead of an Object array is because we haven't found how to easily create
172    * an Array in Velocity whereas a List is easily created. For example: <code>$msg.get("key", ["1", "2", "3"])</code>
173    * .
174    * </p>
175    *
176    * @param key the key of the string to find
177    * @param params the list of parameters to use for replacing "{N}" elements in the string. See
178    * {@link java.text.MessageFormat} for the full syntax
179    * @return the translated string with parameters resolved
180    */
 
181  1 toggle public String get(String key, List<?> params)
182    {
183  1 return get(key, params.toArray());
184    }
185   
186    /**
187    * Find a translation and then replace any parameters found in the translation by the passed parameters. The format
188    * is the one used by {@link java.text.MessageFormat}.
189    *
190    * @param key the key of the string to find
191    * @param params the list of parameters to use for replacing "{N}" elements in the string. See
192    * {@link java.text.MessageFormat} for the full syntax
193    * @return the translated string with parameters resolved
194    */
 
195  2462 toggle public String get(String key, Object... params)
196    {
197  2462 String translation;
198  2462 if (this.localization != null) {
199  2461 translation = this.localization.getTranslationPlain(key, params);
200  2461 if (translation == null) {
201  16 translation = key;
202    }
203    } else {
204  1 translation = get(key);
205   
206  1 if (params != null && translation != null) {
207  1 translation = MessageFormat.format(translation, params);
208    }
209    }
210   
211  2462 return translation;
212    }
213   
214    /**
215    * @return the list of internationalization document bundle names as a list of XWiki page names ("Space.Document")
216    * or an empty list if no such documents have been found
217    * @see com.xpn.xwiki.web.XWikiMessageTool for more details on the algorithm used to find the document bundles
218    */
 
219  16 toggle protected List<String> getDocumentBundleNames()
220    {
221  16 List<String> docNamesList;
222   
223  16 XWikiContext context = getXWikiContext();
224   
225  16 String docNames = context.getWiki().getXWikiPreference(KEY, context);
226  16 if (docNames == null || "".equals(docNames)) {
227  4 docNames = context.getWiki().Param("xwiki." + KEY);
228    }
229   
230  16 if (docNames == null) {
231  2 docNamesList = new ArrayList<String>();
232    } else {
233  14 docNamesList = Arrays.asList(docNames.split(","));
234    }
235   
236  16 return docNamesList;
237    }
238   
239    /**
240    * @return the internationalization document bundles (a list of {@link XWikiDocument})
241    * @see com.xpn.xwiki.web.XWikiMessageTool for more details on the algorithm used to find the document bundles
242    */
 
243  16 toggle public List<XWikiDocument> getDocumentBundles()
244    {
245  16 XWikiContext context = getXWikiContext();
246   
247  16 String defaultLanguage = context.getWiki().getDefaultLanguage(context);
248  16 List<XWikiDocument> result = new ArrayList<XWikiDocument>();
249  16 for (String docName : getDocumentBundleNames()) {
250  15 for (XWikiDocument docBundle : getDocumentBundles(docName.trim(), defaultLanguage)) {
251  17 if (docBundle != null) {
252  17 if (!docBundle.isNew()) {
253    // Checks for a name update
254  16 Long docId = Long.valueOf(docBundle.getId());
255  16 Date docDate = docBundle.getDate();
256    // Check for a doc modification
257  16 if (!docDate.equals(this.previousDates.get(docId))) {
258  10 this.docsToRefresh.add(docId);
259  10 this.previousDates.put(docId, docDate);
260    }
261  16 result.add(docBundle);
262    } else {
263    // The document listed as a document bundle doesn't exist. Do nothing
264    // and log.
265  1 LOGGER.warn("The document [" + docBundle.getFullName() + "] is listed "
266    + "as an internationalization document bundle but it does not " + "exist.");
267    }
268    }
269    }
270    }
271  16 return result;
272    }
273   
274    /**
275    * Helper method to help get a translated version of a document. It handles any exception raised to make it easy to
276    * use.
277    *
278    * @param documentName the document's name (eg Space.Document)
279    * @return the document object corresponding to the passed document's name. A translated version of the document for
280    * the current Locale is looked for.
281    */
 
282  0 toggle public XWikiDocument getDocumentBundle(String documentName)
283    {
284  0 XWikiDocument docBundle;
285   
286  0 if (documentName.length() == 0) {
287  0 docBundle = null;
288    } else {
289  0 try {
290  0 XWikiContext context = getXWikiContext();
291   
292    // First, looks for a document suffixed by the language
293  0 docBundle = context.getWiki().getDocument(documentName, context);
294  0 docBundle = docBundle.getTranslatedDocument(context);
295    } catch (XWikiException e) {
296    // Error while loading the document.
297    // TODO: A runtime exception should be thrown that will bubble up till the
298    // topmost level. For now simply log the error
299  0 LOGGER.error(String.format(LOAD_ERROR_MSG_FMT, documentName), e);
300  0 docBundle = null;
301    }
302    }
303   
304  0 return docBundle;
305    }
306   
307    /**
308    * Helper method to help get a translated version of a document. It handles any exception raised to make it easy to
309    * use.
310    *
311    * @param documentName the document's name (eg Space.Document)
312    * @param defaultLanguage default language
313    * @return the document object corresponding to the passed document's name. A translated version of the document for
314    * the current Locale is looked for.
315    */
 
316  15 toggle public List<XWikiDocument> getDocumentBundles(String documentName, String defaultLanguage)
317    {
318  15 List<XWikiDocument> list = new ArrayList<XWikiDocument>();
319   
320  15 if (documentName.length() != 0) {
321  15 try {
322  15 XWikiContext context = getXWikiContext();
323   
324    // First, looks for a document suffixed by the language
325  15 XWikiDocument docBundle = context.getWiki().getDocument(documentName, context);
326  15 XWikiDocument tdocBundle = docBundle.getTranslatedDocument(context);
327  15 list.add(tdocBundle);
328  15 if (!tdocBundle.getRealLanguage().equals(defaultLanguage)) {
329  2 XWikiDocument defdocBundle = docBundle.getTranslatedDocument(defaultLanguage, context);
330  2 if (tdocBundle != defdocBundle) {
331  2 list.add(defdocBundle);
332    }
333    }
334   
335    } catch (XWikiException e) {
336    // Error while loading the document.
337    // TODO: A runtime exception should be thrown that will bubble up till the
338    // topmost level. For now simply log the error
339  0 LOGGER.error(String.format(LOAD_ERROR_MSG_FMT, documentName), e);
340    }
341    }
342   
343  15 return list;
344    }
345   
346    /**
347    * @param docBundle the document bundle.
348    * @return properties of the document bundle.
349    */
 
350  10 toggle public Properties getDocumentBundleProperties(XWikiDocument docBundle)
351    {
352  10 Properties props = new Properties();
353  10 String content = docBundle.getContent();
354  10 try {
355  10 props.load(new StringReader(content));
356    } catch (IOException e) {
357  0 LOGGER.error("Failed to parse content of document [" + docBundle + "] as translation content", e);
358    }
359   
360  10 return props;
361    }
362   
363    /**
364    * Looks for a translation in the list of internationalization document bundles. It first checks if the translation
365    * can be found in the cache.
366    *
367    * @param key the key identifying the translation
368    * @return the translation or null if not found or if the passed key is null
369    */
 
370  16 toggle protected String getTranslation(String key)
371    {
372  16 String returnValue = null;
373   
374  16 if (key != null) {
375  15 for (XWikiDocument docBundle : getDocumentBundles()) {
376  15 if (docBundle != null) {
377  15 Long docId = Long.valueOf(docBundle.getId());
378  15 Properties props = null;
379  15 if (this.docsToRefresh.contains(docId) || !this.propsCache.containsKey(docId)) {
380    // Cache needs to be updated
381  10 props = getDocumentBundleProperties(docBundle);
382    // updates cache
383  10 this.propsCache.put(docId, props);
384  10 this.docsToRefresh.remove(docId);
385    } else {
386    // gets from cache
387  5 props = this.propsCache.get(docId);
388    }
389   
390  15 returnValue = props.getProperty(key);
391  15 if (returnValue != null) {
392  12 break;
393    }
394    }
395    }
396    }
397   
398  16 return returnValue;
399    }
400    }