1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.i18nsystem;
35
36 import info.magnolia.cms.i18n.MessagesManager;
37 import info.magnolia.i18nsystem.util.LocaleUtils;
38 import info.magnolia.objectfactory.Components;
39 import info.magnolia.resourceloader.Resource;
40 import info.magnolia.resourceloader.ResourceOrigin;
41 import info.magnolia.resourceloader.util.FileResourceCollectorVisitor;
42 import info.magnolia.resourceloader.util.Functions;
43
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.Reader;
48 import java.util.Collection;
49 import java.util.HashMap;
50 import java.util.Locale;
51 import java.util.Map;
52 import java.util.Properties;
53 import java.util.Set;
54 import java.util.TreeMap;
55 import java.util.TreeSet;
56
57 import javax.inject.Inject;
58
59 import org.apache.commons.io.input.BOMInputStream;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.common.base.Predicate;
64 import com.google.common.base.Predicates;
65
66
67
68
69 public class DefaultMessageBundlesLoader {
70 public static final String MGNL_I18N_PATH = "^/([^/]+/|mgnl-)i18n/";
71 public static final String MGNL_I18N_PROPERTIES = MGNL_I18N_PATH + ".*\\.properties$";
72
73
74 protected static final Predicate<Resource> DIRECTORY_PREDICATE = Predicates.alwaysTrue();
75 protected static final Predicate<Resource> RESOURCE_PREDICATE = Functions.pathMatches(MGNL_I18N_PROPERTIES);
76
77 private static final Logger log = LoggerFactory.getLogger(DefaultMessageBundlesLoader.class);
78
79 private final Map<Locale, Properties> messages = new HashMap<>();
80
81 private Map<String, DuplicateEntry> duplicateEntriesMap = new HashMap<>();
82
83 @Inject
84 public DefaultMessageBundlesLoader(ResourceOrigin resourceOrigin) {
85 loadMessages(resourceOrigin, newVisitor());
86 }
87
88
89
90
91 @Deprecated
92 public DefaultMessageBundlesLoader() {
93 this(Components.getComponent(ResourceOrigin.class));
94 }
95
96 Map<Locale, Properties> getMessages() {
97 return messages;
98 }
99
100 private FileResourceCollectorVisitor newVisitor() {
101 return FileResourceCollectorVisitor.on(DIRECTORY_PREDICATE, RESOURCE_PREDICATE);
102 }
103
104 private void loadMessages(ResourceOrigin resourceOrigin, FileResourceCollectorVisitor visitor) {
105 resourceOrigin.traverseWith(visitor);
106 final Collection<Resource> collected = visitor.getCollectedResources();
107 for (Resource langFile : collected) {
108 final String fileName = langFile.getName();
109 final Locale locale = resolveLocale(fileName);
110 loadResourcesInPropertiesMap(langFile, locale);
111 }
112
113 logDuplicates();
114 }
115
116
117
118
119
120
121
122 private void loadResourcesInPropertiesMap(Resource propertiesFile, Locale locale) {
123 try (InputStream in = propertiesFile.openStream()) {
124 log.debug("Loading properties file at [{}] with locale [{}]...", propertiesFile, locale);
125
126
127 final Reader inStream = new InputStreamReader(new BOMInputStream(in), "UTF-8");
128
129 final Properties properties = new Properties();
130 properties.load(inStream);
131 Properties existingProperties = messages.get(locale);
132
133 if (existingProperties != null) {
134 checkForDuplicates(existingProperties, properties, locale, propertiesFile);
135
136 log.debug("Adding properties to already existing ones under {} locale", locale);
137 properties.putAll(existingProperties);
138 }
139
140 messages.put(locale, properties);
141 } catch (IOException e) {
142 log.warn("An IO error occurred while trying to read properties file at [{}]", propertiesFile, e);
143 }
144 }
145
146 private void checkForDuplicates(Properties existingProperties, Properties newProperties, Locale locale, Resource resource) {
147 for (String key : newProperties.stringPropertyNames()) {
148 if (existingProperties.containsKey(key)) {
149 DuplicateEntry duplicateEntry = new DuplicateEntry(resource, locale, key);
150 duplicateEntriesMap.put(duplicateEntry.getKeyWithLocaleAndUrl(), duplicateEntry);
151 }
152 }
153 }
154
155 private void logDuplicates() {
156
157 Map<String, DuplicateEntry> perLocaleDuplicates = new TreeMap<>();
158
159 Map<String, DuplicateEntry> perKeyDuplicates = new TreeMap<>();
160 Set<String> urlsSet = new TreeSet<>();
161
162 for (DuplicateEntry duplicateEntry : duplicateEntriesMap.values()) {
163 perLocaleDuplicates.put(duplicateEntry.getKeyWithLocale(), duplicateEntry);
164 perKeyDuplicates.put(duplicateEntry.getKey(), duplicateEntry);
165 urlsSet.add(duplicateEntry.getResource().toString());
166 }
167
168 if (duplicateEntriesMap.size() > 0) {
169 StringBuilder logMsg = new StringBuilder("\n");
170 logMsg.append("------------------------------------\n");
171 logMsg.append("Duplicated keys found while loading message bundles from ./mgnl-i18n :\n");
172 logMsg.append("------------------------------------\n");
173 logMsg.append("Number of duplicates based on key pattern <key>_<locale>_<bundle-url>: ").append(duplicateEntriesMap.size()).append("\n");
174 logMsg.append("Number of duplicates based on key pattern <key>_<locale>: ").append(perLocaleDuplicates.size()).append("\n");
175 logMsg.append("Number of duplicates based on key pattern <key>: ").append(perKeyDuplicates.size()).append("\n");
176 logMsg.append("To get more details concerning the keys, raise the log level to 'DEBUG' for ").append(this.getClass().getName()).append(".\n");
177 logMsg.append("If you encounter a large number of duplicates, it's possible that you are running in a development environment where you have multiple copies of the web-apps in the overlays folder of your web-app.\n");
178 logMsg.append("URLs of the affected files creating duplicate entries:\n");
179 for (String url : urlsSet) {
180 logMsg.append(url).append("\n");
181 }
182 logMsg.append("------------------------------------");
183 log.info(logMsg.toString());
184
185
186 for (DuplicateEntry duplicateEntry : perLocaleDuplicates.values()) {
187 log.debug("Duplicate key found: [{}]; for locale [{}]; in resource [{}]", duplicateEntry.getKey(), duplicateEntry.getLocale(), duplicateEntry.getResource());
188 }
189 }
190 duplicateEntriesMap = null;
191 }
192
193
194
195
196 protected Locale resolveLocale(final String fileName) {
197 return LocaleUtils.parseFromFilename(fileName, getFallbackLocale());
198 }
199
200
201 private Locale getFallbackLocale() {
202 return MessagesManager.getInstance().getDefaultLocale();
203 }
204
205
206 private class DuplicateEntry {
207 private Resource resource;
208 private Locale locale;
209 private String key;
210 private String keyWithLocale;
211 private String keyWithLocaleAndUrl;
212
213 private DuplicateEntry(Resource resource, Locale locale, String key) {
214 this.resource = resource;
215 this.locale = locale;
216 this.key = key;
217 this.keyWithLocale = this.key + "_" + locale.toString();
218 this.keyWithLocaleAndUrl = this.keyWithLocale + "_" + resource.getPath();
219 }
220
221 public Resource getResource() {
222 return resource;
223 }
224
225 private String getKeyWithLocale() {
226 return keyWithLocale;
227 }
228
229 private String getKeyWithLocaleAndUrl() {
230 return keyWithLocaleAndUrl;
231 }
232
233 private Locale getLocale() {
234 return locale;
235 }
236
237 private String getKey() {
238 return key;
239 }
240
241 }
242
243 }