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
65
66
67
68 public class DefaultMessageBundlesLoader {
69 protected static final Predicate<Resource> DIRECTORY_PREDICATE = Functions.pathStartsWith("/mgnl-i18n");
70 protected static final Predicate<Resource> RESOURCE_PREDICATE = Functions.pathMatches("/mgnl-i18n/.*\\.properties");
71 private static final Logger log = LoggerFactory.getLogger(DefaultMessageBundlesLoader.class);
72
73 private final Map<Locale, Properties> messages = new HashMap<>();
74
75 private Map<String, DuplicateEntry> duplicateEntriesMap = new HashMap<>();
76
77 @Inject
78 public DefaultMessageBundlesLoader(ResourceOrigin resourceOrigin) {
79 loadMessages(resourceOrigin, newVisitor());
80 }
81
82
83
84
85 @Deprecated
86 public DefaultMessageBundlesLoader() {
87 this(Components.getComponent(ResourceOrigin.class));
88 }
89
90 Map<Locale, Properties> getMessages() {
91 return messages;
92 }
93
94 private FileResourceCollectorVisitor newVisitor() {
95 return FileResourceCollectorVisitor.on(DIRECTORY_PREDICATE, RESOURCE_PREDICATE);
96 }
97
98 private void loadMessages(ResourceOrigin resourceOrigin, FileResourceCollectorVisitor visitor) {
99 resourceOrigin.traverseWith(visitor);
100 final Collection<Resource> collected = visitor.getCollectedResources();
101 for (Resource langFile : collected) {
102 final String fileName = langFile.getName();
103 final Locale locale = resolveLocale(fileName);
104 loadResourcesInPropertiesMap(langFile, locale);
105 }
106
107 logDuplicates();
108 }
109
110
111
112
113
114
115
116 private void loadResourcesInPropertiesMap(Resource propertiesFile, Locale locale) {
117 try (InputStream in = propertiesFile.openStream()) {
118 log.debug("Loading properties file at [{}] with locale [{}]...", propertiesFile, locale);
119
120
121 final Reader inStream = new InputStreamReader(new BOMInputStream(in), "UTF-8");
122
123 final Properties properties = new Properties();
124 properties.load(inStream);
125 Properties existingProperties = messages.get(locale);
126
127 if (existingProperties != null) {
128 checkForDuplicates(existingProperties, properties, locale, propertiesFile);
129
130 log.debug("Adding properties to already existing ones under {} locale", locale);
131 properties.putAll(existingProperties);
132 }
133
134 messages.put(locale, properties);
135 } catch (IOException e) {
136 log.warn("An IO error occurred while trying to read properties file at [{}]", propertiesFile, e);
137 }
138 }
139
140 private void checkForDuplicates(Properties existingProperties, Properties newProperties, Locale locale, Resource resource) {
141 for (String key : newProperties.stringPropertyNames()) {
142 if (existingProperties.containsKey(key)) {
143 DuplicateEntry duplicateEntry = new DuplicateEntry(resource, locale, key);
144 duplicateEntriesMap.put(duplicateEntry.getKeyWithLocaleAndUrl(), duplicateEntry);
145 }
146 }
147 }
148
149 private void logDuplicates() {
150
151 Map<String, DuplicateEntry> perLocaleDuplicates = new TreeMap<>();
152
153 Map<String, DuplicateEntry> perKeyDuplicates = new TreeMap<>();
154 Set<String> urlsSet = new TreeSet<>();
155
156 for (DuplicateEntry duplicateEntry : duplicateEntriesMap.values()) {
157 perLocaleDuplicates.put(duplicateEntry.getKeyWithLocale(), duplicateEntry);
158 perKeyDuplicates.put(duplicateEntry.getKey(), duplicateEntry);
159 urlsSet.add(duplicateEntry.getResource().toString());
160 }
161
162 if (duplicateEntriesMap.size() > 0) {
163 StringBuilder logMsg = new StringBuilder("\n");
164 logMsg.append("------------------------------------\n");
165 logMsg.append("Duplicated keys found while loading message bundles from ./mgnl-i18n :\n");
166 logMsg.append("------------------------------------\n");
167 logMsg.append("Number of duplicates based on key pattern <key>_<locale>_<bundle-url>: ").append(duplicateEntriesMap.size()).append("\n");
168 logMsg.append("Number of duplicates based on key pattern <key>_<locale>: ").append(perLocaleDuplicates.size()).append("\n");
169 logMsg.append("Number of duplicates based on key pattern <key>: ").append(perKeyDuplicates.size()).append("\n");
170 logMsg.append("To get more details concerning the keys, raise the log level to 'DEBUG' for ").append(this.getClass().getName()).append(".\n");
171 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");
172 logMsg.append("URLs of the affected files creating duplicate entries:\n");
173 for (String url : urlsSet) {
174 logMsg.append(url).append("\n");
175 }
176 logMsg.append("------------------------------------");
177 log.info(logMsg.toString());
178
179
180 for (DuplicateEntry duplicateEntry : perLocaleDuplicates.values()) {
181 log.debug("Duplicate key found: [{}]; for locale [{}]; in resource [{}]", duplicateEntry.getKey(), duplicateEntry.getLocale(), duplicateEntry.getResource());
182 }
183 }
184 duplicateEntriesMap = null;
185 }
186
187
188
189
190 protected Locale resolveLocale(final String fileName) {
191 return LocaleUtils.parseFromFilename(fileName, getFallbackLocale());
192 }
193
194
195 private Locale getFallbackLocale() {
196 return MessagesManager.getInstance().getDefaultLocale();
197 }
198
199
200 private class DuplicateEntry {
201 private Resource resource;
202 private Locale locale;
203 private String key;
204 private String keyWithLocale;
205 private String keyWithLocaleAndUrl;
206
207 private DuplicateEntry(Resource resource, Locale locale, String key) {
208 this.resource = resource;
209 this.locale = locale;
210 this.key = key;
211 this.keyWithLocale = this.key + "_" + locale.toString();
212 this.keyWithLocaleAndUrl = this.keyWithLocale + "_" + resource.getPath();
213 }
214
215 public Resource getResource() {
216 return resource;
217 }
218
219 private String getKeyWithLocale() {
220 return keyWithLocale;
221 }
222
223 private String getKeyWithLocaleAndUrl() {
224 return keyWithLocaleAndUrl;
225 }
226
227 private Locale getLocale() {
228 return locale;
229 }
230
231 private String getKey() {
232 return key;
233 }
234
235 }
236
237 }