View Javadoc

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 }