View Javadoc

1   package org.lsst.ccs.utilities.jars;
2   
3   import org.lsst.ccs.bootstrap.resources.BootstrapResourceUtils;
4   import org.lsst.ccs.utilities.logging.Logger;
5   
6   import java.io.File;
7   import java.io.FileInputStream;
8   import java.io.IOException;
9   import java.io.InputStream;
10  import java.net.URL;
11  import java.util.ArrayList;
12  import java.util.Enumeration;
13  import java.util.Properties;
14  import java.util.jar.Manifest;
15  import java.util.logging.Level;
16  
17  /**
18   * This class deals with "common resources": that is resources that
19   * are not linked to a specific package (such resources are often queried
20   * using an initial slash when querying a resource through the <TT>Class</TT>
21   * resource methods such as <TT>getResource</TT> : <TT>getResource("/global.properties")</TT>).
22   * <p/>
23   * <B>Beware</B>: in the code using this class these resource names <B>should not</B> start with a slash (same protocol as with the <TT>ClassLoader</TT>
24   * resource methods.
25   * <p/>
26   * The general idea is that when dealing with multiple jars there may be many "common resources" with the same name.
27   * <BR/>
28   * So:
29   * <UL>
30   * <LI/> the <TT>getURLsFrom</TT> method will return all the URL of this resource known by a <TT>ClassLoader</TT>
31   * (chances are that there may be many of these)
32   * <LI/> the <TT>getURLsFor</TT> method will return the resource linked to a specific object. So if there
33   * are many resources with the same name this will pick up only the one that is in the jar of the current class.
34   * (chances are that there may be only one such resource: but this is not 100% sure if different versions
35   * of the same class are referenced in different jars! -an unlikely occurence-)
36   * </UL>
37   * <p/>
38   * Other utilities methods:
39   * <UL>
40   * <LI/> <TT>getPropertiesFrom(URL)</TT> loads a property from a common resource
41   * <LI/> <TT>getResourceContainer</TT> gives the name of the jar that contains a resource
42   * </UL>
43   *
44   * @author bamade
45   */
46  // Date: 14/05/13
47  
48  public class CommonResources {
49      public static final URL[] URL_ARRAY_MODEL = new URL[0];
50      public static final Logger CURLOG = Logger.getLogger("org.lsst.ccs.utilities");
51  
52      /**
53       * As "seen" from a <TT>ClassLoader</TT> the various URLs that point to resources with a given name.
54       * Normally these are ordered along the ClassPath order so if used for a hierarchical exploration
55       * the resulting array should be exploited backwards.
56       *
57       * @param loader             a current ClassLoader
58       * @param commonResourceName name of resource (without initial slash!)
59       * @return an Array of URL that point to resources with that name
60       */
61      public static URL[] getURLsFrom(ClassLoader loader, String commonResourceName) {
62          ArrayList<URL> list = new ArrayList<URL>();
63          try {
64              Enumeration<URL> enumURL = loader.getResources(commonResourceName);
65              while (enumURL.hasMoreElements()) {
66                  list.add(enumURL.nextElement());
67              }
68          } catch (IOException e) {
69              CURLOG.warn("common resource Name error: " + commonResourceName, e);
70          }
71          return list.toArray(URL_ARRAY_MODEL);
72      }
73  
74      /**
75       * As "seen" from the context ClassLoader the various URLs that point to resources with a given name
76       *
77       * @param commonResourceName name of resource (without initial slash!)
78       * @return an Array of URL that point to resources with that name
79       */
80      public static URL[] getURLsFrom(String commonResourceName) {
81          return getURLsFrom(Thread.currentThread().getContextClassLoader(), commonResourceName);
82      }
83  
84      /**
85       * return the URL of a resource that lies in the same jar as a class.
86       *
87       * @param obj                any instance of a Class object if no instance is at hand
88       * @param commonResourceName name of resource (without initial slash!)
89       * @return an Array (with normally only one element)
90       */
91      public static URL[] getURLsFor(Object obj, String commonResourceName) {
92          Class clazz;
93          if (obj instanceof Class) {
94              clazz = (Class) obj;
95          } else {
96              clazz = obj.getClass();
97          }
98          ArrayList<URL> list = new ArrayList<URL>();
99          ClassLoader loader = clazz.getClassLoader();
100         URL[] allResources = getURLsFrom(loader, commonResourceName);
101         //System.out.println(" name path : " + classNamePath);
102         String[] urlNames = getClassURLNamesFor(clazz);
103         for (String urlName : urlNames) {
104             //System.out.println(" URL name : " + urlName);
105             for (URL urlResource : allResources) {
106                 //System.out.println(" URL resource : " + urlResource);
107                 if (urlResource.toString().startsWith(urlName)) {
108                     list.add(urlResource);
109                 }
110             }
111 
112         }
113         return list.toArray(URL_ARRAY_MODEL);
114     }
115 
116     /**
117      * get the names of places where a Class lies in the classPath
118      *
119      * @param obj any object or Class
120      * @return mostly an array of size one
121      */
122     public static String[] getClassURLNamesFor(Object obj) {
123         Class clazz;
124         if (obj instanceof Class) {
125             clazz = (Class) obj;
126         } else {
127             clazz = obj.getClass();
128         }
129         String classNamePath = clazz.getCanonicalName().replace('.', '/') + ".class";
130         ClassLoader loader = clazz.getClassLoader();
131         URL[] classRepositories = getURLsFrom(loader, classNamePath);
132         String[] res = new String[classRepositories.length];
133         for (int ix = 0; ix < classRepositories.length; ix++) {
134             String urlName = classRepositories[ix].toString().replace(classNamePath, "");
135             res[ix] = urlName.substring(0, urlName.length() - 1);
136         }
137         return res;
138     }
139 
140     /**
141      * fills a Property Object from an <TT>URL the references</TT> a file in ".properties" format
142      *
143      * @param url
144      * @return a Properties object that may be empty if URL incorrect (or read error of the resource)
145      */
146     public static Properties getPropertiesFrom(URL url) {
147         Properties props = new Properties();
148         try {
149             InputStream is = url.openStream();
150             props.load(is);
151         } catch (Exception e) {
152             CURLOG.warn("URL stream error for: " + url, e);
153         }
154         return props;
155     }
156 
157     /**
158      * gets the name of a jar that contains a "common resource"
159      *
160      * @param url
161      * @param commonResourceName name of resource (without initial slash!)
162      * @return the name of a jar that "contains" the object referenced by the URL (can also be a directory)
163      */
164     public static String getResourceContainer(URL url, String commonResourceName) {
165         String path = url.getPath();
166         int idx = path.lastIndexOf(commonResourceName);
167         if (idx < 0) return null;
168         String pathWithoutResource = path.substring(0, idx - 1);
169         //System.out.println("pathWithoutResource :" +pathWithoutResource);
170         int lastSlash = pathWithoutResource.lastIndexOf('/');
171         String res = pathWithoutResource.substring(lastSlash + 1);
172         if (res.endsWith("!")) {
173             res = res.substring(0, res.length() - 1);
174         } else {
175             res = pathWithoutResource;
176         }
177         return res;
178     }
179 
180     /**
181      * returns a Manifest for an object
182      *
183      * @param obj an object instance or a Class
184      * @return a Manifest or null if not found
185      */
186     public static Manifest getManifestFor(Object obj) {
187         URL[] jarURLs = getURLsFor(obj, "META-INF/MANIFEST.MF");
188         if (jarURLs.length > 0) {
189             URL jarURL = jarURLs[0];
190             try {
191                 InputStream is = jarURL.openStream();
192                 Manifest res = new Manifest(is);
193                 return res;
194             } catch (IOException e) {
195                 CURLOG.warn("Manifest opening problem at " + jarURL, e);
196                 return null;
197             }
198 
199         } else {
200             return null;
201         }
202     }
203 
204     /**
205      * searches for a file as a local file or a resource linked to a class or a global resource.
206      *
207      * @param clazz
208      * @param pathName
209      * @return
210      * @throws IOException
211      */
212     // TODO: possible duplication of code in core utils
213     public static InputStream getInput(Class clazz, String pathName) throws IOException {
214         //TODO: this is not in the initial specs of getInput and prone to fire bugs
215         if (pathName.endsWith("properties")) {
216             return BootstrapResourceUtils.getBootstrapPropertiesInputStream(pathName, clazz);
217         } else {
218             InputStream is = BootstrapResourceUtils.getBootstrapResource(pathName, clazz);
219             // modified by bamade. reason: we should be able to use a local directory (for tests for example).
220             if (is == null) {
221                 File file = new File(pathName);
222                 if (file.exists()) {
223                     is = new FileInputStream(file);
224                 }
225             }
226             if (is == null) {
227                 throw new IOException(pathName + "no input found!");
228             }
229             return is;
230         }
231 //        InputStream is = null;
232 //        File file = new File(pathName);
233 //        if (file.exists()) {
234 //            is = new FileInputStream(file);
235 //
236 //        } else {
237 //            is = clazz.getResourceAsStream(pathName);
238 //            if (is == null) {
239 //                is = clazz.getResourceAsStream("/" + pathName);
240 //            }
241 //            if (is == null) {
242 //                throw new IllegalArgumentException("no resource " + pathName);
243 //            }
244 //        }
245 //        return is;
246     }
247 
248     /**
249      * @param name
250      * @return
251      */
252     public static Properties getPropertiesFromAny(String name) {
253         return BootstrapResourceUtils.getBootstrapProperties(name);
254 //        Properties res = new Properties();
255 //        try {
256 //            InputStream is = getInput(CommonResources.class, name);
257 //            res.load(is);
258 //            is.close(); //TODO : use automatic closing 1.7
259 //        } catch (IOException e) {
260 //            CURLOG.log(Level.WARNING, "no general resource for :" + name, e);
261 //        }
262 //            return res;
263     }
264 
265     /**
266      * this method will create a <TT>Properties</TT> object filled with ALL the ".properties" resources
267      * and Files that happen to bear the same name in the ClassPath and in the current directory.
268      * It uses a hierarchical reading of these files: any property which is in a file "before" the others
269      * in the current directory and in the classPath will "shadow" other values.
270      *
271      * @param loader             ClassLoader
272      * @param loaderResourceName the name of the Resource according to ClassLoader conventions
273      *                           (this means that the usual ResourceName with Objects should not be used)
274      * @return a Properties object with content read from these various files
275      */
276     public static Properties gatherAllPropertiesFrom(ClassLoader loader, String loaderResourceName) {
277         Properties res = new Properties();
278         // then from all resources
279         URL[] urls = getURLsFrom(loader, loaderResourceName);
280         for (int ix = urls.length - 1; ix >= 0; ix--) {
281             URL url = urls[ix];
282             try {
283                 InputStream is = url.openStream();
284                 res.load(is);
285                 is.close();
286             } catch (IOException e) {
287                 CURLOG.log(Level.WARNING, "reading for properties url : " + url, e);  //To change body of catch statement use File | Settings | File Templates.
288             }
289         }
290         //first from a file
291         File file = new File(loaderResourceName);
292         if (file.exists()) {
293             try (InputStream is = new FileInputStream(file)) {
294                 //InputStream is = new FileInputStream(file);
295                 res.load(is);
296                 //is.close(); //DONE : use 1.7 closing
297             } catch (IOException e) {
298                 CURLOG.log(Level.WARNING, "", e);
299             }
300         }
301 
302         return res;
303 
304     }
305 
306     /**
307      * same as the method with a ClassLoader argument but uses the current ClassLoader
308      *
309      * @param loaderResourceName file name or resource name that uses the ClassLoader resource naming convention
310      * @return a filled Properties object
311      */
312     public static Properties gatherAllPropertiesFrom(String loaderResourceName) {
313         return gatherAllPropertiesFrom(Thread.currentThread().getContextClassLoader(), loaderResourceName);
314     }
315 }