1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.core.config;
17
18 import org.apache.commons.collections.CollectionUtils;
19 import org.apache.commons.lang.StringUtils;
20 import org.apache.log4j.Logger;
21 import org.kuali.rice.core.config.xsd.Param;
22 import org.kuali.rice.core.util.ImmutableProperties;
23 import org.kuali.rice.core.util.RiceUtilities;
24 import org.xml.sax.Attributes;
25 import org.xml.sax.InputSource;
26 import org.xml.sax.SAXException;
27 import org.xml.sax.XMLFilter;
28 import org.xml.sax.helpers.XMLFilterImpl;
29
30 import javax.xml.bind.JAXBContext;
31 import javax.xml.bind.Unmarshaller;
32 import javax.xml.bind.UnmarshallerHandler;
33 import javax.xml.parsers.SAXParserFactory;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.util.*;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40
41
42
43
44
45
46
47
48
49
50
51 public class JAXBConfigImpl extends AbstractBaseConfig {
52
53 private static final Logger LOG = Logger.getLogger(JAXBConfigImpl.class);
54
55 private static final String CONFIG_CODED_DEFAULTS = "classpath:org/kuali/rice/core/config-coded-defaults.xml";
56 private static final String IMPORT_NAME = "config.location";
57 private static final String INDENT = " ";
58 private static final String PLACEHOLDER_REGEX = "\\$\\{([^{}]+)\\}";
59
60
61 private static final Random RANDOM = new Random();
62
63 private final List<String> fileLocs = new ArrayList<String>();
64
65 private final Map<String, Object> objects = new LinkedHashMap<String, Object>();
66 private final Properties rawProperties = new Properties();
67 private final Properties resolvedProperties = new Properties();
68
69
70 private final Pattern pattern = Pattern.compile(PLACEHOLDER_REGEX);
71
72 private boolean systemOverride = false;
73
74 public JAXBConfigImpl(){}
75
76 public JAXBConfigImpl(org.kuali.rice.core.config.Config config) {
77 this.copyConfig(config);
78 }
79
80 public JAXBConfigImpl(String fileLoc, org.kuali.rice.core.config.Config config) {
81 this.copyConfig(config);
82 this.fileLocs.add(fileLoc);
83 }
84
85 public JAXBConfigImpl(List<String> fileLocs, org.kuali.rice.core.config.Config config) {
86 this.copyConfig(config);
87 this.fileLocs.addAll(fileLocs);
88
89 }
90
91 public JAXBConfigImpl(String fileLoc) {
92 this.fileLocs.add(fileLoc);
93 }
94
95 public JAXBConfigImpl(List<String> fileLocs) {
96 this.fileLocs.addAll(fileLocs);
97 }
98
99 public JAXBConfigImpl(Properties properties) {
100 this.putProperties(properties);
101 }
102
103 public JAXBConfigImpl(String fileLoc, Properties properties) {
104 this.fileLocs.add(fileLoc);
105 this.putProperties(properties);
106 }
107
108 public JAXBConfigImpl(List<String> fileLocs, Properties properties) {
109 this.fileLocs.addAll(fileLocs);
110 this.putProperties(properties);
111 }
112
113
114
115
116
117
118
119 private void copyConfig(Config config){
120 if(config == null) return;
121
122 if(config instanceof JAXBConfigImpl) {
123 this.rawProperties.putAll(((JAXBConfigImpl) config).rawProperties);
124 this.resolvedProperties.putAll(((JAXBConfigImpl) config).resolvedProperties);
125 }else{
126
127 this.putProperties(config.getProperties());
128
129 }
130 if(config.getObjects() != null)
131 this.objects.putAll(config.getObjects());
132 }
133
134 public Object getObject(String key) {
135 return objects.get(key);
136 }
137
138 public Map<String, Object> getObjects() {
139 return Collections.unmodifiableMap(objects);
140 }
141
142 public Properties getProperties() {
143 return new ImmutableProperties(resolvedProperties);
144 }
145
146 public String getProperty(String key) {
147 return resolvedProperties.getProperty(key);
148 }
149
150 public void overrideProperty(String name, String value) {
151 this.putProperty(name, value);
152 }
153
154
155
156
157
158
159
160 public void putProperty(String key, String value) {
161 this.setProperty(key, replaceVariable(key, value));
162 resolveRawToCache();
163 }
164
165 public void putProperties(Properties properties) {
166 if (properties != null) {
167 for(Object o : properties.keySet()) {
168 this.setProperty((String)o, replaceVariable((String)o, properties.getProperty((String)o)));
169 }
170
171 resolveRawToCache();
172 }
173 }
174
175 public void parseConfig() throws IOException {
176
177 if (fileLocs.size() > 0) {
178
179 if (LOG.isInfoEnabled()) {
180 LOG.info("Loading Rice configs: " + StringUtils.join(fileLocs, ", "));
181 }
182
183 JAXBContext jaxbContext;
184 Unmarshaller unmarshaller;
185
186 try {
187 jaxbContext = JAXBContext.newInstance(org.kuali.rice.core.config.xsd.Config.class);
188 unmarshaller = jaxbContext.createUnmarshaller();
189 } catch (Exception ex) {
190 throw new ConfigurationException("Error initializing JAXB for config", ex);
191 }
192
193
194 configureBuiltIns();
195
196
197 for (String s : fileLocs) {
198 parseConfig(s, unmarshaller, 0);
199 }
200
201
202
203
204 resolveRawToCache();
205
206 if (LOG.isInfoEnabled()) {
207 final StringBuilder log = new StringBuilder();
208 log.append("\n");
209 log.append("####################################\n");
210 log.append("#\n");
211 log.append("# Properties used after config override/replacement\n");
212 log.append("# " + StringUtils.join(fileLocs, ", ") + "\n");
213 log.append("#\n");
214 log.append("####################################\n");
215
216
217
218 SortedSet<String> sorted = new TreeSet<String>();
219 CollectionUtils.addAll(sorted, rawProperties.propertyNames());
220
221 for (String s : sorted) {
222 log.append("Using config Prop " + s + "=[" + ConfigLogger.getDisplaySafeValue(s, this.getProperty(s)) + "]\n");
223 }
224 LOG.info(log);
225 }
226
227 } else {
228 LOG.info("Loading Rice configs: No config files specified");
229 }
230 }
231
232 protected void parseConfig(String filename, Unmarshaller unmarshaller, int depth) throws IOException {
233
234 InputStream in = null;
235
236
237
238 if (StringUtils.isNotEmpty(filename)) {
239 in = RiceUtilities.getResourceAsStream(filename);
240 }
241
242 if (in == null) {
243 final StringBuilder log = new StringBuilder();
244 log.append("\n");
245 log.append("####################################\n");
246 log.append("#\n");
247 log.append("# Configuration file '" + filename + "' not found!\n");
248 log.append("#\n");
249 log.append("####################################\n");
250 LOG.warn(log);
251 } else {
252
253 final String prefix = StringUtils.repeat(INDENT, depth);
254 LOG.info(prefix + "+ Parsing config: " + filename);
255 org.kuali.rice.core.config.xsd.Config config;
256
257 try {
258 config = unmarshal(unmarshaller, in);
259 } catch (Exception ex) {
260 throw new ConfigurationException("Error parsing config file: " + filename, ex);
261 }
262
263 for (Param p : config.getParamList()) {
264
265 String name = p.getName();
266
267 if (name.equals(IMPORT_NAME)) {
268 String configLocation = parseValue(p.getValue(), new HashSet<String>());
269
270 if(configLocation != null){
271 configLocation = configLocation.trim();
272 }
273 parseConfig(configLocation, unmarshaller, depth + 1);
274 } else if(p.isSystem()){
275 if (p.isOverride() || !(System.getProperty(name) != null)){
276 if(p.isRandom()){
277 String randStr = String.valueOf(generateRandomInteger(p.getValue()));
278 System.setProperty(name, randStr);
279 this.setProperty(p.getName(), randStr);
280 if(LOG.isInfoEnabled())
281 {
282 LOG.info("generating random string " + randStr + " for system property " + p.getName());
283 }
284 }else{
285
286
287
288 HashSet<String> set = new HashSet<String>();
289 set.add(p.getName());
290 String value = parseValue(p.getValue(), set);
291 System.setProperty(name, value);
292 this.setProperty(name, value);
293 }
294 }
295 }
296 else if (p.isOverride() || !rawProperties.containsKey(name)) {
297
298 if (p.isRandom()) {
299
300 String randStr = String.valueOf(generateRandomInteger(p.getValue()));
301 this.setProperty(p.getName(), randStr);
302 if(LOG.isInfoEnabled())
303 {
304 LOG.info("generating random string " + randStr + " for property " + p.getName());
305 }
306 } else {
307
308
309
310
311
312
313
314
315
316 String value = replaceVariable(name, p.getValue());
317
318 this.setProperty(name, value);
319 }
320 }
321 }
322
323 LOG.info(prefix + "- Parsed config: " + filename);
324 }
325 }
326
327
328
329
330
331 protected void setProperty(String name, String value){
332 if(LOG.isInfoEnabled()){
333 String oldProp = rawProperties.getProperty(name);
334 if(oldProp != null && !oldProp.equals(value)){
335 LOG.info("Raw Config Override: " + name + "=[" + ConfigLogger.getDisplaySafeValue(name,oldProp) +"]->[" + ConfigLogger.getDisplaySafeValue(name,value) +"]");
336 }
337 }
338 rawProperties.setProperty(name, value);
339 }
340
341 protected String resolve(String key) {
342 return resolve(key, null);
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358 protected String resolve(String key, Set keySet) {
359
360
361 if (keySet != null && keySet.contains(key)) {
362 throw new ConfigurationException("Circular reference in config: " + key);
363 }
364
365 String value = this.rawProperties.getProperty(key);
366
367 if ((value == null || systemOverride) && System.getProperties().containsKey(key)) {
368 value = System.getProperty(key);
369 }
370
371 if (value != null && value.contains("${")) {
372 if(keySet == null) {
373 keySet = new HashSet<String>();
374 }
375 keySet.add(key);
376
377 value = parseValue(value, keySet);
378
379 keySet.remove(key);
380 }
381
382 if(value == null) {
383 value = "";
384 LOG.warn("Property key: '" + key + "' is not available and hence set to empty");
385 }
386
387 return value;
388 }
389
390
391
392
393
394
395
396
397
398
399 protected String parseValue(String value, Set<String> keySet) {
400 String result = value;
401
402 Matcher matcher = pattern.matcher(value);
403
404 while (matcher.find()) {
405
406
407 String key = matcher.group(1);
408
409 String resolved = resolve(key, keySet);
410
411 result = matcher.replaceFirst(Matcher.quoteReplacement(resolved));
412 matcher = matcher.reset(result);
413 }
414
415 return result;
416 }
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 protected String replaceVariable(String name, String value){
441 String regex = "(?:\\$\\{"+ name +"\\})";
442 String temporary = null;
443
444
445
446
447 if(value.contains("${" + name + "}")) {
448 if( (temporary = rawProperties.getProperty(name)) == null ) {
449 temporary = System.getProperty(name);
450 }
451
452 if(temporary != null) {
453 return value.replaceAll(regex, Matcher.quoteReplacement(temporary));
454 }
455 }
456
457 return value;
458 }
459
460
461
462
463
464
465 protected void resolveRawToCache() {
466 if(rawProperties.size() > 0) {
467 Properties oldProps = new Properties(new ImmutableProperties(resolvedProperties));
468
469 resolvedProperties.clear();
470
471 for(Object o : rawProperties.keySet()) {
472 String resolved = resolve((String)o);
473
474 if(LOG.isInfoEnabled()){
475 String oldResolved = oldProps.getProperty((String)o);
476 if(oldResolved != null && !oldResolved.equals(resolved)){
477 String key = (String)o;
478 String unResolved = rawProperties.getProperty(key);
479
480 if(unResolved.contains("$")){
481 LOG.info("Resolved Config Override: " + key + "(" + unResolved +")=[" + ConfigLogger.getDisplaySafeValue(key,oldResolved) +"]->[" + ConfigLogger.getDisplaySafeValue(key,resolved) +"]");
482 }else{
483 LOG.info("Resolved Config Override: " + key + "=[" + ConfigLogger.getDisplaySafeValue(key,oldResolved) +"]->[" + ConfigLogger.getDisplaySafeValue(key,resolved) +"]");
484 }
485 }
486 }
487 resolvedProperties.setProperty((String)o, resolved);
488 }
489 }
490 }
491
492
493
494
495 protected void configureBuiltIns() {
496 this.setProperty("host.ip", RiceUtilities.getIpNumber());
497 this.setProperty("host.name", RiceUtilities.getHostName());
498 }
499
500
501
502
503
504
505
506
507 protected int generateRandomInteger(String rangeSpec) {
508 String[] range = rangeSpec.split("-");
509 if (range.length != 2) {
510 throw new RuntimeException("Invalid range specifier: " + rangeSpec);
511 }
512 int from = Integer.parseInt(range[0].trim());
513 int to = Integer.parseInt(range[1].trim());
514 if (from > to) {
515 int tmp = from;
516 from = to;
517 to = tmp;
518 }
519 int num;
520
521 if (from == to) {
522 num = from;
523 if(LOG.isInfoEnabled())
524 {
525 LOG.info("from==to, so not generating random value for property.");
526 }
527 } else {
528 num = from + RANDOM.nextInt((to - from) + 1);
529 }
530 return num;
531 }
532
533 public boolean isSystemOverride() {
534 return systemOverride;
535 }
536
537
538
539
540
541
542
543
544
545 public void setSystemOverride(boolean systemOverride) {
546 this.systemOverride = systemOverride;
547 }
548
549 protected org.kuali.rice.core.config.xsd.Config unmarshal(Unmarshaller unmarshaller, InputStream in) throws Exception {
550 SAXParserFactory spf = SAXParserFactory.newInstance();
551 spf.setNamespaceAware(true);
552
553 XMLFilter filter = new ConfigNamespaceURIFilter();
554 filter.setParent(spf.newSAXParser().getXMLReader());
555
556 UnmarshallerHandler handler = unmarshaller.getUnmarshallerHandler();
557 filter.setContentHandler(handler);
558
559 filter.parse(new InputSource(in));
560
561 return (org.kuali.rice.core.config.xsd.Config)handler.getResult();
562 }
563
564
565
566
567
568
569
570
571
572
573
574 public class ConfigNamespaceURIFilter extends XMLFilterImpl {
575
576 public static final String CONFIG_URI="http://rice.kuali.org/xsd/core/config";
577
578 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
579 if(StringUtils.isBlank(uri)) {
580 uri = CONFIG_URI;
581 }
582
583 super.startElement(uri, localName, qName, atts);
584 }
585
586 public void endElement(String uri, String localName, String qName) throws SAXException {
587 if(StringUtils.isBlank(uri)) {
588 uri = CONFIG_URI;
589 }
590
591 super.endElement(uri, localName, qName);
592 }
593 }
594
595
596 public void putObject(String key, Object value) {
597 this.objects.put(key, value);
598 }
599
600 public void putObjects(Map<String, Object> objects) {
601 this.objects.putAll(objects);
602 }
603
604 public void removeObject(String key){
605 this.objects.remove(key);
606 }
607
608 public void removeProperty(String key){
609 this.rawProperties.remove(key);
610
611 resolveRawToCache();
612 }
613
614 public void putConfig(Config config) {
615 this.copyConfig(config);
616 }
617
618
619 }