-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathDefaultConfiguration.java
1539 lines (1299 loc) · 41.8 KB
/
DefaultConfiguration.java
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
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
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package com.github.bordertech.config;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConversionException;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SimpleLog;
import org.apache.commons.text.StringSubstitutor;
/**
* <p>
* Implementation of the {@link Configuration} interface.
* </p>
*
* @author Jonathan Austin
* @see Config
* @since 1.0.0
*/
public class DefaultConfiguration implements Configuration {
/**
* If this parameter is defined, it is treated as a comma-separated list of additional resources to load. The
* include is processed immediately.
*/
public static final String INCLUDE = "include";
/**
* If this parameter is defined, it is taken as a (comma-separated) resource to load. The resource is loaded after
* the current (set of) resources is loaded.
*/
public static final String INCLUDE_AFTER = "includeAfter";
/**
* If this parameter is defined and resolves to true as a boolean, then the system properties will be merged at the
* end of the loading process.
*/
public static final String USE_SYSTEM_PROPERTIES = "bordertech.config.parameters.useSystemProperties";
/**
* If merging System Properties this parameter controls if a system property will only overwrite an existing
* property. The default is false.
*
* @deprecated Use {@link #USE_SYSTEM_PREFIXES} to control which properties can be merged
*/
@Deprecated
public static final String USE_SYSTEM_OVERWRITEONLY = "bordertech.config.parameters.useSystemOverWriteOnly";
/**
* If merging System Properties, this parameter can be used to define a list of attribute prefixes that are allowed
* to be merged. The default is allow all System Properties to be merged.
*/
public static final String USE_SYSTEM_PREFIXES = "bordertech.config.parameters.useSystemPrefixes";
/**
* If this parameter is defined and resolves to true as a boolean, then the OS Environment properties will be merged
* at the end of the loading process.
*/
public static final String USE_OSENV_PROPERTIES = "bordertech.config.parameters.useEnvProperties";
/**
* If merging OS Environment Properties, this parameter can be used to define a list of attribute prefixes that are
* allowed to be merged. The default is allow all Environment Properties to be merged.
*/
public static final String USE_OSENV_PREFIXES = "bordertech.config.parameters.useEnvPrefixes";
/**
* If this parameter is set to true, then after loading the parameters, they will be dumped to the console.
*/
public static final String DUMP = "bordertech.config.parameters.dump.console";
/**
* If this parameter is set, then after loading the parameters, they will be dumped to the specified file.
*/
public static final String DUMP_FILE = "bordertech.config.parameters.dump.file";
/**
* If this parameter is set, it will be used as the environment suffix for each property lookup.
*
* @deprecated Use {@link #PROFILE_PROPERTY} to define the profile property
*/
@Deprecated
public static final String ENVIRONMENT_PROPERTY = "bordertech.config.environment";
/**
* If this parameter is set, it will be used as the environment suffix for each property lookup.
*/
public static final String PROFILE_PROPERTY = "bordertech.config.profile";
/**
* Parameters with this prefix will be dumped into the System parameters. This feature is for handling recalcitrant
* 3rd party software only - not for general use!!!
*/
public static final String SYSTEM_PARAMETERS_PREFIX = "bordertech.config.parameters.system.";
/**
* Logger for debug information.
*/
private static final Log LOG = new SimpleLog("DefaultConfig");
/**
* If this parameter is defined and resolves to true as a boolean, then the system properties will be merged at the
* end of the loading process.
*
* @deprecated use {@link #USE_SYSTEM_PROPERTIES} instead
*/
@Deprecated
private static final String LEGACY_USE_SYSTEM_PROPERTIES = "bordertech.wcomponents.parameters.useSystemProperties";
/**
* If this parameter is set to true, then after loading the parameters, they will be dumped to the console.
*
* @deprecated use {@link #DUMP} property instead
*/
@Deprecated
private static final String LEGACY_DUMP = "bordertech.wcomponents.parameters.dump.console";
/**
* Parameters with this prefix will be dumped into the System parameters. This feature is for handling recalcitrant
* 3rd party software only - not for general use!!!
*
* @deprecated use {@link #SYSTEM_PARAMETERS_PREFIX} instead
*/
@Deprecated
private static final String LEGACY_SYSTEM_PARAMETERS_PREFIX = "bordertech.wcomponents.parameters.system.";
/**
* The prefix output before log messages.
*/
private static final String LOG_PREFIX = "PARAM_DEBUG: ";
// -----------------------------------------------------------------------------------------------------------------
// State used during loading of parameters
/**
* The messages logged during loading of the configuration.
*/
private final StringBuilder messages = new StringBuilder();
/**
* A generic object that allows us to synchronized refreshes. Required so that gets and refreshes are threadsafe
*/
private final Object lockObject = new Object();
// -----------------------------------------------------------------------------------------------------------------
// Implementation
/**
* Resource load order.
*/
private final String[] resourceLoadOrder;
/**
* Hold the current profile (if set).
*/
private String currentProfile = null;
/**
* Our backing store is a Map object.
*/
private Map<String, String> backing;
/**
* Explicitly cache booleans for flag look-up speed.
*/
private Set<String> booleanBacking;
/**
* Stores "explanations" of where each setting comes from. Each parameter will have a history, explaining all the
* locations where that parameter was defined, in reverse order (so the first entry is the defining entry).
*/
private Map<String, String> locations;
/**
* Cache of subcontexts, by {true,false}-prefix.
*/
private Map<String, Properties> subcontextCache;
/**
* Properties added at runtime.
*/
private IncludeProperties runtimeProperties;
/**
* Creates a Default Configuration.
*/
public DefaultConfiguration() {
this(InitHelper.getDefaultResourceLoadOrder());
}
/**
* Creates a Default Configuration with the specified resour ce order.
*
* @param resourceLoadOrder the resource order
*/
public DefaultConfiguration(final String... resourceLoadOrder) {
if (resourceLoadOrder == null || resourceLoadOrder.length == 0 || Arrays
.stream(resourceLoadOrder)
.anyMatch(StringUtils::isBlank)) {
this.resourceLoadOrder = InitHelper.getDefaultResourceLoadOrder();
} else {
this.resourceLoadOrder = resourceLoadOrder;
}
initialiseInstanceVariables();
load();
}
/**
* Copies information from the input stream to the output stream using a specified buffer size.
*
* @param in the source stream.
* @param out the destination stream.
* @throws IOException if there is an error reading or writing to the streams.
*/
private static void copyStream(final InputStream in, final OutputStream out) throws IOException {
final byte[] buf = new byte[2048];
int bytesRead = in.read(buf);
while (bytesRead != -1) {
out.write(buf, 0, bytesRead);
bytesRead = in.read(buf);
}
out.flush();
}
// -----------------------------------------------------------------------------------------------------------------
/**
* Splits the given comma-delimited string into an an array. Leading/trailing spaces in list items will be trimmed.
*
* @param list the String to split.
* @return the split version of the list.
*/
private String[] parseStringArray(final String list) {
if (StringUtils.isBlank(list)) {
return new String[0];
} else {
return list.trim().split("\\s*,\\s*");
}
}
/**
* This method initialises most of the instance variables.
*/
private void initialiseInstanceVariables() {
backing = new HashMap<>();
booleanBacking = new HashSet<>();
locations = new HashMap<>();
// subContextCache is updated on the fly so ensure no concurrent modification.
subcontextCache = Collections.synchronizedMap(new HashMap<>());
runtimeProperties = new IncludeProperties("Runtime: property added at runtime");
currentProfile = null;
}
/**
* Load the backing from the properties file visible to our classloader, plus the filesystem.
*/
private void load() {
recordMessage("Loading parameters");
File cwd = new File(".");
String workingDir;
try {
workingDir = cwd.getCanonicalPath();
} catch (IOException ex) {
workingDir = "UNKNOWN";
}
recordMessage("Working directory is " + workingDir);
for (String resourceName : resourceLoadOrder) {
loadTop(resourceName);
}
if (isUseSystemProperties()) {
recordMessage("Loading from system properties");
loadSystemProperties();
}
if (isUseOsEnvProperties()) {
recordMessage("Loading from environment properties");
loadEnvironmentProperties();
}
handlePropertySubstitution();
checkProfileProperty();
// Dump Header Info
LOG.info(getDumpHeader());
// Dump properties
if (isDumpPropertiesConsole() || isDumpPropertiesFile()) {
handleDumpPropertyDetails();
}
// We don't want the StringBuilder hanging around after 'DUMP'.
clearMessages();
// Now move any parameters with the system parameters prefix into the real system parameters.
Properties systemProperties = getSubProperties(SYSTEM_PARAMETERS_PREFIX, true);
System.getProperties().putAll(systemProperties);
// LEGACY
systemProperties = getSubProperties(LEGACY_SYSTEM_PARAMETERS_PREFIX, true);
System.getProperties().putAll(systemProperties);
}
/**
* @return true if load system properties into config
*/
private boolean isUseSystemProperties() {
return getBoolean(USE_SYSTEM_PROPERTIES) || getBoolean(LEGACY_USE_SYSTEM_PROPERTIES);
}
/**
* @return true if load OS Environment properties into config
*/
private boolean isUseOsEnvProperties() {
return getBoolean(USE_OSENV_PROPERTIES);
}
/**
* @return true if dump properties to the console
*/
private boolean isDumpPropertiesConsole() {
return getBoolean(DUMP) || getBoolean(LEGACY_DUMP);
}
/**
* @return true if dump properties to a file
*/
private boolean isDumpPropertiesFile() {
return !StringUtils.isEmpty(getDumpFileLocation());
}
/**
* @return the dump properties to file
*/
private String getDumpFileLocation() {
return get(DUMP_FILE, "");
}
/**
* Dump the property details.
*/
private void handleDumpPropertyDetails() {
String loadMessages = getDumpLoadMessages();
String propMessages = getDumpPropertyDetails();
// Dump to console
if (isDumpPropertiesConsole()) {
LOG.info(loadMessages);
LOG.info(propMessages);
}
// Dump to File
if (isDumpPropertiesFile()) {
String dest = getDumpFileLocation();
try (FileOutputStream fos = new FileOutputStream(dest); PrintStream stream = new PrintStream(fos)) {
stream.println(loadMessages);
stream.println(propMessages);
} catch (IOException e) {
recordException(e);
}
}
}
/**
* @return debugging information for logging
*/
private String getDumpHeader() {
File cwd = new File(".");
String workingDir;
try {
workingDir = cwd.getCanonicalPath();
} catch (IOException ex) {
workingDir = "UNKNOWN";
}
String codesourceStr = "";
// Try to be sneaky and print the codesource location (for orientation of user)
try {
ProtectionDomain domain = getClass().getProtectionDomain();
if (domain != null) {
CodeSource codesource = domain.getCodeSource();
codesourceStr = codesource == null ? "" : "Code location of Config implementation: " + codesource.getLocation();
}
} catch (Exception failed) {
codesourceStr = "Could not determine location of Config implementation [" + failed.getMessage() + "].";
}
StringBuilder info = new StringBuilder();
info.append("----Config: Info start----\n");
info.append(codesourceStr);
info.append("\nWorking directory is: ");
info.append(workingDir);
info.append("\nTo dump all params to the console set ");
info.append(DUMP);
info.append(" to true; current value is ");
info.append(isDumpPropertiesConsole());
info.append("\nTo dump all params to a file set ");
info.append(DUMP_FILE);
info.append(" to file location; current value is ");
info.append(getDumpFileLocation());
info.append("\nLOGGING can be controlled by configuring org.apache.commons.logging.impl.SimpleLog.");
info.append("\nSimpleLog writes to System.err by default.");
info.append("\n----Config: Info end------");
return info.toString();
}
/**
* @return dump of all properties loaded with their location history
*/
private String getDumpPropertyDetails() {
StringBuilder info = new StringBuilder();
info.append("----Config: Properties loaded start----\n");
for (String key : new TreeSet<>(backing.keySet())) {
String value = backing.get(key);
String history = locations.get(key);
info.append(LOG_PREFIX);
info.append(key);
info.append(" = ");
info.append(value);
info.append(" (");
info.append(history);
info.append(")\n");
}
info.append("----Config: Properties loaded end----\n");
return info.toString();
}
/**
* @return debugging load messages
*/
private String getDumpLoadMessages() {
StringBuilder info = new StringBuilder();
info.append("----Config: Load messages start----\n");
info.append(messages.toString());
info.append("----Config: Load messages end----\n");
return info.toString();
}
/**
* Loading of "top level" resources is different to the general recursive case, since it is only at the top level
* that we check for the includeAfter parameter.
*
* @param resourceName the path of the resource to load from.
*/
private void loadTop(final String resourceName) {
load(resourceName);
if (backing.containsKey(INCLUDE_AFTER)) {
// First, do substitution on the INCLUDE_AFTER
substitute(INCLUDE_AFTER);
// Now split and process
String[] includeAfter = parseStringArray(get(INCLUDE_AFTER));
backing.remove(INCLUDE_AFTER);
for (String after : includeAfter) {
loadTop(after);
}
}
}
/**
* Try loading the given resource name. There may be several resources corresponding to that name...
*
* @param resourceName the path of the resource to load from.
*/
private void load(final String resourceName) {
boolean found = false;
try {
// Load the resource/s from the class loader
List<URL> urls = findClassLoaderResources(resourceName);
if (!urls.isEmpty()) {
found = true;
List<Pair<URL, byte[]>> contents = getResourceContents(urls);
loadResourceContents(contents);
}
// Load the resource as a FILE from the user home directory (if exists)
if (loadFile(FileUtils.getFile(SystemUtils.getUserHome(), resourceName))) {
found = true;
}
// Load the resource as a FILE from the user directory (if exists)
if (loadFile(FileUtils.getFile(SystemUtils.getUserDir(), resourceName))) {
found = true;
}
if (!found) {
recordMessage("Did not find resource " + resourceName);
}
} catch (IOException ex) {
// Most likely a "Malformed uxxxx encoding." error, which is
// usually caused by a developer forgetting to escape backslashes
recordException(ex);
}
}
private boolean loadFile(final File file) throws IOException {
if (file.exists()) {
loadFileResource(file);
return true;
}
return false;
}
/**
* Find the resources from the class loader as there maybe more than one.
*
* @param resourceName the resource name to load
* @return the list of URLs from the class loader
* @throws IOException an IO Exception has occurred
*/
private List<URL> findClassLoaderResources(final String resourceName) throws IOException {
// Try classloader - load the resources in reverse order of the enumeration. Since later-loaded resources
// override earlier-loaded ones, this better corresponds to the usual classpath behaviour.
ClassLoader classloader = getParamsClassLoader();
recordMessage("Using classloader " + classloader);
List<URL> urls = new ArrayList<>();
for (Enumeration<URL> res = classloader.getResources(resourceName); res.hasMoreElements();) {
urls.add(res.nextElement());
}
recordMessage("Resource " + resourceName + " was found " + urls.size() + " times");
return urls;
}
/**
* Retrieve the resource contents.
*
* @param urls the list of URLS to load
* @return a list of URLs and resource contents
* @throws IOException an IO Exception has occurred
*/
private List<Pair<URL, byte[]>> getResourceContents(final List<URL> urls) throws IOException {
// Sometimes the same URL will crop up several times (because of redundant entries in classpaths). Also,
// sometimes the same file appears under several URLS (because it's packaged into a jar and also a classes
// directory, perhaps). In these circumstances we really only want to load the resource once - we load the
// first one and then ignore later ones.
Map<String, String> loadedFiles = new HashMap<>();
// Build up a list of the byte arrays from the files that we then process.
List<Pair<URL, byte[]>> contentsList = new ArrayList<>();
// This processes from the front-of-classpath to end-of-classpath since end-of-classpath ones appear last in
// the enumeration
for (URL url : urls) {
// Load the contents of the resource, for comparison with existing resources.
byte[] urlContentBytes;
try (InputStream urlContentStream = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
copyStream(urlContentStream, baos);
urlContentBytes = baos.toByteArray();
}
String urlContent = new String(urlContentBytes, StandardCharsets.UTF_8);
// Check if we have already loaded this file.
if (loadedFiles.containsKey(urlContent)) {
recordMessage("Skipped url " + url + " - duplicate of " + loadedFiles.get(urlContent));
continue;
}
loadedFiles.put(urlContent, url.toString());
contentsList.add(new ImmutablePair<>(url, urlContentBytes));
}
return contentsList;
}
/**
* Load the resource contents.
*
* @param contentsList the list of URLs and resource content
* @throws IOException an IO Exception occurred
*/
private void loadResourceContents(final List<Pair<URL, byte[]>> contentsList) throws IOException {
// Load in reverse order
for (int i = contentsList.size() - 1; i >= 0; i--) {
URL url = contentsList.get(i).getLeft();
byte[] buff = contentsList.get(i).getRight();
recordMessage("Loading from url " + url + "...");
try (ByteArrayInputStream in = new ByteArrayInputStream(buff)) {
// Use the "IncludeProperties" to load properties into us one at a time....
new IncludeProperties(url.toString()).load(in);
}
}
}
/**
* Load the file resource.
*
* @param file the file to load
* @throws IOException an IO Exception occurred
*/
private void loadFileResource(final File file) throws IOException {
final String fileName = filename(file);
recordMessage("Loading from file " + fileName + "...");
try (FileInputStream fin = new FileInputStream(file); BufferedInputStream bin = new BufferedInputStream(fin)) {
// Use the "IncludeProperties" to load properties into us, one at a time....
new IncludeProperties("file:" + fileName).load(bin);
}
}
/**
* Retrieves the canonical path for a given file.
*
* @param aFile the file to get the canonical path for.
* @return the canonical path to the given file, or <code>"UNKNOWN FILE"</code> on error.
*/
private String filename(final File aFile) {
try {
return aFile.getCanonicalPath();
} catch (IOException ex) {
recordException(ex);
return "UNKNOWN FILE";
}
}
/**
* @return the ClassLoader instance for this class.
*/
private ClassLoader getParamsClassLoader() {
// Ideally we could just use the defining classloader for this class. But unfortunately we have to deal with
// "legacy" deployment styles where this class is visible to the container's system class loader (ie in the
// system class path), instead of being deployed within the application.
//
// One idea is to use the context class loader; but iPlanet does not set this usefully, so we have to fool
// about...
// First, try the context class loader.
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
recordMessage("No context classloader had been set");
loader = getClass().getClassLoader();
return loader;
}
// Are we visible to this class loader?
try {
Class test = loader.loadClass(getClass().getName());
if (test == getClass()) {
recordMessage("Visible to ContextClassLoader");
// Beauty - context class loader looks good
return loader;
} else {
// Rats - this should not happen with a sane application server
recordMessage("Whoa - is visible to context class loader, but it gives a different class");
// If this happens we need to investigate further, but for the time being we'll use the context class
// loader
return loader;
}
} catch (ClassNotFoundException ex) {
recordMessage("Not visible to context class loader (" + loader + "):" + ex.getMessage());
loader = getClass().getClassLoader();
return loader;
}
}
/**
* Load the System Properties into Config.
*/
private void loadSystemProperties() {
boolean overWriteOnly = getBoolean(USE_SYSTEM_OVERWRITEONLY, false);
List<String> allowedPrefixes = Arrays.asList(getStringArray(USE_SYSTEM_PREFIXES));
System
.getProperties()
.forEach((key, value) -> mergeExternalProperty("System Properties",
(String) key,
(String) value,
overWriteOnly,
allowedPrefixes));
}
/**
* Load the OS Environment Properties into Config.
*/
private void loadEnvironmentProperties() {
List<String> allowedPrefixes = Arrays.asList(getStringArray(USE_OSENV_PREFIXES));
System
.getenv()
.forEach((key, value) -> mergeExternalProperty("Environment Properties", key, value, false, allowedPrefixes));
}
/**
* Merge the external property.
*
* @param location the location of the properties
* @param key the property key
* @param value the property value
* @param overWriteOnly true if only overwrite existing properties
* @param allowedPrefixes the list of allowed property prefixes
*/
private void mergeExternalProperty(
final String location,
final String key,
final String value,
final boolean overWriteOnly,
final List<String> allowedPrefixes) {
// Check for "include" keys (should not come from System or Environment Properties)
if (INCLUDE.equals(key) || INCLUDE_AFTER.equals(key)) {
return;
}
// Check allowed prefixes
if (!isAllowedKeyPrefix(allowedPrefixes, key)) {
return;
}
// Check overwrite only
if (overWriteOnly && get(key) == null) {
return;
}
// Load property
put(key, value, location);
}
/**
* Check allowed prefixes.
*
* @param allowedPrefixes the list of allowed prefixes
* @param key the key to check
* @return true if the key is an allowed prefix
*/
private boolean isAllowedKeyPrefix(final List<String> allowedPrefixes, final String key) {
// If no prefixes defined, then ALL keys are allowed
if (allowedPrefixes == null || allowedPrefixes.isEmpty()) {
return true;
}
// Check allowed prefixes
for (String prefix : allowedPrefixes) {
if (key.startsWith(prefix)) {
return true;
}
}
return false;
}
/**
* Logs an exception.
*
* @param throwable the exception to log.
*/
private void recordException(final Throwable throwable) {
LOG.error("Error loading config. " + throwable.getMessage(), throwable);
}
/**
* Records a message in the internal log buffer.
*
* @param msg the message log.
*/
private void recordMessage(final String msg) {
messages.append(msg).append('\n');
}
/**
* Clears the logged message buffer.
*/
private void clearMessages() {
messages.setLength(0);
}
/**
* Performs value substitution for the given key. For values containing ${...} strings, we substitute if the stuff
* in the {...} is a defined key.
*
* @param aKey the key to run the substitution for.
*/
private void substitute(final String aKey) {
String value = backing.get(aKey);
String newValue = StringSubstitutor.replace(value, backing);
if (StringUtils.equals(value, newValue)) {
return;
}
put(aKey, newValue, "substitution of ${" + value + "}");
}
private void put(final String key, final String value, final String historyMsg) {
backing.put(key, value);
if (BooleanUtils.toBoolean(value)) {
booleanBacking.add(key);
} else {
booleanBacking.remove(key);
}
String history = locations.get(key);
if (history == null) {
history = historyMsg;
} else {
history = historyMsg + "; " + history;
}
locations.put(key, history);
}
/**
* Reload the properties to their initial state.
*/
public void refresh() {
synchronized (lockObject) {
// Now reset this object back to its initial state.
initialiseInstanceVariables();
// Load all the parameters from scratch.
load();
// Finally, notify all the listeners that have registered with this object that a change in properties has
// occurred.
Config.notifyListeners();
}
}
/**
* @return a copy of the current properties
*/
public Properties getProperties() {
// Don't return the backing directly; make a copy so that the caller can't change us...
Properties copy = new Properties();
copy.putAll(backing);
return copy;
}
// -----------------------------------------------------------------------------------------------------------------
// The rest of this class is the implementation of Configuration interface
@Override
public int getInt(final String key, final int defolt) {
try {
String value = get(key);
if (value == null) {
return defolt;
}
return Integer.parseInt(value);
} catch (NumberFormatException ex) {
throw new ConversionException(ex);
}
}
@Override
public int getInt(final String key) {
return getInt(key, 0);
}
@Override
public short getShort(final String key) {
return getShort(key, (short) 0);
}
@Override
public short getShort(final String key, final short defaultValue) {
try {
String value = get(key);
if (value == null) {
return defaultValue;
}
return Short.parseShort(value);
} catch (NumberFormatException ex) {
throw new ConversionException(ex);
}
}
@Override
public Short getShort(final String key, final Short defaultValue) {
try {
String value = get(key);
if (value == null) {
return defaultValue;
}
return Short.valueOf(value);
} catch (NumberFormatException ex) {
throw new ConversionException(ex);
}
}
@Override
public void addProperty(final String key, final Object value) {
if (containsKey(key)) {
String newValue = get(key) + ',' + (value == null ? "" : value);
addOrModifyProperty(key, newValue);
} else {
addOrModifyProperty(key, value == null ? null : value.toString());
}
}
@Override
public void clear() {
backing.clear();
handlePropertiesChanged();
}
@Override
public void clearProperty(final String key) {
backing.remove(key);
handlePropertiesChanged();
}
@Override
public boolean containsKey(final String key) {
if (useProfileKey(key) && backing.containsKey(getProfileKey(key))) {
return true;
}
return backing.containsKey(key);
}
@Override
public BigDecimal getBigDecimal(final String key) {
return getBigDecimal(key, new BigDecimal("0.0"));
}
@Override
public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
try {
String value = get(key);
if (value == null) {
return defaultValue;
}
return new BigDecimal(value);
} catch (NumberFormatException ex) {
throw new ConversionException(ex);
}
}
@Override
public BigInteger getBigInteger(final String key) {
return getBigInteger(key, BigInteger.ZERO);
}
@Override
public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
try {
String value = get(key);
if (value == null) {
return defaultValue;
}