001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.xbean.finder; 018 019import java.io.BufferedInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.File; 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.HttpURLConnection; 025import java.net.JarURLConnection; 026import java.net.MalformedURLException; 027import java.net.URL; 028import java.net.URLConnection; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.Enumeration; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.List; 035import java.util.Map; 036import java.util.Properties; 037import java.util.Vector; 038import java.util.jar.JarEntry; 039import java.util.jar.JarFile; 040 041/** 042 * @author David Blevins 043 * @version $Rev: 1778104 $ $Date: 2017-01-10 11:05:25 +0100 (Tue, 10 Jan 2017) $ 044 */ 045public class ResourceFinder { 046 private final URL[] urls; 047 private final String path; 048 private final ClassLoader classLoader; 049 private final List<String> resourcesNotLoaded = new ArrayList<String>(); 050 051 public ResourceFinder(URL... urls) { 052 this(null, Thread.currentThread().getContextClassLoader(), urls); 053 } 054 055 public ResourceFinder(String path) { 056 this(path, Thread.currentThread().getContextClassLoader(), null); 057 } 058 059 public ResourceFinder(String path, URL... urls) { 060 this(path, Thread.currentThread().getContextClassLoader(), urls); 061 } 062 063 public ResourceFinder(String path, ClassLoader classLoader) { 064 this(path, classLoader, null); 065 } 066 067 public ResourceFinder(String path, ClassLoader classLoader, URL... urls) { 068 if (path == null){ 069 path = ""; 070 } else if (path.length() > 0 && !path.endsWith("/")) { 071 path += "/"; 072 } 073 this.path = path; 074 075 if (classLoader == null) { 076 classLoader = Thread.currentThread().getContextClassLoader(); 077 } 078 this.classLoader = classLoader; 079 080 for (int i = 0; urls != null && i < urls.length; i++) { 081 URL url = urls[i]; 082 if (url == null || "jar".equals(url.getProtocol()) || isDirectory(url)) { // test directory last since it is the longer in time 083 continue; 084 } 085 try { 086 urls[i] = new URL("jar", "", -1, url.toString() + "!/"); 087 } catch (MalformedURLException e) { 088 // no-op 089 } 090 } 091 this.urls = (urls == null || urls.length == 0)? null : urls; 092 } 093 094 private static boolean isDirectory(URL url) { 095 String file = url.getFile(); 096 return (file.length() > 0 && file.charAt(file.length() - 1) == '/') || new File(file).isDirectory(); // with surefire first test can easily fail 097 } 098 099 /** 100 * Returns a list of resources that could not be loaded in the last invoked findAvailable* or 101 * mapAvailable* methods. 102 * <p/> 103 * The list will only contain entries of resources that match the requirements 104 * of the last invoked findAvailable* or mapAvailable* methods, but were unable to be 105 * loaded and included in their results. 106 * <p/> 107 * The list returned is unmodifiable and the results of this method will change 108 * after each invocation of a findAvailable* or mapAvailable* methods. 109 * <p/> 110 * This method is not thread safe. 111 */ 112 public List<String> getResourcesNotLoaded() { 113 return Collections.unmodifiableList(resourcesNotLoaded); 114 } 115 116 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 117 // 118 // Find 119 // 120 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 121 122 public URL find(String uri) throws IOException { 123 String fullUri = path + uri; 124 125 URL resource = getResource(fullUri); 126 if (resource == null) { 127 throw new IOException("Could not find resource '" + path + uri + "'"); 128 } 129 130 return resource; 131 } 132 133 public List<URL> findAll(String uri) throws IOException { 134 String fullUri = path + uri; 135 136 Enumeration<URL> resources = getResources(fullUri); 137 List<URL> list = new ArrayList(); 138 while (resources.hasMoreElements()) { 139 URL url = resources.nextElement(); 140 list.add(url); 141 } 142 return list; 143 } 144 145 146 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 147 // 148 // Find String 149 // 150 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 151 152 /** 153 * Reads the contents of the URL as a {@link String}'s and returns it. 154 * 155 * @param uri 156 * @return a stringified content of a resource 157 * @throws IOException if a resource pointed out by the uri param could not be find 158 * @see ClassLoader#getResource(String) 159 */ 160 public String findString(String uri) throws IOException { 161 String fullUri = path + uri; 162 163 URL resource = getResource(fullUri); 164 if (resource == null) { 165 throw new IOException("Could not find a resource in : " + fullUri); 166 } 167 168 return readContents(resource); 169 } 170 171 /** 172 * Reads the contents of the found URLs as a list of {@link String}'s and returns them. 173 * 174 * @param uri 175 * @return a list of the content of each resource URL found 176 * @throws IOException if any of the found URLs are unable to be read. 177 */ 178 public List<String> findAllStrings(String uri) throws IOException { 179 String fulluri = path + uri; 180 181 List<String> strings = new ArrayList<String>(); 182 183 Enumeration<URL> resources = getResources(fulluri); 184 while (resources.hasMoreElements()) { 185 URL url = resources.nextElement(); 186 String string = readContents(url); 187 strings.add(string); 188 } 189 return strings; 190 } 191 192 /** 193 * Reads the contents of the found URLs as a Strings and returns them. 194 * Individual URLs that cannot be read are skipped and added to the 195 * list of 'resourcesNotLoaded' 196 * 197 * @param uri 198 * @return a list of the content of each resource URL found 199 * @throws IOException if classLoader.getResources throws an exception 200 */ 201 public List<String> findAvailableStrings(String uri) throws IOException { 202 resourcesNotLoaded.clear(); 203 String fulluri = path + uri; 204 205 List<String> strings = new ArrayList<String>(); 206 207 Enumeration<URL> resources = getResources(fulluri); 208 while (resources.hasMoreElements()) { 209 URL url = resources.nextElement(); 210 try { 211 String string = readContents(url); 212 strings.add(string); 213 } catch (IOException notAvailable) { 214 resourcesNotLoaded.add(url.toExternalForm()); 215 } 216 } 217 return strings; 218 } 219 220 /** 221 * Reads the contents of all non-directory URLs immediately under the specified 222 * location and returns them in a map keyed by the file name. 223 * <p/> 224 * Any URLs that cannot be read will cause an exception to be thrown. 225 * <p/> 226 * Example classpath: 227 * <p/> 228 * META-INF/serializables/one 229 * META-INF/serializables/two 230 * META-INF/serializables/three 231 * META-INF/serializables/four/foo.txt 232 * <p/> 233 * ResourceFinder finder = new ResourceFinder("META-INF/"); 234 * Map map = finder.mapAvailableStrings("serializables"); 235 * map.contains("one"); // true 236 * map.contains("two"); // true 237 * map.contains("three"); // true 238 * map.contains("four"); // false 239 * 240 * @param uri 241 * @return a list of the content of each resource URL found 242 * @throws IOException if any of the urls cannot be read 243 */ 244 public Map<String, String> mapAllStrings(String uri) throws IOException { 245 Map<String, String> strings = new HashMap<String, String>(); 246 Map<String, URL> resourcesMap = getResourcesMap(uri); 247 for (Iterator iterator = resourcesMap.entrySet().iterator(); iterator.hasNext();) { 248 Map.Entry entry = (Map.Entry) iterator.next(); 249 String name = (String) entry.getKey(); 250 URL url = (URL) entry.getValue(); 251 String value = readContents(url); 252 strings.put(name, value); 253 } 254 return strings; 255 } 256 257 /** 258 * Reads the contents of all non-directory URLs immediately under the specified 259 * location and returns them in a map keyed by the file name. 260 * <p/> 261 * Individual URLs that cannot be read are skipped and added to the 262 * list of 'resourcesNotLoaded' 263 * <p/> 264 * Example classpath: 265 * <p/> 266 * META-INF/serializables/one 267 * META-INF/serializables/two # not readable 268 * META-INF/serializables/three 269 * META-INF/serializables/four/foo.txt 270 * <p/> 271 * ResourceFinder finder = new ResourceFinder("META-INF/"); 272 * Map map = finder.mapAvailableStrings("serializables"); 273 * map.contains("one"); // true 274 * map.contains("two"); // false 275 * map.contains("three"); // true 276 * map.contains("four"); // false 277 * 278 * @param uri 279 * @return a list of the content of each resource URL found 280 * @throws IOException if classLoader.getResources throws an exception 281 */ 282 public Map<String, String> mapAvailableStrings(String uri) throws IOException { 283 resourcesNotLoaded.clear(); 284 Map<String, String> strings = new HashMap<String, String>(); 285 Map<String, URL> resourcesMap = getResourcesMap(uri); 286 for (Iterator iterator = resourcesMap.entrySet().iterator(); iterator.hasNext();) { 287 Map.Entry entry = (Map.Entry) iterator.next(); 288 String name = (String) entry.getKey(); 289 URL url = (URL) entry.getValue(); 290 try { 291 String value = readContents(url); 292 strings.put(name, value); 293 } catch (IOException notAvailable) { 294 resourcesNotLoaded.add(url.toExternalForm()); 295 } 296 } 297 return strings; 298 } 299 300 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 301 // 302 // Find Class 303 // 304 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 305 306 /** 307 * Executes {@link #findString(String)} assuming the contents URL found is the name of 308 * a class that should be loaded and returned. 309 * 310 * @param uri 311 * @return 312 * @throws IOException 313 * @throws ClassNotFoundException 314 */ 315 public Class<?> findClass(String uri) throws IOException, ClassNotFoundException { 316 String className = findString(uri); 317 return classLoader.loadClass(className); 318 } 319 320 /** 321 * Executes findAllStrings assuming the strings are 322 * the names of a classes that should be loaded and returned. 323 * <p/> 324 * Any URL or class that cannot be loaded will cause an exception to be thrown. 325 * 326 * @param uri 327 * @return 328 * @throws IOException 329 * @throws ClassNotFoundException 330 */ 331 public List<Class<?>> findAllClasses(String uri) throws IOException, ClassNotFoundException { 332 List<Class<?>> classes = new ArrayList<Class<?>>(); 333 List<String> strings = findAllStrings(uri); 334 for (String className : strings) { 335 Class<?> clazz = classLoader.loadClass(className); 336 classes.add(clazz); 337 } 338 return classes; 339 } 340 341 /** 342 * Executes findAvailableStrings assuming the strings are 343 * the names of a classes that should be loaded and returned. 344 * <p/> 345 * Any class that cannot be loaded will be skipped and placed in the 346 * 'resourcesNotLoaded' collection. 347 * 348 * @param uri 349 * @return 350 * @throws IOException if classLoader.getResources throws an exception 351 */ 352 public List<Class<?>> findAvailableClasses(String uri) throws IOException { 353 resourcesNotLoaded.clear(); 354 List<Class<?>> classes = new ArrayList<Class<?>>(); 355 List<String> strings = findAvailableStrings(uri); 356 for (String className : strings) { 357 try { 358 Class<?> clazz = classLoader.loadClass(className); 359 classes.add(clazz); 360 } catch (Exception notAvailable) { 361 resourcesNotLoaded.add(className); 362 } 363 } 364 return classes; 365 } 366 367 /** 368 * Executes mapAllStrings assuming the value of each entry in the 369 * map is the name of a class that should be loaded. 370 * <p/> 371 * Any class that cannot be loaded will be cause an exception to be thrown. 372 * <p/> 373 * Example classpath: 374 * <p/> 375 * META-INF/xmlparsers/xerces 376 * META-INF/xmlparsers/crimson 377 * <p/> 378 * ResourceFinder finder = new ResourceFinder("META-INF/"); 379 * Map map = finder.mapAvailableStrings("xmlparsers"); 380 * map.contains("xerces"); // true 381 * map.contains("crimson"); // true 382 * Class xercesClass = map.get("xerces"); 383 * Class crimsonClass = map.get("crimson"); 384 * 385 * @param uri 386 * @return 387 * @throws IOException 388 * @throws ClassNotFoundException 389 */ 390 public Map<String, Class<?>> mapAllClasses(String uri) throws IOException, ClassNotFoundException { 391 Map<String, Class<?>> classes = new HashMap<String, Class<?>>(); 392 Map<String, String> map = mapAllStrings(uri); 393 for (Map.Entry<String, String> entry : map.entrySet()) { 394 String string = entry.getKey(); 395 String className = entry.getValue(); 396 Class<?> clazz = classLoader.loadClass(className); 397 classes.put(string, clazz); 398 } 399 return classes; 400 } 401 402 /** 403 * Executes mapAvailableStrings assuming the value of each entry in the 404 * map is the name of a class that should be loaded. 405 * <p/> 406 * Any class that cannot be loaded will be skipped and placed in the 407 * 'resourcesNotLoaded' collection. 408 * <p/> 409 * Example classpath: 410 * <p/> 411 * META-INF/xmlparsers/xerces 412 * META-INF/xmlparsers/crimson 413 * <p/> 414 * ResourceFinder finder = new ResourceFinder("META-INF/"); 415 * Map map = finder.mapAvailableStrings("xmlparsers"); 416 * map.contains("xerces"); // true 417 * map.contains("crimson"); // true 418 * Class xercesClass = map.get("xerces"); 419 * Class crimsonClass = map.get("crimson"); 420 * 421 * @param uri 422 * @return 423 * @throws IOException if classLoader.getResources throws an exception 424 */ 425 public Map<String, Class<?>> mapAvailableClasses(String uri) throws IOException { 426 resourcesNotLoaded.clear(); 427 Map<String, Class<?>> classes = new HashMap<String, Class<?>>(); 428 Map<String, String> map = mapAvailableStrings(uri); 429 for (Map.Entry<String, String> entry : map.entrySet()) { 430 String string = entry.getKey(); 431 String className = entry.getValue(); 432 try { 433 Class<?> clazz = classLoader.loadClass(className); 434 classes.put(string, clazz); 435 } catch (Exception notAvailable) { 436 resourcesNotLoaded.add(className); 437 } 438 } 439 return classes; 440 } 441 442 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 443 // 444 // Find Implementation 445 // 446 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 447 448 /** 449 * Assumes the class specified points to a file in the classpath that contains 450 * the name of a class that implements or is a subclass of the specfied class. 451 * <p/> 452 * Any class that cannot be loaded will be cause an exception to be thrown. 453 * <p/> 454 * Example classpath: 455 * <p/> 456 * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream 457 * META-INF/java.io.OutputStream 458 * <p/> 459 * ResourceFinder finder = new ResourceFinder("META-INF/"); 460 * Class clazz = finder.findImplementation(java.io.InputStream.class); 461 * clazz.getName(); // returns "org.acme.AcmeInputStream" 462 * 463 * @param interfase a superclass or interface 464 * @return 465 * @throws IOException if the URL cannot be read 466 * @throws ClassNotFoundException if the class found is not loadable 467 * @throws ClassCastException if the class found is not assignable to the specified superclass or interface 468 */ 469 public Class<?> findImplementation(Class<?> interfase) throws IOException, ClassNotFoundException { 470 String className = findString(interfase.getName()); 471 Class<?> impl = classLoader.loadClass(className); 472 if (!interfase.isAssignableFrom(impl)) { 473 throw new ClassCastException("Class not of type: " + interfase.getName()); 474 } 475 return impl; 476 } 477 478 /** 479 * Assumes the class specified points to a file in the classpath that contains 480 * the name of a class that implements or is a subclass of the specfied class. 481 * <p/> 482 * Any class that cannot be loaded or assigned to the specified interface will be cause 483 * an exception to be thrown. 484 * <p/> 485 * Example classpath: 486 * <p/> 487 * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream 488 * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream 489 * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream 490 * <p/> 491 * ResourceFinder finder = new ResourceFinder("META-INF/"); 492 * List classes = finder.findAllImplementations(java.io.InputStream.class); 493 * classes.contains("org.acme.AcmeInputStream"); // true 494 * classes.contains("org.widget.NeatoInputStream"); // true 495 * classes.contains("com.foo.BarInputStream"); // true 496 * 497 * @param interfase a superclass or interface 498 * @return 499 * @throws IOException if the URL cannot be read 500 * @throws ClassNotFoundException if the class found is not loadable 501 * @throws ClassCastException if the class found is not assignable to the specified superclass or interface 502 */ 503 public <T> List<Class<? extends T>> findAllImplementations(Class<T> interfase) throws IOException, ClassNotFoundException { 504 List<Class<? extends T>> implementations = new ArrayList<Class<? extends T>>(); 505 List<String> strings = findAllStrings(interfase.getName()); 506 for (String className : strings) { 507 Class<? extends T> impl = classLoader.loadClass(className).asSubclass(interfase); 508 implementations.add(impl); 509 } 510 return implementations; 511 } 512 513 /** 514 * Assumes the class specified points to a file in the classpath that contains 515 * the name of a class that implements or is a subclass of the specfied class. 516 * <p/> 517 * Any class that cannot be loaded or are not assignable to the specified class will be 518 * skipped and placed in the 'resourcesNotLoaded' collection. 519 * <p/> 520 * Example classpath: 521 * <p/> 522 * META-INF/java.io.InputStream # contains the classname org.acme.AcmeInputStream 523 * META-INF/java.io.InputStream # contains the classname org.widget.NeatoInputStream 524 * META-INF/java.io.InputStream # contains the classname com.foo.BarInputStream 525 * <p/> 526 * ResourceFinder finder = new ResourceFinder("META-INF/"); 527 * List classes = finder.findAllImplementations(java.io.InputStream.class); 528 * classes.contains("org.acme.AcmeInputStream"); // true 529 * classes.contains("org.widget.NeatoInputStream"); // true 530 * classes.contains("com.foo.BarInputStream"); // true 531 * 532 * @param interfase a superclass or interface 533 * @return 534 * @throws IOException if classLoader.getResources throws an exception 535 */ 536 public <T> List<Class<? extends T>> findAvailableImplementations(Class<T> interfase) throws IOException { 537 resourcesNotLoaded.clear(); 538 List<Class<? extends T>> implementations = new ArrayList<Class<? extends T>>(); 539 List<String> strings = findAvailableStrings(interfase.getName()); 540 for (String className : strings) { 541 try { 542 Class<?> impl = classLoader.loadClass(className); 543 if (interfase.isAssignableFrom(impl)) { 544 implementations.add(impl.asSubclass(interfase)); 545 } else { 546 resourcesNotLoaded.add(className); 547 } 548 } catch (Exception notAvailable) { 549 resourcesNotLoaded.add(className); 550 } 551 } 552 return implementations; 553 } 554 555 /** 556 * Assumes the class specified points to a directory in the classpath that holds files 557 * containing the name of a class that implements or is a subclass of the specfied class. 558 * <p/> 559 * Any class that cannot be loaded or assigned to the specified interface will be cause 560 * an exception to be thrown. 561 * <p/> 562 * Example classpath: 563 * <p/> 564 * META-INF/java.net.URLStreamHandler/jar 565 * META-INF/java.net.URLStreamHandler/file 566 * META-INF/java.net.URLStreamHandler/http 567 * <p/> 568 * ResourceFinder finder = new ResourceFinder("META-INF/"); 569 * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); 570 * Class jarUrlHandler = map.get("jar"); 571 * Class fileUrlHandler = map.get("file"); 572 * Class httpUrlHandler = map.get("http"); 573 * 574 * @param interfase a superclass or interface 575 * @return 576 * @throws IOException if the URL cannot be read 577 * @throws ClassNotFoundException if the class found is not loadable 578 * @throws ClassCastException if the class found is not assignable to the specified superclass or interface 579 */ 580 public <T> Map<String, Class<? extends T>> mapAllImplementations(Class<T> interfase) throws IOException, ClassNotFoundException { 581 Map<String, Class<? extends T>> implementations = new HashMap<String, Class<? extends T>>(); 582 Map<String, String> map = mapAllStrings(interfase.getName()); 583 for (Map.Entry<String, String> entry : map.entrySet()) { 584 String string = entry.getKey(); 585 String className = entry.getValue(); 586 Class<? extends T> impl = classLoader.loadClass(className).asSubclass(interfase); 587 implementations.put(string, impl); 588 } 589 return implementations; 590 } 591 592 /** 593 * Assumes the class specified points to a directory in the classpath that holds files 594 * containing the name of a class that implements or is a subclass of the specfied class. 595 * <p/> 596 * Any class that cannot be loaded or are not assignable to the specified class will be 597 * skipped and placed in the 'resourcesNotLoaded' collection. 598 * <p/> 599 * Example classpath: 600 * <p/> 601 * META-INF/java.net.URLStreamHandler/jar 602 * META-INF/java.net.URLStreamHandler/file 603 * META-INF/java.net.URLStreamHandler/http 604 * <p/> 605 * ResourceFinder finder = new ResourceFinder("META-INF/"); 606 * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class); 607 * Class jarUrlHandler = map.get("jar"); 608 * Class fileUrlHandler = map.get("file"); 609 * Class httpUrlHandler = map.get("http"); 610 * 611 * @param interfase a superclass or interface 612 * @return 613 * @throws IOException if classLoader.getResources throws an exception 614 */ 615 public <T> Map<String, Class<? extends T>> mapAvailableImplementations(Class<T> interfase) throws IOException { 616 resourcesNotLoaded.clear(); 617 Map<String, Class<? extends T>> implementations = new HashMap<String, Class<? extends T>>(); 618 Map<String, String> map = mapAvailableStrings(interfase.getName()); 619 for (Map.Entry<String, String> entry : map.entrySet()) { 620 String string = entry.getKey(); 621 String className = entry.getValue(); 622 try { 623 Class<?> impl = classLoader.loadClass(className); 624 if (interfase.isAssignableFrom(impl)) { 625 implementations.put(string, impl.asSubclass(interfase)); 626 } else { 627 resourcesNotLoaded.add(className); 628 } 629 } catch (Exception notAvailable) { 630 resourcesNotLoaded.add(className); 631 } 632 } 633 return implementations; 634 } 635 636 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 637 // 638 // Find Properties 639 // 640 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 641 642 /** 643 * Finds the corresponding resource and reads it in as a properties file 644 * <p/> 645 * Example classpath: 646 * <p/> 647 * META-INF/widget.properties 648 * <p/> 649 * ResourceFinder finder = new ResourceFinder("META-INF/"); 650 * Properties widgetProps = finder.findProperties("widget.properties"); 651 * 652 * @param uri 653 * @return 654 * @throws IOException if the URL cannot be read or is not in properties file format 655 */ 656 public Properties findProperties(String uri) throws IOException { 657 String fulluri = path + uri; 658 659 URL resource = getResource(fulluri); 660 if (resource == null) { 661 throw new IOException("Could not find resource: " + fulluri); 662 } 663 664 return loadProperties(resource); 665 } 666 667 /** 668 * Finds the corresponding resources and reads them in as a properties files 669 * <p/> 670 * Any URL that cannot be read in as a properties file will cause an exception to be thrown. 671 * <p/> 672 * Example classpath: 673 * <p/> 674 * META-INF/app.properties 675 * META-INF/app.properties 676 * META-INF/app.properties 677 * <p/> 678 * ResourceFinder finder = new ResourceFinder("META-INF/"); 679 * List<Properties> appProps = finder.findAllProperties("app.properties"); 680 * 681 * @param uri 682 * @return 683 * @throws IOException if the URL cannot be read or is not in properties file format 684 */ 685 public List<Properties> findAllProperties(String uri) throws IOException { 686 String fulluri = path + uri; 687 688 List<Properties> properties = new ArrayList<Properties>(); 689 690 Enumeration<URL> resources = getResources(fulluri); 691 while (resources.hasMoreElements()) { 692 URL url = resources.nextElement(); 693 Properties props = loadProperties(url); 694 properties.add(props); 695 } 696 return properties; 697 } 698 699 /** 700 * Finds the corresponding resources and reads them in as a properties files 701 * <p/> 702 * Any URL that cannot be read in as a properties file will be added to the 703 * 'resourcesNotLoaded' collection. 704 * <p/> 705 * Example classpath: 706 * <p/> 707 * META-INF/app.properties 708 * META-INF/app.properties 709 * META-INF/app.properties 710 * <p/> 711 * ResourceFinder finder = new ResourceFinder("META-INF/"); 712 * List<Properties> appProps = finder.findAvailableProperties("app.properties"); 713 * 714 * @param uri 715 * @return 716 * @throws IOException if classLoader.getResources throws an exception 717 */ 718 public List<Properties> findAvailableProperties(String uri) throws IOException { 719 resourcesNotLoaded.clear(); 720 String fulluri = path + uri; 721 722 List<Properties> properties = new ArrayList<Properties>(); 723 724 Enumeration<URL> resources = getResources(fulluri); 725 while (resources.hasMoreElements()) { 726 URL url = resources.nextElement(); 727 try { 728 Properties props = loadProperties(url); 729 properties.add(props); 730 } catch (Exception notAvailable) { 731 resourcesNotLoaded.add(url.toExternalForm()); 732 } 733 } 734 return properties; 735 } 736 737 /** 738 * Finds the corresponding resources and reads them in as a properties files 739 * <p/> 740 * Any URL that cannot be read in as a properties file will cause an exception to be thrown. 741 * <p/> 742 * Example classpath: 743 * <p/> 744 - META-INF/jdbcDrivers/oracle.properties 745 - META-INF/jdbcDrivers/mysql.props 746 - META-INF/jdbcDrivers/derby 747 * <p/> 748 * ResourceFinder finder = new ResourceFinder("META-INF/"); 749 * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers"); 750 * Properties oracleProps = driversList.get("oracle.properties"); 751 * Properties mysqlProps = driversList.get("mysql.props"); 752 * Properties derbyProps = driversList.get("derby"); 753 * 754 * @param uri 755 * @return 756 * @throws IOException if the URL cannot be read or is not in properties file format 757 */ 758 public Map<String, Properties> mapAllProperties(String uri) throws IOException { 759 Map<String, Properties> propertiesMap = new HashMap<String, Properties>(); 760 Map<String, URL> map = getResourcesMap(uri); 761 for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { 762 Map.Entry entry = (Map.Entry) iterator.next(); 763 String string = (String) entry.getKey(); 764 URL url = (URL) entry.getValue(); 765 Properties properties = loadProperties(url); 766 propertiesMap.put(string, properties); 767 } 768 return propertiesMap; 769 } 770 771 /** 772 * Finds the corresponding resources and reads them in as a properties files 773 * <p/> 774 * Any URL that cannot be read in as a properties file will be added to the 775 * 'resourcesNotLoaded' collection. 776 * <p/> 777 * Example classpath: 778 * <p/> 779 * META-INF/jdbcDrivers/oracle.properties 780 * META-INF/jdbcDrivers/mysql.props 781 * META-INF/jdbcDrivers/derby 782 * <p/> 783 * ResourceFinder finder = new ResourceFinder("META-INF/"); 784 * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers"); 785 * Properties oracleProps = driversList.get("oracle.properties"); 786 * Properties mysqlProps = driversList.get("mysql.props"); 787 * Properties derbyProps = driversList.get("derby"); 788 * 789 * @param uri 790 * @return 791 * @throws IOException if classLoader.getResources throws an exception 792 */ 793 public Map<String, Properties> mapAvailableProperties(String uri) throws IOException { 794 resourcesNotLoaded.clear(); 795 Map<String, Properties> propertiesMap = new HashMap<String, Properties>(); 796 Map<String, URL> map = getResourcesMap(uri); 797 for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) { 798 Map.Entry entry = (Map.Entry) iterator.next(); 799 String string = (String) entry.getKey(); 800 URL url = (URL) entry.getValue(); 801 try { 802 Properties properties = loadProperties(url); 803 propertiesMap.put(string, properties); 804 } catch (Exception notAvailable) { 805 resourcesNotLoaded.add(url.toExternalForm()); 806 } 807 } 808 return propertiesMap; 809 } 810 811 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 812 // 813 // Map Resources 814 // 815 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 816 817 public Map<String, URL> getResourcesMap(String uri) throws IOException { 818 String basePath = path + uri; 819 820 Map<String, URL> resources = new HashMap<String, URL>(); 821 if (!basePath.endsWith("/")) { 822 basePath += "/"; 823 } 824 Enumeration<URL> urls = getResources(basePath); 825 826 while (urls.hasMoreElements()) { 827 URL location = urls.nextElement(); 828 829 try { 830 if (location.getProtocol().equals("jar")) { 831 832 readJarEntries(location, basePath, resources); 833 834 } else if (location.getProtocol().equals("file")) { 835 836 readDirectoryEntries(location, resources); 837 838 } 839 } catch (Exception e) { 840 } 841 } 842 843 return resources; 844 } 845 846 private static void readDirectoryEntries(URL location, Map<String, URL> resources) throws MalformedURLException { 847 File dir = new File(decode(location.getPath())); 848 if (dir.isDirectory()) { 849 File[] files = dir.listFiles(); 850 if (files == null) { 851 return; 852 } 853 for (File file : files) { 854 if (!file.isDirectory()) { 855 String name = file.getName(); 856 URL url = file.toURI().toURL(); 857 resources.put(name, url); 858 } 859 } 860 } 861 } 862 863 private static void readJarEntries(URL location, String basePath, Map<String, URL> resources) throws IOException { 864 JarURLConnection conn = (JarURLConnection) location.openConnection(); 865 JarFile jarfile = null; 866 jarfile = conn.getJarFile(); 867 868 Enumeration<JarEntry> entries = jarfile.entries(); 869 while (entries != null && entries.hasMoreElements()) { 870 JarEntry entry = entries.nextElement(); 871 String name = entry.getName(); 872 873 if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) { 874 continue; 875 } 876 877 name = name.substring(basePath.length()); 878 879 if (name.contains("/")) { 880 continue; 881 } 882 883 URL resource = new URL(location, name); 884 resources.put(name, resource); 885 } 886 } 887 888 private Properties loadProperties(URL resource) throws IOException { 889 InputStream in = resource.openStream(); 890 891 BufferedInputStream reader = null; 892 try { 893 reader = new BufferedInputStream(in); 894 Properties properties = new Properties(); 895 properties.load(reader); 896 897 return properties; 898 } finally { 899 try { 900 in.close(); 901 reader.close(); 902 } catch (Exception e) { 903 } 904 } 905 } 906 907 private String readContents(URL resource) throws IOException { 908 InputStream in = resource.openStream(); 909 BufferedInputStream reader = null; 910 StringBuffer sb = new StringBuffer(); 911 912 try { 913 reader = new BufferedInputStream(in); 914 915 int b = reader.read(); 916 while (b != -1) { 917 sb.append((char) b); 918 b = reader.read(); 919 } 920 921 return sb.toString().trim(); 922 } finally { 923 try { 924 in.close(); 925 reader.close(); 926 } catch (Exception e) { 927 } 928 } 929 } 930 931 public URL getResource(String fullUri) { 932 if (urls == null){ 933 return classLoader.getResource(fullUri); 934 } 935 return findResource(fullUri, urls); 936 } 937 938 private Enumeration<URL> getResources(String fulluri) throws IOException { 939 if (urls == null) { 940 return classLoader.getResources(fulluri); 941 } 942 Vector<URL> resources = new Vector(); 943 for (URL url : urls) { 944 URL resource = findResource(fulluri, url); 945 if (resource != null){ 946 resources.add(resource); 947 } 948 } 949 return resources.elements(); 950 } 951 952 private URL findResource(String resourceName, URL... search) { 953 for (int i = 0; i < search.length; i++) { 954 URL currentUrl = search[i]; 955 if (currentUrl == null) { 956 continue; 957 } 958 959 try { 960 String protocol = currentUrl.getProtocol(); 961 if (protocol.equals("jar")) { 962 /* 963 * If the connection for currentUrl or resURL is 964 * used, getJarFile() will throw an exception if the 965 * entry doesn't exist. 966 */ 967 URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL(); 968 JarFile jarFile; 969 JarURLConnection juc; 970 try { 971 juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection(); 972 jarFile = juc.getJarFile(); 973 } catch (IOException e) { 974 // Don't look for this jar file again 975 search[i] = null; 976 throw e; 977 } 978 979 try { 980 juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection(); 981 jarFile = juc.getJarFile(); 982 String entryName; 983 if (currentUrl.getFile().endsWith("!/")) { 984 entryName = resourceName; 985 } else { 986 String file = currentUrl.getFile(); 987 int sepIdx = file.lastIndexOf("!/"); 988 if (sepIdx == -1) { 989 // Invalid URL, don't look here again 990 search[i] = null; 991 continue; 992 } 993 sepIdx += 2; 994 StringBuffer sb = new StringBuffer(file.length() - sepIdx + resourceName.length()); 995 sb.append(file.substring(sepIdx)); 996 sb.append(resourceName); 997 entryName = sb.toString(); 998 } 999 if (entryName.equals("META-INF/") && jarFile.getEntry("META-INF/MANIFEST.MF") != null) { 1000 return targetURL(currentUrl, "META-INF/MANIFEST.MF"); 1001 } 1002 if (jarFile.getEntry(entryName) != null) { 1003 return targetURL(currentUrl, resourceName); 1004 } 1005 } finally { 1006 if (!juc.getUseCaches()) { 1007 try { 1008 jarFile.close(); 1009 } catch (Exception e) { 1010 } 1011 } 1012 } 1013 1014 } else if (protocol.equals("file")) { 1015 String baseFile = currentUrl.getFile(); 1016 String host = currentUrl.getHost(); 1017 int hostLength = 0; 1018 if (host != null) { 1019 hostLength = host.length(); 1020 } 1021 StringBuffer buf = new StringBuffer(2 + hostLength + baseFile.length() + resourceName.length()); 1022 1023 if (hostLength > 0) { 1024 buf.append("//").append(host); 1025 } 1026 // baseFile should always ends with '/' 1027 buf.append(baseFile); 1028 if (!baseFile.endsWith("/")) { 1029 buf.append("/"); 1030 } 1031 1032 String fixedResName = resourceName; 1033 // Do not create a UNC path, i.e. \\host 1034 while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) { 1035 fixedResName = fixedResName.substring(1); 1036 } 1037 buf.append(fixedResName); 1038 String filename = buf.toString(); 1039 File file = new File(filename); 1040 File file2 = new File(decode(filename)); 1041 1042 if (file.exists() || file2.exists()) { 1043 return targetURL(currentUrl, fixedResName); 1044 } 1045 } else { 1046 URL resourceURL = targetURL(currentUrl, resourceName); 1047 URLConnection urlConnection = resourceURL.openConnection(); 1048 1049 try { 1050 urlConnection.getInputStream().close(); 1051 } catch (SecurityException e) { 1052 return null; 1053 } 1054 // HTTP can return a stream on a non-existent file 1055 // So check for the return code; 1056 if (!resourceURL.getProtocol().equals("http")) { 1057 return resourceURL; 1058 } 1059 1060 int code = ((HttpURLConnection) urlConnection).getResponseCode(); 1061 if (code >= 200 && code < 300) { 1062 return resourceURL; 1063 } 1064 } 1065 } catch (MalformedURLException e) { 1066 // Keep iterating through the URL list 1067 } catch (IOException e) { 1068 } catch (SecurityException e) { 1069 } 1070 } 1071 return null; 1072 } 1073 1074 private URL targetURL(URL base, String name) throws MalformedURLException { 1075 final String baseFile = base.getFile(); 1076 final StringBuffer sb = new StringBuffer(baseFile.length() + name.length()); 1077 sb.append(baseFile); 1078 if (!baseFile.endsWith("/")) { 1079 sb.append("/"); 1080 } 1081 sb.append(name); 1082 return new URL(base.getProtocol(), base.getHost(), base.getPort(), sb.toString(), null); 1083 } 1084 1085 public static String decode(String fileName) { 1086 if (fileName.indexOf('%') == -1) return fileName; 1087 1088 StringBuilder result = new StringBuilder(fileName.length()); 1089 ByteArrayOutputStream out = new ByteArrayOutputStream(); 1090 1091 for (int i = 0; i < fileName.length();) { 1092 char c = fileName.charAt(i); 1093 1094 if (c == '%') { 1095 out.reset(); 1096 do { 1097 if (i + 2 >= fileName.length()) { 1098 throw new IllegalArgumentException("Incomplete % sequence at: " + i); 1099 } 1100 1101 int d1 = Character.digit(fileName.charAt(i + 1), 16); 1102 int d2 = Character.digit(fileName.charAt(i + 2), 16); 1103 1104 if (d1 == -1 || d2 == -1) { 1105 throw new IllegalArgumentException("Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + String.valueOf(i)); 1106 } 1107 1108 out.write((byte) ((d1 << 4) + d2)); 1109 1110 i += 3; 1111 1112 } while (i < fileName.length() && fileName.charAt(i) == '%'); 1113 1114 1115 result.append(out.toString()); 1116 1117 continue; 1118 } else { 1119 result.append(c); 1120 } 1121 1122 i++; 1123 } 1124 return result.toString(); 1125 } 1126 1127}