1 package org.lsst.ccs.bootstrap.resources;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.FileNotFoundException;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.HashSet;
9 import java.util.ListIterator;
10 import java.util.Properties;
11 import java.util.Set;
12 import org.lsst.ccs.bootstrap.Bootstrap;
13 import org.lsst.ccs.bootstrap.BootstrapUtils;
14
15
16 /**
17 * Utility methods for loading Bootstrap Resources.
18 * <p>
19 * The utility methods provided in this class are meant to be used throughout the CCS environment
20 * to load resources in the Bootstrap environment.
21 * <p>
22 * For a description on how to control the Bootstrap environment please refer to the
23 * following <a href="https://confluence.slac.stanford.edu/x/-upFC">manual</a>.
24 *
25 * @author The LSST CCS Team
26 */
27 public abstract class BootstrapResourceUtils {
28
29
30 /**
31 * Return the Bootstrap System Properties object.
32 * <p>
33 * This function builds a Properties object by searching and chaining properties
34 * according to the following search criteria
35 * <ul>
36 * <li><b>System Properties</b>: System Properties are the first Properties
37 * to be added to the merge chain.</li>
38 * <li><b>Bootstrap Resources search</b> : The Bootstrap Resources directories
39 * (defined by the environment variable CCS_RESOURCE_PATH) are searched in the
40 * reversed order. In each directory properties are merged by searching for
41 * application and global properties files (these files are loaded from the
42 * file system). </li>
43 * <li><b>Command line </b>: Properties specified from the command line with
44 * the -D option are added to the top of the Properties chain.</li>
45 * </ul>
46 *
47 * @return The merged Properties object.
48 */
49 public static Properties getBootstrapSystemProperties() {
50 if( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
51 System.out.println("*** Getting Bootstrap System Properties");
52 }
53 return buildBootstrapProperties(null, null, true);
54 }
55
56 /**
57 * Utility function to find the top most property file in the user provided
58 * resource directories (as specified in the CCS_RESOURCE_PATH environment
59 * variable). If the CCS_RESOURCE_PATH environment variable is not specified
60 * null will be returned. If specified the first property file will be returned
61 * scanning the resource directories in CCS_RESOURCE_PATH left to right.
62 *
63 * @param propertyFileName The mane of the property file to get
64 * @return The full path of the property file.
65 *
66 */
67 public static String getPathOfPropertiesFileInUserResourceDirectories(String propertyFileName) {
68
69 if ( ! propertyFileName.endsWith(".properties") ) {
70 propertyFileName = propertyFileName += ".properties";
71 }
72
73 ResourcesTree tree = BootstrapUtils.getBootstrapResourcesTree();
74
75 ListIterator<ResourceDirectory> resourceDirs = tree.getResourceDirectoryList().listIterator();
76 while (resourceDirs.hasNext() ) {
77 ResourceDirectory resourceDir = resourceDirs.next();
78 if ( ! resourceDir.isDistributionDir() ) {
79 if ( resourceDir.hasResource(propertyFileName) ) {
80 return resourceDir.getResouceDirectoryPath()+propertyFileName;
81 }
82 }
83 }
84
85 return null;
86 }
87
88 /**
89 * Utility function to get the top User defined Resource Directory.
90 * This is the left-most directory defined in environment variable CCS_RESOURCE_PATH.
91 * If not defined, null is returned.
92 * @return the top user defined Resource directory. Null if not defined.
93 */
94
95 public static String getTopUserResourceDirectory() {
96 ResourcesTree tree = BootstrapUtils.getBootstrapResourcesTree();
97
98 ListIterator<ResourceDirectory> resourceDirs = tree.getResourceDirectoryList().listIterator();
99 while (resourceDirs.hasNext() ) {
100 ResourceDirectory resourceDir = resourceDirs.next();
101 if ( ! resourceDir.isDistributionDir() ) {
102 return resourceDir.getResouceDirectoryPath();
103 }
104 }
105 return null;
106 }
107
108
109 /**
110 * Utility function to fetch Properties in the Bootstrap environment.
111 * <p>
112 * This function builds a Properties object by searching and chaining properties
113 * according to the following search criteria
114 * <ul>
115 * <li><b>Classpath search</b>: getResourceAsStream is invoked on the provided class.
116 * The method is first invoked for fileName. If not input stream is found it is then invoked for
117 * "/"+fileName</li>
118 * <li><b>System Properties</b>: if the parameter includeSystem is set to true, System
119 * Properties are added to the chain</li>
120 * <li><b>Bootstrap Resources search</b>:
121 * The Bootstrap Resources directories (defined by the environment variable
122 * CCS_RESOURCE_PATH) are searched in the reversed order. In each directory
123 * properties are merged by searching for application, global and topic
124 * (defined by fileName) properties files (these files are loaded from the
125 * file system). If fileName is a path to a properties file, two searches
126 * are performed for each resource directory: the first one is for the fully
127 * qualified path; if this yields no result, the properties file is then
128 * searched at the root of each resource directory.</li>
129 * <li><b>Command line</b>:
130 * Properties specified from the command line with the -D option are added
131 * to the top of the Properties chain.</li>
132 * </ul>
133 *
134 * @param fileName The name of the properties file to load. This can be a path.
135 * @param includeSystem If true System Properties are chained after the classpath search.
136 * @param clazz The class on which getResourceAsStream is invoked for the classpath search. If null BootstrapResourceUtils.class is used.
137 * @return The merged Properties object.
138 *
139 */
140 public static Properties getBootstrapProperties(String fileName, Class clazz) {
141 if (clazz == null) {
142 clazz = Bootstrap.getLoaderClass();
143 }
144 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
145 System.out.println("*** Looking for Properties "+fileName);
146 }
147 InputStream in = getResourceFromClassPath(clazz, fileName);
148 return buildBootstrapProperties(in, fileName, false);
149 }
150
151 /**
152 * Utility function to fetch Properties in the Bootstrap environment.
153 * <p>
154 * This function is similar to {@link #getBootstrapProperties(java.lang.String, boolean, java.lang.Class) }.
155 * In this case the Class object is set to null and it defaults to BootstrapResourceUtils.class.
156 *
157 * @see #getBootstrapProperties(java.lang.String, boolean, java.lang.Class)
158 * @param fileName The name of the properties file to load. This can be a path.
159 * @param includeSystem If true System Properties are chained after the classpath search.
160 * @return The merged Properties object.
161 *
162 */
163 public static Properties getBootstrapProperties(String fileName) {
164 return getBootstrapProperties(fileName, Bootstrap.getLoaderClass());
165 }
166
167 /**
168 * Utility function to fetch Properties in the Bootstrap environment.
169 * <p>
170 * This function is similar to {@link #getBootstrapProperties(java.lang.String, boolean, java.lang.Class) } but
171 * for the fact that a ClassLoader is used for the <b>Classpath search</b>, rather than a Class.
172 *
173 * @see #getBootstrapProperties(java.lang.String, boolean, java.lang.Class)
174 * @param fileName The name of the properties file to load. This can be a path.
175 * @param includeSystem If true System Properties are chained after the classpath search.
176 * @param classLoader The classLoader on which getResourceAsStream is invoked for the classpath search.
177 * @return The merged Properties object.
178 *
179 */
180 public static Properties getBootstrapProperties(String fileName, ClassLoader classLoader) {
181 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet()) {
182 System.out.println("*** Looking for Properties "+fileName);
183 }
184 InputStream in = getResourceFromClassPath(classLoader, fileName);
185 return buildBootstrapProperties(in, fileName, false);
186 }
187
188 private static Properties buildBootstrapProperties(InputStream in, String fileName, boolean includeSystem) {
189 ResourcesTreeProperties props = null;
190
191 if (in != null && fileName != null) {
192 props = new ResourcesTreeProperties("ClassPath " + fileName, null, null);
193 try {
194 props.load(in);
195 } catch (IOException ioe) {
196 throw new RuntimeException("Problem loading properties from classpath", ioe);
197 }
198 }
199
200 String shortName = getShortNameForFileName(fileName);
201
202 String applicationLevel = Bootstrap.getBootstrapApplication();
203 String topicLevel = fileName;
204
205 if (BootstrapUtils.getBootstrapListOfApplications().contains(shortName)) {
206 applicationLevel = shortName;
207 topicLevel = null;
208 }
209
210 props = (ResourcesTreeProperties) ResourcesUtils.getMergedProperties(BootstrapUtils.getBootstrapResourcesTree(), new String[]{topicLevel, "ccsGlobal", applicationLevel}, includeSystem, props);
211 if (!Bootstrap.getCmdLineProperties().isEmpty()) {
212 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
213 System.out.println("*** Adding command line properties to properties chain.");
214 }
215 ResourcesTreeProperties cmdLinePros = new ResourcesTreeProperties("Command Line Properties", null, props);
216 cmdLinePros.putAll(Bootstrap.getCmdLineProperties());
217 props = cmdLinePros;
218 }
219
220 //If it's a test environment add all System properties that start with "bootstrap.test."
221 if ( "true".equals(System.getProperty("org.lsst.ccs.bootstrap.test") ) ) {
222 Properties testProperties = System.getProperties();
223 for ( String key : testProperties.stringPropertyNames() ) {
224 if ( key.startsWith("bootstrap.test.") ) {
225 props.put(key.replace("bootstrap.test.",""), testProperties.getProperty(key));
226 }
227 }
228
229 }
230 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
231 ResourcesUtils.printProperties(props);
232 }
233 return props;
234
235 }
236
237
238 /**
239 * Get an InputStream corresponding to the Bootstrap Properties object.
240 * <p>
241 * This function is similar to {@link #getBootstrapProperties(java.lang.String, boolean, java.lang.Class) }.
242 * Rather than a Properties object it returns the InputStream corresponsing to
243 * that Properties object. This is done by invoking {@link java.util.Properties#store(java.io.OutputStream, java.lang.String) }
244 * on a ByteArrayOutputStream and then returning an ByteArrayInputStream.
245 * <p>
246 *
247 * @see BootstrapResourceUtils#getBootstrapProperties(java.lang.String, boolean, java.lang.Class)
248 * @param fileName The name of the properties file to load. This can be a path.
249 * @param includeSystem If true System Properties are chained after the classpath search.
250 * @param clazz The class on which getResourceAsStream is invoked for the classpath search. If null BootstrapResourceUtils.class is used.
251 * @return
252 */
253 public static InputStream getBootstrapPropertiesInputStream(String fileName, Class clazz) {
254 Properties props = getBootstrapProperties(fileName, clazz);
255 if (props == null) {
256 return null;
257 }
258
259 Properties outProps = props;
260 if ( props instanceof ResourcesTreeProperties ) {
261 outProps = new Properties();
262 ((ResourcesTreeProperties)props).copyProperties(outProps);
263 }
264
265 InputStream is = null;
266 ByteArrayOutputStream output = new ByteArrayOutputStream();
267 try {
268 outProps.store(output, null);
269 is = new ByteArrayInputStream(output.toByteArray());
270 } catch (IOException ex) {
271 throw new RuntimeException(ex);
272 }
273
274 return is;
275 }
276
277 /**
278 * Utility functions to look for resources (excluding Properties) in the Bootstrap environment.
279 * <p>
280 * This function will search for the desired resource following the search criteria described below.
281 * It will return an InputStream for the first resource found.
282 * <p>
283 * Search Criteria:
284 * <ul>
285 * <li><b>Bootstrap Resources search</b>: The Bootstrap Resources directories (defined by the
286 * environment variable CCS_RESOURCE_PATH) are searched in the defined
287 * order. If fileName is a path to a resource, two searches are performed
288 * for each resource directory: the first one is for the fully qualified
289 * path; if this yields no result, the resource file is then searched at the
290 * root of each resource directory.</li>
291 * <li><b>Classpath search</b>:
292 * getResourceAsStrean is invoked on the provided clazz. The method is first invoked for
293 * fileName. If not input stream is found it is then invoked for "/"+fileName</li>
294 * </ul>
295 * <p>
296 * For Properties refer to {@link #getBootstrapProperties(java.lang.String, boolean,java.lang.Class).
297 *
298 * @param fileName The name of the resource. This can be a path.
299 * @param clazz The class on which getResourceAsStream is invoked for the
300 * classpath search. If null BootstrapResourceUtils.class is used.
301 * @return The InputStream of the first found resource.
302 *
303 */
304 public static InputStream getBootstrapResource(String resourceName, Class clazz) {
305 if (clazz == null) {
306 clazz = Bootstrap.getLoaderClass();
307 }
308 if (resourceName.endsWith("properties")) {
309 throw new RuntimeException("Bootstrap Properties must be fetched with method: getBootstrapProperties");
310 }
311
312 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
313 System.out.println("*** Looking for resource "+resourceName+" for Class "+clazz);
314 }
315
316 InputStream in = null;
317
318 try {
319 in = ResourcesUtils.getResourceFromResourceTree(BootstrapUtils.getBootstrapResourcesTree(), resourceName);
320 } catch (FileNotFoundException fnfe) {
321 throw new RuntimeException(fnfe);
322 }
323
324 if (in != null) {
325 return in;
326 } else {
327 return getResourceFromClassPath(clazz, resourceName);
328 }
329
330 }
331
332 /**
333 * Utility functions to look for resources (excluding Properties) in the Bootstrap environment.
334 * <p>
335 * Internally it invokes {@link getBootstrapResource(java.lang.String,java.lang.Class)} with Class
336 * set to BootstrapResourceUtils.class.
337 *
338 * @see #getBootstrapResource(java.lang.String, java.lang.Class)
339 * @param fileName The name of the resource. This can be a path.
340 * @return The InputStream of the first found resource.
341 *
342 */
343 public static InputStream getBootstrapResource(String resourceName) {
344 return getBootstrapResource(resourceName, Bootstrap.getLoaderClass());
345 }
346
347
348 /**
349 * Utility functions to look for resources (excluding properties)
350 * in the Bootstrap environment.
351 * <p>
352 * This function is similar to {@link #getBootstrapResource(java.lang.String, java.lang.Class) } but
353 * for the fact that a ClassLoader is used for the <b>Classpath search</b>, rather than a Class.
354 *
355 * @see #getBootstrapResource(java.lang.String, java.lang.Class)
356 * @param fileName The name of the resource. This can be a path.
357 * @param classLoader The classLoader on which getResourceAsStream is invoked for the classpath search.
358 * @return The InputStream of the first found resource.
359 *
360 */
361 public static InputStream getBootstrapResource(String resourceName, ClassLoader classLoader) {
362 if (resourceName.endsWith("properties")) {
363 throw new RuntimeException("Bootstrap Properties must be fetched with method: getBootstrapProperties");
364 }
365
366 if ( Bootstrap.verbose() && ! Bootstrap.isQuiet() ) {
367 System.out.println("*** Looking for resource "+resourceName+" for ClassLoader "+classLoader);
368 }
369
370 InputStream in = null;
371
372 try {
373 in = ResourcesUtils.getResourceFromResourceTree(BootstrapUtils.getBootstrapResourcesTree(), resourceName);
374 } catch (FileNotFoundException fnfe) {
375 throw new RuntimeException(fnfe);
376 }
377
378 if (in != null) {
379 return in;
380 } else {
381 return getResourceFromClassPath(classLoader, resourceName);
382 }
383
384 }
385
386
387 /**
388 * Utility method to fetch all the keys in a properties file, including its parent, to loop over them.
389 * <p>
390 * This method keeps into account if a Properties object has a parent and goes down
391 * the parent chain to fetch all the keys.
392 * @param props The Properties object
393 * @return The Set<Object> of all the keys in the Properties object.
394 */
395 public static Set<Object> getAllKeysInProperties(Properties props) {
396 Set<Object> keySet = new HashSet<>();
397 if (props != null) {
398 ResourcesUtils.loadKeySetForProperties(props, keySet);
399 }
400 return keySet;
401 }
402
403
404 private static InputStream getResourceFromClassPath(Class clazz, String resourceName) {
405 InputStream in = clazz.getResourceAsStream(resourceName);
406 if (in == null && !resourceName.startsWith(BootstrapUtils.FILE_SEPARATOR)) {
407 resourceName = BootstrapUtils.FILE_SEPARATOR + resourceName;
408 in = clazz.getResourceAsStream(resourceName);
409 }
410 if ( (Bootstrap.verbose() && in != null) && ! Bootstrap.isQuiet() ) {
411 System.out.println("*** Found resource "+resourceName+" in classpath for Class "+clazz);
412 }
413 return in;
414 }
415
416 private static InputStream getResourceFromClassPath(ClassLoader classLoader, String resourceName) {
417 InputStream in = classLoader.getResourceAsStream(resourceName);
418
419 if (in == null && !resourceName.startsWith(BootstrapUtils.FILE_SEPARATOR)) {
420 resourceName = BootstrapUtils.FILE_SEPARATOR + resourceName;
421 in = classLoader.getResourceAsStream(resourceName);
422 }
423 if ( (Bootstrap.verbose() && in != null) && ! Bootstrap.isQuiet() ) {
424 System.out.println("*** Found resource "+resourceName+" in classpath for ClassLoader "+classLoader);
425 }
426 return in;
427 }
428
429 private static String getShortNameForFileName(String fileName) {
430 if (fileName == null) {
431 return null;
432 }
433 String bootstrapPropertyName = fileName;
434 if (fileName.contains(BootstrapUtils.FILE_SEPARATOR)) {
435 bootstrapPropertyName = fileName.substring(fileName.lastIndexOf(BootstrapUtils.FILE_SEPARATOR) + 1);
436 }
437 String shortName = bootstrapPropertyName.replace(".properties", "");
438 return shortName;
439 }
440
441 }