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 }