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.init;
35
36 import static info.magnolia.objectfactory.Components.getComponent;
37 import static org.apache.commons.collections4.CollectionUtils.isEmpty;
38 import static org.apache.commons.lang3.StringUtils.substringAfter;
39
40 import info.magnolia.init.properties.EnvironmentPropertySource;
41 import info.magnolia.init.properties.FileSystemPropertySource;
42 import info.magnolia.init.properties.ServletContextPropertySource;
43 import info.magnolia.init.properties.SystemPropertySource;
44
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 import java.nio.file.Paths;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51 import java.util.Optional;
52 import java.util.function.Supplier;
53 import java.util.stream.Collectors;
54 import java.util.stream.Stream;
55
56 import javax.inject.Inject;
57 import javax.inject.Singleton;
58 import javax.servlet.ServletContext;
59
60 import org.apache.commons.lang3.StringUtils;
61 import org.apache.commons.text.StringSubstitutor;
62 import org.apache.commons.text.lookup.StringLookup;
63
64 import com.google.common.base.Suppliers;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 @Singleton
173 public class DefaultMagnoliaPropertiesResolver implements MagnoliaPropertiesResolver {
174 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultMagnoliaPropertiesResolver.class);
175
176
177
178
179
180 public static final String CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX = "contextAttribute/";
181
182
183
184
185
186 public static final String CONTEXT_PARAM_PLACEHOLDER_PREFIX = "contextParam/";
187
188
189
190
191
192 public static final String SYSTEM_PROPERTY_PLACEHOLDER_PREFIX = "systemProperty/";
193
194
195
196
197
198 public static final String ENV_PROPERTY_PLACEHOLDER_PREFIX = "env/";
199
200
201
202
203
204 protected static final String MAGNOLIA_INITIALIZATION_FILE = "magnolia.initialization.file";
205
206
207
208
209
210
211
212 @Deprecated
213 protected static final String DEFAULT_INITIALIZATION_PARAMETER =
214 "WEB-INF/config/${servername}/${contextPath}/magnolia.properties,"
215 + "WEB-INF/config/${servername}/${webapp}/magnolia.properties,"
216 + "WEB-INF/config/${servername}/magnolia.properties,"
217 + "WEB-INF/config/${contextPath}/magnolia.properties,"
218 + "WEB-INF/config/${webapp}/magnolia.properties,"
219 + "WEB-INF/config/default/magnolia.properties,"
220 + "WEB-INF/config/magnolia.properties";
221
222
223
224
225
226 protected static final String PROFILE_INITIALIZATION_PARAMETER =
227 "WEB-INF/config/${MAGNOLIA_PROFILE}/magnolia_${MAGNOLIA_INSTANCE_TYPE}_${MAGNOLIA_STAGE}.properties," +
228 "WEB-INF/config/${MAGNOLIA_PROFILE}/magnolia_${MAGNOLIA_INSTANCE_TYPE}.properties," +
229 "WEB-INF/config/${MAGNOLIA_PROFILE}/magnolia.properties," +
230 "WEB-INF/config/shared/magnolia_${MAGNOLIA_INSTANCE_TYPE}_${MAGNOLIA_STAGE}.properties," +
231 "WEB-INF/config/shared/magnolia_${MAGNOLIA_INSTANCE_TYPE}.properties," +
232 "WEB-INF/config/shared/magnolia.properties," +
233 "WEB-INF/config/default/magnolia_${MAGNOLIA_INSTANCE_TYPE}_${MAGNOLIA_STAGE}.properties," +
234 "WEB-INF/config/default/magnolia_${MAGNOLIA_INSTANCE_TYPE}.properties," +
235 "WEB-INF/config/default/magnolia.properties,";
236
237
238
239
240 protected static final String MAGNOLIA_PROFILE_ENV = "MAGNOLIA_PROFILE";
241
242 private final ServletContext context;
243 private final MagnoliaInitPaths initPaths;
244 private final SystemPropertySource systemPropertySource;
245 private final EnvironmentPropertySource environmentPropertySource;
246 private final Supplier<List<String>> locations = Suppliers.memoize(this::resolveLocations);
247
248 @Inject
249 public DefaultMagnoliaPropertiesResolver(ServletContext context, MagnoliaInitPaths initPaths, SystemPropertySource systemPropertySource, EnvironmentPropertySource environmentPropertySource) {
250 this.context = context;
251 this.initPaths = initPaths;
252 this.systemPropertySource = systemPropertySource;
253 this.environmentPropertySource = environmentPropertySource;
254 }
255
256
257
258
259 @Deprecated
260 public DefaultMagnoliaPropertiesResolver(ServletContext context, MagnoliaInitPaths initPaths) {
261 this(context, initPaths, getComponent(SystemPropertySource.class), getComponent(EnvironmentPropertySource.class));
262 }
263
264 protected String getConfigurationProfile() {
265 String profile = systemPropertySource.getProperty(MAGNOLIA_PROFILE_ENV);
266 return profile != null ? profile : environmentPropertySource.getProperty(MAGNOLIA_PROFILE_ENV);
267 }
268
269 protected String getInitParameter(ServletContext ctx, String name, String defaultValue) {
270 final String propertiesFilesString = ctx.getInitParameter(name);
271 if (StringUtils.isEmpty(propertiesFilesString)) {
272 log.debug("{} value in web.xml is undefined, falling back to default: {}", name, defaultValue);
273 String profile = getConfigurationProfile();
274 if (profile == null) {
275 log.info("Profile based configuration is disabled, falling back to configuration based on servername, webapp and contextPath");
276 log.warn("This configuration mode is deprecated and will not be supported in future versions. " +
277 "Consider migrating to profile directory based configuration.");
278 } else {
279 String profileDir = "/WEB-INF/config/" + profile;
280 if (isEmpty(ctx.getResourcePaths(profileDir))) {
281 throw new RuntimeException("Invalid profile " + profile + ". Directory " + profileDir + " is empty or does not exist");
282 } else {
283 log.info("Profile based configuration is enabled. Profile is set to {}", profile);
284 }
285 }
286 return defaultValue;
287 }
288 log.info("Found custom value of the {} context attribute in web.xml. " +
289 "Resolving Magnolia property files from that location: {}", name, propertiesFilesString);
290 return propertiesFilesString;
291 }
292
293
294
295
296 protected List<String> getLocations() {
297 return locations.get();
298 }
299
300 private List<String> resolveLocations() {
301 String propertiesFilesString = getInitParameter(context, MAGNOLIA_INITIALIZATION_FILE,
302 getConfigurationProfile() == null ? DEFAULT_INITIALIZATION_PARAMETER : PROFILE_INITIALIZATION_PARAMETER);
303
304 return Arrays.stream(propertiesFilesString.trim().split("[,]+", 0))
305 .map(String::trim)
306 .map(this::interpolate)
307 .filter(Optional::isPresent)
308 .map(Optional::get)
309 .collect(Collectors.toList());
310 }
311
312 @Override
313 public List<PropertySource> getSources() {
314 final List<PropertySource> sources = new ArrayList<PropertySource>();
315 boolean foundFiles = false;
316 for (String location : getLocations()) {
317 try {
318 if (Paths.get(location).isAbsolute()) {
319 sources.add(new FileSystemPropertySource(location));
320 } else {
321 sources.add(new ServletContextPropertySource(context, location));
322 }
323 foundFiles = true;
324 } catch (FileNotFoundException e) {
325 log.debug("Configuration file not found with path [{}]", location);
326 } catch (IOException e) {
327 throw new RuntimeException("Error while reading from the configuration file " + location, e);
328 }
329 }
330 if (!foundFiles) {
331 log.warn("No configuration files found using location list {}.", getLocations());
332 }
333
334 return sources;
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 private Optional<String> interpolate(String source) {
359 Resolver resolver = new Resolver();
360 String interpolation = new StringSubstitutor(resolver).replace(source);
361 if (resolver.getUnresolvedKeys().isEmpty()) {
362 log.info("Resolved Magnolia property file location {} to {}", source, interpolation);
363 return Optional.of(interpolation);
364 } else {
365 log.debug("Could not resolve Magnolia property file locations {}. Missing keys: {}", source, resolver.getUnresolvedKeys());
366 return Optional.empty();
367 }
368 }
369
370 private class Resolver implements StringLookup {
371 private List<String> unresolvedKeys = new ArrayList<>();
372
373 Optional<String> getBasicProperty(String key) {
374 switch (key) {
375 case "servername":
376 return Optional.ofNullable(initPaths.getServerName());
377 case "webapp":
378 return Optional.ofNullable(initPaths.getWebappFolderName());
379 case "contextPath":
380
381 String contextPath = initPaths.getContextPath();
382 if (contextPath.length() == 0) {
383 return Optional.of("ROOT");
384 } else {
385 return Optional.of(contextPath.substring(1));
386 }
387 default:
388 return Optional.empty();
389 }
390 }
391
392 private boolean qualified(String key) {
393 return key.contains("/");
394 }
395
396 private Optional<String> stripPrefix(String key, String prefix) {
397 if (key.startsWith(prefix)) {
398 return Optional.of(substringAfter(key, prefix));
399 } else if (!qualified(key)) {
400 return Optional.of(key);
401 } else {
402 return Optional.empty();
403 }
404 }
405
406 private Optional<String> getContextAttribute(String key) {
407 return stripPrefix(key, CONTEXT_ATTRIBUTE_PLACEHOLDER_PREFIX)
408 .map(context::getAttribute)
409 .map(Object::toString);
410 }
411
412 private Optional<String> getContextParameter(String key) {
413 return stripPrefix(key, CONTEXT_PARAM_PLACEHOLDER_PREFIX)
414 .map(context::getInitParameter);
415 }
416
417 private Optional<String> getSystemProperty(String key) {
418 return stripPrefix(key, SYSTEM_PROPERTY_PLACEHOLDER_PREFIX)
419 .filter(StringUtils::isNoneBlank)
420 .map(systemPropertySource::getProperty);
421 }
422
423 private Optional<String> getEnvironmentVariable(String key) {
424 return stripPrefix(key, ENV_PROPERTY_PLACEHOLDER_PREFIX)
425 .filter(StringUtils::isNoneBlank)
426 .map(environmentPropertySource::getProperty);
427 }
428
429 @Override
430 public String lookup(String key) {
431 Optional<String> value = Stream.<Supplier<Optional<String>>>of(
432 () -> getBasicProperty(key),
433 () -> getContextAttribute(key),
434 () -> getContextParameter(key),
435 () -> getSystemProperty(key),
436 () -> getEnvironmentVariable(key)
437 )
438 .map(Supplier::get)
439 .filter(Optional::isPresent)
440 .map(Optional::get)
441 .findFirst();
442
443 if (value.isPresent()) {
444 return value.get();
445 } else {
446 unresolvedKeys.add(key);
447 return null;
448 }
449 }
450
451 List<String> getUnresolvedKeys() {
452 return unresolvedKeys;
453 }
454 }
455
456 }