1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.common.util.properties;
17
18 import static com.google.common.base.Preconditions.checkArgument;
19 import static com.google.common.base.Preconditions.checkNotNull;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Properties;
28 import java.util.SortedSet;
29
30 import javax.xml.bind.JAXBContext;
31 import javax.xml.bind.JAXBException;
32 import javax.xml.bind.Unmarshaller;
33 import javax.xml.bind.UnmarshallerHandler;
34 import javax.xml.parsers.ParserConfigurationException;
35 import javax.xml.parsers.SAXParser;
36 import javax.xml.parsers.SAXParserFactory;
37
38 import org.apache.commons.io.IOUtils;
39 import org.apache.commons.lang3.StringUtils;
40 import org.kuali.common.util.LocationUtils;
41 import org.kuali.common.util.PropertyUtils;
42 import org.kuali.common.util.Str;
43 import org.kuali.common.util.execute.Executable;
44 import org.kuali.common.util.execute.impl.NoOpExecutable;
45 import org.kuali.common.util.execute.impl.SetSystemPropertyExecutable;
46 import org.kuali.common.util.log.LoggerUtils;
47 import org.kuali.common.util.obscure.DefaultObscurer;
48 import org.kuali.common.util.obscure.Obscurer;
49 import org.kuali.common.util.properties.rice.Config;
50 import org.kuali.common.util.properties.rice.Param;
51 import org.slf4j.Logger;
52 import org.springframework.util.PropertyPlaceholderHelper;
53 import org.xml.sax.InputSource;
54 import org.xml.sax.SAXException;
55 import org.xml.sax.XMLReader;
56
57 import com.google.common.base.Optional;
58 import com.google.common.collect.ImmutableList;
59 import com.google.common.collect.Lists;
60 import com.google.common.collect.Maps;
61 import com.google.common.collect.Sets;
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 public class RicePropertiesLoader {
80
81 private static final Logger logger = LoggerUtils.make();
82
83 private final PropertyPlaceholderHelper propertyPlaceholderHelper;
84 private final String chainedConfigLocationKey;
85 private final ImmutableList<String> obscureTokens;
86 private final Obscurer obscurer;
87 private final Randomizer randomizer;
88 private final boolean systemPropertiesWin;
89
90 public Properties load(String location) {
91 checkArgument(!StringUtils.isBlank(location), "'location' cannot be blank");
92 checkArgument(LocationUtils.exists(location), "[%s] does not exist", location);
93 Unmarshaller unmarshaller = getUnmarshaller();
94 Map<String, Param> params = Maps.newHashMap();
95 load(location, unmarshaller, 0, params);
96 if (systemPropertiesWin) {
97 Map<String, Param> system = convert(PropertyUtils.getGlobalProperties(), true, false);
98 for (Param param : system.values()) {
99 update(params, param, "");
100 }
101 }
102 handleRandomParams(params);
103 handleSystemParams(params);
104 return convert(params);
105 }
106
107 protected void load(String location, Unmarshaller unmarshaller, int depth, Map<String, Param> params) {
108
109
110 final String prefix = StringUtils.repeat(" ", depth);
111
112
113 if (location.equals("") || !LocationUtils.exists(location)) {
114 logger.info("{}# skip non-existent location [{}]", prefix, location);
115 return;
116 }
117
118 InputStream in = null;
119 try {
120 in = LocationUtils.getInputStream(location);
121 load(prefix, location, in, params, depth, unmarshaller);
122 } catch (IOException e) {
123 throw new IllegalStateException(e);
124 } finally {
125 IOUtils.closeQuietly(in);
126 }
127 }
128
129 protected void load(String prefix, String location, InputStream in, Map<String, Param> params, int depth, Unmarshaller unmarshaller) throws IOException {
130 if (isPropertiesFile(location)) {
131 loadJavaProperties(prefix, location, in, params, depth);
132 } else {
133 loadRiceProperties(prefix, location, in, params, depth, unmarshaller);
134 }
135 }
136
137 protected void loadRiceProperties(String prefix, String location, InputStream in, Map<String, Param> params, int depth, Unmarshaller unmarshaller) throws IOException {
138 logger.info("{}+ loading - [{}]", prefix, location);
139 Config config = unmarshal(unmarshaller, in);
140 for (Param p : config.getParams()) {
141 handleParam(p, depth, unmarshaller, params, prefix);
142 }
143 logger.info("{}- loaded - [{}]", prefix, location);
144 }
145
146 protected void handleParam(Param p, int depth, Unmarshaller unmarshaller, Map<String, Param> params, String prefix) {
147
148
149 if (p.getName().equalsIgnoreCase(chainedConfigLocationKey)) {
150 String originalLocation = p.getValue();
151 String resolvedLocation = getResolvedValue(prefix, originalLocation, params);
152 load(resolvedLocation, unmarshaller, depth + 1, params);
153 } else {
154
155 update(params, p, prefix);
156 }
157
158 }
159
160 protected void loadJavaProperties(String prefix, String location, InputStream in, Map<String, Param> params, int depth) throws IOException {
161 logger.info("{}+ loading - [{}]", prefix, location);
162 Properties loaded = new Properties();
163 loaded.load(in);
164
165
166
167
168
169 Map<String, Param> newMap = convert(loaded, true, false);
170 for (Param p : newMap.values()) {
171 update(params, p, prefix);
172 }
173 logger.info("{}- loaded - [{}]", prefix, location);
174 }
175
176 protected void update(Map<String, Param> params, Param p, String prefix) {
177 checkNotNull(p.getValue(), "parameter value cannot be null");
178
179
180 Optional<Param> oldParam = Optional.fromNullable(params.get(p.getName()));
181
182
183 String newLogValue = getLogValue(p);
184
185
186 if (!oldParam.isPresent()) {
187 Object[] args = { prefix, p.getName(), newLogValue };
188 logger.debug("{}~ add - [{}]=[{}]", args);
189 params.put(p.getName(), p);
190 return;
191 }
192
193
194 if (oldParam.get().getValue().equals(p.getValue())) {
195 Object[] args = { prefix, p.getName(), newLogValue };
196 logger.debug("{}~ duplicate - [{}]=[{}]", args);
197 return;
198 }
199
200
201 String oldLogValue = getLogValue(oldParam.get());
202
203
204 if (p.isOverride()) {
205
206 Object[] args = { prefix, p.getName(), oldLogValue, newLogValue };
207 logger.info("{}* override - [{}]=[{}] -> [{}]", args);
208 params.put(p.getName(), p);
209 } else {
210
211 Object[] args = { prefix, p.getName(), newLogValue };
212 logger.info("{}~ ignore - [{}]=[{}]", args);
213 }
214 }
215
216 protected String getLogValue(Param param) {
217 String lcase = param.getName().toLowerCase();
218 for (String obscurePattern : obscureTokens) {
219 if (lcase.contains(obscurePattern)) {
220 return Str.flatten(obscurer.obscure(param.getValue()));
221 }
222 }
223 return Str.flatten(param.getValue());
224 }
225
226 protected Unmarshaller getUnmarshaller() {
227 try {
228 JAXBContext context = JAXBContext.newInstance(Config.class);
229 return context.createUnmarshaller();
230 } catch (JAXBException e) {
231 throw new IllegalStateException("Error initializing JAXB for config", e);
232 }
233 }
234
235 protected Config unmarshal(Unmarshaller unmarshaller, InputStream in) throws IOException {
236 try {
237 UnmarshallerHandler unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
238 SAXParserFactory spf = SAXParserFactory.newInstance();
239 SAXParser sp = spf.newSAXParser();
240 XMLReader xr = sp.getXMLReader();
241 xr.setContentHandler(unmarshallerHandler);
242 InputSource xmlSource = new InputSource(in);
243 xr.parse(xmlSource);
244 return (Config) unmarshallerHandler.getResult();
245 } catch (SAXException e) {
246 throw new IllegalStateException("Unexpected SAX error", e);
247 } catch (ParserConfigurationException e) {
248 throw new IllegalStateException("Unexpected parser configuration error", e);
249 } catch (JAXBException e) {
250 throw new IllegalStateException("Unexpected JAXB error", e);
251 }
252 }
253
254 protected boolean isPropertiesFile(String location) {
255 return location.toLowerCase().endsWith(".properties");
256 }
257
258 protected Map<String, Param> convert(Properties properties, boolean override, boolean system) {
259 Map<String, Param> params = Maps.newHashMap();
260 SortedSet<String> keys = Sets.newTreeSet(properties.stringPropertyNames());
261 for (String key : keys) {
262 String value = properties.getProperty(key);
263 Param param = Param.builder(key, value).override(override).system(system).build();
264 params.put(param.getName(), param);
265 }
266 return params;
267 }
268
269 protected Properties convert(Map<String, Param> params) {
270 Properties properties = new Properties();
271 for (Param p : params.values()) {
272 properties.setProperty(p.getName(), p.getValue());
273 }
274 return properties;
275 }
276
277 protected String getResolvedValue(String prefix, String value, Map<String, Param> params) {
278 Properties properties = convert(params);
279 Properties global = PropertyUtils.getGlobalProperties(properties);
280 String resolvedValue = propertyPlaceholderHelper.replacePlaceholders(value, global);
281 return resolvedValue;
282 }
283
284 public static Builder builder() {
285 return new Builder();
286 }
287
288 private RicePropertiesLoader(Builder builder) {
289 this.propertyPlaceholderHelper = builder.propertyPlaceholderHelper;
290 this.chainedConfigLocationKey = builder.chainedConfigLocationKey;
291 this.obscureTokens = ImmutableList.copyOf(builder.obscureTokens);
292 this.obscurer = builder.obscurer;
293 this.randomizer = builder.randomizer;
294 this.systemPropertiesWin = builder.systemPropertiesWin;
295 }
296
297 public static class Builder {
298
299 private String chainedConfigLocationKey = "config.location";
300 private Obscurer obscurer = new DefaultObscurer();
301 private Randomizer randomizer = DefaultRandomizer.create();
302 private PropertyPlaceholderHelper propertyPlaceholderHelper = RicePropertyPlaceholderHelper.create();
303 private boolean systemPropertiesWin = false;
304 private List<String> obscureTokens = ImmutableList.of("private", "password", "secret", "encryption.key", "accountAccessKey");
305
306 public Builder systemPropertiesWin(boolean systemPropertiesWin) {
307 this.systemPropertiesWin = systemPropertiesWin;
308 return this;
309 }
310
311 public Builder propertyPlaceholderHelper(PropertyPlaceholderHelper propertyPlaceholderHelper) {
312 this.propertyPlaceholderHelper = propertyPlaceholderHelper;
313 return this;
314 }
315
316 public Builder chainedConfigLocationKey(String chainedConfigLocationKey) {
317 this.chainedConfigLocationKey = chainedConfigLocationKey;
318 return this;
319 }
320
321 public Builder obscurePatterns(List<String> obscurePatterns) {
322 this.obscureTokens = obscurePatterns;
323 return this;
324 }
325
326 public Builder randomizer(Randomizer randomizer) {
327 this.randomizer = randomizer;
328 return this;
329 }
330
331 public RicePropertiesLoader build() {
332 RicePropertiesLoader instance = new RicePropertiesLoader(this);
333 validate(instance);
334 return instance;
335 }
336
337 private static void validate(RicePropertiesLoader instance) {
338 checkNotNull(instance.propertyPlaceholderHelper, "propertyPlaceholderHelper cannot be null");
339 checkNotNull(instance.obscureTokens, "obscureTokens cannot be null");
340 checkNotNull(instance.obscurer, "obscurer cannot be null");
341 checkNotNull(instance.randomizer, "randomizer cannot be null");
342 checkArgument(!StringUtils.isBlank(instance.chainedConfigLocationKey), "chainedConfigLocationKey cannot be blank");
343 }
344
345 public String getChainedConfigLocationKey() {
346 return chainedConfigLocationKey;
347 }
348
349 public void setChainedConfigLocationKey(String chainedConfigLocationKey) {
350 this.chainedConfigLocationKey = chainedConfigLocationKey;
351 }
352
353 public List<String> getObscureTokens() {
354 return obscureTokens;
355 }
356
357 public void setObscureTokens(List<String> obscureTokens) {
358 this.obscureTokens = obscureTokens;
359 }
360
361 public Obscurer getObscurer() {
362 return obscurer;
363 }
364
365 public void setObscurer(Obscurer obscurer) {
366 this.obscurer = obscurer;
367 }
368
369 public Randomizer getRandomizer() {
370 return randomizer;
371 }
372
373 public void setRandomizer(Randomizer randomizer) {
374 this.randomizer = randomizer;
375 }
376
377 public boolean isSystemPropertiesWin() {
378 return systemPropertiesWin;
379 }
380
381 public void setSystemPropertiesWin(boolean systemPropertiesWin) {
382 this.systemPropertiesWin = systemPropertiesWin;
383 }
384
385 }
386
387 protected void handleRandomParams(Map<String, Param> params) {
388 List<Param> randoms = getRandomParams(params.values());
389 for (Param param : randoms) {
390 String rangeSpec = param.getValue();
391 int random = randomizer.getInteger(rangeSpec);
392 String value = Integer.toString(random);
393 Param newParam = Param.builder(param.getName(), value).build();
394 params.put(newParam.getName(), newParam);
395 }
396 }
397
398 protected void handleSystemParams(Map<String, Param> params) {
399 Properties properties = convert(params);
400 List<Param> system = getSystemParams(params.values());
401 for (Param param : system) {
402 if (isOverrideSystemProperty(param)) {
403 overrideSystemProperty(param, params, properties);
404 }
405 }
406 }
407
408 protected boolean isOverrideSystemProperty(Param param) {
409
410
411 if (!param.isSystem()) {
412 return false;
413 }
414
415
416
417 Optional<String> system = Optional.fromNullable(System.getProperty(param.getName()));
418
419 if (system.isPresent()) {
420
421 return param.isOverride();
422 } else {
423
424 return true;
425 }
426 }
427
428 protected void overrideSystemProperty(Param param, Map<String, Param> params, Properties properties) {
429 Param resolved = getResolvedParam(param, properties);
430 if (!resolved.getValue().equals(param.getValue())) {
431 params.put(resolved.getName(), resolved);
432 properties.setProperty(resolved.getName(), resolved.getValue());
433 }
434 getSystemPropertySetter(resolved).execute();
435 }
436
437 protected Param getResolvedParam(Param param, Properties properties) {
438 String originalValue = param.getValue();
439 String resolvedValue = propertyPlaceholderHelper.replacePlaceholders(originalValue, properties);
440 if (resolvedValue.equals(originalValue)) {
441 return param;
442 } else {
443 return Param.create(param.getName(), resolvedValue);
444 }
445 }
446
447 protected List<Param> getRandomParams(Collection<Param> params) {
448 List<Param> list = Lists.newArrayList();
449 for (Param param : params) {
450 if (param.isRandom()) {
451 list.add(param);
452 }
453 }
454 Collections.sort(list);
455 return list;
456 }
457
458 protected List<Param> getSystemParams(Collection<Param> params) {
459 List<Param> list = Lists.newArrayList();
460 for (Param param : params) {
461 if (param.isSystem()) {
462 list.add(param);
463 }
464 }
465 Collections.sort(list);
466 return list;
467 }
468
469 protected Executable getSystemPropertySetter(final Param param) {
470 Optional<String> system = Optional.fromNullable(System.getProperty(param.getName()));
471
472 List<Object> args = ImmutableList.<Object> of(param.getName(), getLogValue(param));
473
474 if (!system.isPresent()) {
475 String msg = "~ add system property [{}]=[{}]";
476 return SetSystemPropertyExecutable.builder(param.getName(), param.getValue()).log(msg, args).build();
477 }
478
479
480
481 if (system.isPresent() && !system.get().equals(param.getValue())) {
482 String msg = "* override system property [{}]=[{}]";
483 return SetSystemPropertyExecutable.builder(param.getName(), param.getValue()).log(msg, args).build();
484 }
485
486
487 return NoOpExecutable.INSTANCE;
488 }
489
490 public PropertyPlaceholderHelper getPropertyPlaceholderHelper() {
491 return propertyPlaceholderHelper;
492 }
493
494 public String getChainedConfigLocationKey() {
495 return chainedConfigLocationKey;
496 }
497
498 public ImmutableList<String> getObscureTokens() {
499 return obscureTokens;
500 }
501
502 public Obscurer getObscurer() {
503 return obscurer;
504 }
505
506 public Randomizer getRandomizer() {
507 return randomizer;
508 }
509
510 public boolean isSystemPropertiesWin() {
511 return systemPropertiesWin;
512 }
513
514 }