1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.nuiton.util;
24
25 import org.apache.commons.io.FileUtils;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29 import javax.swing.ImageIcon;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.IOException;
33 import java.net.JarURLConnection;
34 import java.net.MalformedURLException;
35 import java.net.URI;
36 import java.net.URISyntaxException;
37 import java.net.URL;
38 import java.net.URLClassLoader;
39 import java.net.URLConnection;
40 import java.nio.file.FileSystemNotFoundException;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.Enumeration;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Optional;
50 import java.util.jar.Attributes;
51 import java.util.jar.JarEntry;
52 import java.util.jar.JarFile;
53 import java.util.jar.Manifest;
54 import java.util.stream.Stream;
55 import java.util.zip.ZipEntry;
56 import java.util.zip.ZipFile;
57 import java.util.zip.ZipInputStream;
58
59 import static org.nuiton.i18n.I18n.t;
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 public class Resource {
76
77
78 private static final Log log = LogFactory.getLog(Resource.class);
79
80 protected Resource() {
81
82 }
83
84
85
86
87
88
89
90
91 public static URL getURL(String name) {
92 URL url = getURLOrNull(name);
93 if (url != null) {
94 return url;
95 }
96
97 throw new ResourceNotFoundException(t("nuitonutil.error.resource.not.found", name));
98 }
99
100
101
102
103
104
105
106 public static URL getURLOrNull(String name) {
107
108 File file = new File(name);
109 if (file.exists()) {
110 try {
111 return file.toURI().toURL();
112 } catch (MalformedURLException eee) {
113 log.warn(t("nuitonutil.error.convert.file.to.url", file, eee.getMessage()));
114 }
115 }
116
117
118
119
120
121
122 if (name.length() > 1 && name.startsWith("/")) {
123 name = name.substring(1);
124 }
125 URL url = ClassLoader.getSystemClassLoader().getResource(name);
126 if (url != null) {
127 return url;
128 }
129
130 ClassLoader cl = Resource.class.getClassLoader();
131 url = cl.getResource(name);
132 return url;
133 }
134
135
136
137
138
139
140
141 public static ImageIcon getIcon(String name) {
142 try {
143 return new ImageIcon(getURL(name));
144 } catch (Exception eee) {
145 log.warn("Can't find icon: " + name, eee);
146 return null;
147 }
148 }
149
150
151
152
153
154
155
156
157
158
159
160
161 public static List<URL> getURLs(String pattern) {
162 return getURLs(pattern, (URLClassLoader) null);
163 }
164
165
166
167
168
169
170
171
172
173
174
175 public static List<URL> getURLs(String pattern, URLClassLoader urlClassLoader) {
176
177 Optional<URLClassLoader> notNullUrlClassLoader = Optional.ofNullable(urlClassLoader);
178
179
180 if (!notNullUrlClassLoader.isPresent()) {
181 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
182 if (systemClassLoader instanceof URLClassLoader) {
183 notNullUrlClassLoader = Optional.of((URLClassLoader) systemClassLoader);
184 }
185 }
186
187 if (notNullUrlClassLoader.isPresent()) {
188 URL[] urls = ClassLoaderUtil.getURLs(notNullUrlClassLoader.get());
189 List<URL> result = getURLs(pattern, urls);
190 return result;
191 } else {
192
193 List<URL> urls = walkThroughClassLoaderAndGetURLs(pattern);
194 return urls;
195 }
196 }
197
198 private static URL safeUriToUrlOrNull(URI uri) {
199 try {
200 return uri.toURL();
201 } catch (MalformedURLException e) {
202 if (log.isWarnEnabled()) {
203 log.warn("An error occured while walking through classpath, it may not work as expected",e);
204 }
205 return null;
206 }
207 }
208
209 protected static List<URL> walkThroughClassLoaderAndGetURLs(String pattern) {
210
211 try {
212 List<URL> classpathUrls = new ArrayList<>();
213
214 Enumeration<URL> urls = ClassLoader.getSystemResources("");
215 while (urls.hasMoreElements()) {
216 try {
217 try (Stream<Path> walk = Files.walk(Paths.get(urls.nextElement().toURI()))) {
218
219 walk.filter(Files::isRegularFile)
220 .map(Path::toUri)
221 .map(Resource::safeUriToUrlOrNull)
222 .filter(Objects::nonNull)
223 .filter(url -> url.getPath().matches(pattern))
224 .forEach(classpathUrls::add);
225
226 } catch (IOException | FileSystemNotFoundException e) {
227 if (log.isWarnEnabled()) {
228 log.warn("An error occurred while walking through classpath, it may not work as expected", e);
229 }
230 }
231 } catch (URISyntaxException urise) {
232 if (log.isWarnEnabled()) {
233 log.warn("An error occurred while walking through classpath, it may not work as expected", urise);
234 }
235 }
236 }
237
238 return classpathUrls;
239 } catch (IOException e) {
240 return new ArrayList<>();
241 }
242 }
243
244
245
246
247
248
249
250
251
252
253
254 public static List<URL> getURLs(String pattern, URL... arrayURL) {
255 long t0 = System.nanoTime();
256
257 List<URL> urlList = new HashList<URL>();
258
259 if (arrayURL.length == 1) {
260 URL jarURL = arrayURL[0];
261 if (isJar(jarURL.toString())) {
262
263 try {
264 arrayURL = getClassPathURLsFromJarManifest(jarURL);
265 } catch (Exception e) {
266 log.warn(e);
267 arrayURL = new URL[]{jarURL};
268 }
269 }
270 }
271 if (log.isDebugEnabled()) {
272 for (URL url : arrayURL) {
273 log.debug("found url " + url);
274 }
275 }
276
277 for (URL urlFile : arrayURL) {
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 File file = FileUtils.toFile(urlFile);
294 String fileName = file.getAbsolutePath();
295
296 if (!file.exists()) {
297
298 if (log.isDebugEnabled()) {
299 log.debug("Can't find file " + file + " (" + fileName + ")");
300 }
301 continue;
302 }
303 if (isJar(fileName)) {
304
305 if (log.isDebugEnabled()) {
306 log.debug("jar to search " + file);
307 }
308 urlList.addAll(getURLsFromJar(file, pattern));
309 continue;
310 }
311 if (file.isDirectory()) {
312
313 if (log.isDebugEnabled()) {
314 log.debug("directory to search " + file);
315 }
316
317
318 urlList.addAll(getURLsFromDirectory(file, pattern));
319 continue;
320 }
321
322 if (isZip(fileName)) {
323
324 if (log.isDebugEnabled()) {
325 log.debug("zip to search " + file);
326 }
327 urlList.addAll(getURLsFromZip(file, pattern));
328 }
329
330 }
331 if (log.isInfoEnabled()) {
332 log.info("search URLs pattern: " + pattern + " in "
333 + arrayURL.length + " urls in "
334 + StringUtil.convertTime(System.nanoTime() - t0));
335 }
336 return urlList;
337 }
338
339 public static URL[] getClassPathURLsFromJarManifest(URL jarURL)
340 throws IOException {
341 URL[] result;
342 File jarFile = FileUtils.toFile(jarURL);
343 if (log.isDebugEnabled()) {
344 log.debug("class-path jar to scan " + jarFile);
345 }
346 JarFile jar = new JarFile(jarFile);
347 try {
348
349
350 File container = jarFile.getParentFile();
351 Manifest mf = jar.getManifest();
352 String classPath = null;
353 if (mf != null && mf.getMainAttributes() != null) {
354 classPath = mf.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
355 }
356 String[] paths;
357 if (classPath != null) {
358 paths = classPath.split(" ");
359 } else {
360 paths = StringUtil.EMPTY_STRING_ARRAY;
361 }
362 result = new URL[paths.length + 1];
363 result[0] = jarURL;
364 File path;
365 for (int i = 0; i < paths.length; i++) {
366 String s = paths[i];
367
368 if (s.indexOf(':') != -1) {
369 result[i + 1] = new URL(s);
370 continue;
371 }
372
373 if (s.startsWith(".") || !s.startsWith("/")) {
374
375 path = new File(container, s);
376 } else {
377 path = new File(s);
378 }
379 if (log.isDebugEnabled()) {
380 log.debug(path);
381 }
382 result[i + 1] = path.toURI().toURL();
383 }
384
385 } finally {
386 if (jar != null) {
387 jar.close();
388 }
389 }
390 return result;
391 }
392
393 public static List<URL> getURLsFromZip(File zipFile, String pattern) {
394 try {
395 if (log.isTraceEnabled()) {
396 log.trace("search '" + pattern + "' in " + zipFile);
397 }
398
399 List<URL> result = new ArrayList<URL>();
400
401 ZipInputStream zis =
402 new ZipInputStream(new FileInputStream(zipFile));
403 try {
404 while (zis.available() != 0) {
405 ZipEntry entry = zis.getNextEntry();
406
407 if (entry == null) {
408 break;
409 }
410
411 String name = entry.getName();
412 if (log.isTraceEnabled()) {
413 log.trace("zipFile: " + zipFile + " name: " + name);
414 }
415 if (pattern == null || name.matches(pattern)) {
416
417
418 URL url = getURL(name);
419
420
421 if (log.isTraceEnabled()) {
422 log.trace("zipFile: " + zipFile + " url: " + url);
423 }
424 result.add(url);
425 }
426 }
427 } finally {
428 zis.close();
429 }
430 if (log.isTraceEnabled()) {
431 log.trace("found with pattern '" + pattern + "' : " + result);
432 }
433 return result;
434 } catch (IOException eee) {
435 throw new ResourceException(t("nuitonutil.error.get.url.from.zip",
436 zipFile.getAbsolutePath(),
437 eee.getMessage())
438 );
439 }
440 }
441
442 public static List<URL> getURLsFromJar(File jarfile, String pattern) {
443 try {
444 if (log.isTraceEnabled()) {
445 log.trace("search '" + pattern + "' in " + jarfile);
446 }
447
448 List<URL> result = new ArrayList<URL>();
449
450 ZipInputStream zis =
451 new ZipInputStream(new FileInputStream(jarfile));
452 try {
453 while (zis.available() != 0) {
454 ZipEntry entry = zis.getNextEntry();
455
456 if (entry == null) {
457 break;
458 }
459
460 String name = entry.getName();
461 if (log.isTraceEnabled()) {
462 log.trace("jarfile: " + jarfile + " name: " + name);
463 }
464 if (pattern == null || name.matches(pattern)) {
465
466
467 URL url = getURL(name);
468
469
470 if (log.isTraceEnabled()) {
471 log.trace("jarfile: " + jarfile + " url: " + url);
472 }
473 result.add(url);
474 }
475 }
476 } finally {
477 zis.close();
478 }
479 if (log.isTraceEnabled()) {
480 log.trace("found with pattern '" + pattern + "' : " + result);
481 }
482 return result;
483 } catch (IOException eee) {
484 throw new ResourceException(t("nuitonutil.error.get.url.from.zip",
485 jarfile.getAbsolutePath(),
486 eee.getMessage())
487 );
488 }
489 }
490
491
492
493
494
495
496
497
498
499
500
501 public static List<URL> getURLsFromDirectory(File repository, String pattern) {
502 try {
503 if (log.isTraceEnabled()) {
504 log.trace("search '" + pattern + "' in " + repository);
505 }
506
507 List<URL> urlList = new HashList<URL>();
508 File[] filesList = repository.listFiles();
509
510 if (filesList != null) {
511
512 for (File file : filesList) {
513
514 String name = file.getAbsolutePath();
515
516 if (log.isTraceEnabled()) {
517 log.trace("directory: " + repository + " name: "
518 + name);
519 }
520
521
522 if (file.exists() && file.isDirectory()) {
523 urlList.addAll(getURLsFromDirectory(file,
524 pattern));
525
526
527 } else if (pattern == null || name.matches(pattern)) {
528 URL url = file.toURI().toURL();
529 if (log.isTraceEnabled()) {
530 log.trace("directory: " + repository + " url: "
531 + url);
532 }
533 urlList.add(url);
534 }
535 }
536 }
537 if (log.isTraceEnabled()) {
538 log.trace("found with pattern '" + pattern + "' : " + urlList);
539 }
540 return urlList;
541 } catch (MalformedURLException eee) {
542 throw new ResourceException(
543 t("nuitonutil.error.convert.file.to.url",
544 repository + " (pattern " + pattern + ") ",
545 eee.getMessage())
546 );
547
548 }
549 }
550
551
552
553
554
555
556
557 public static boolean isJar(String name) {
558 if (name != null && name.length() > 4) {
559 String ext = name.substring(name.length() - 4, name.length());
560 return ".jar".equalsIgnoreCase(ext);
561 }
562 return false;
563 }
564
565
566
567
568
569
570
571 public static boolean isZip(String name) {
572 if (name != null && name.length() > 4) {
573 String ext = name.substring(name.length() - 4, name.length());
574 return ".zip".equalsIgnoreCase(ext);
575 }
576 return false;
577 }
578
579
580
581
582
583
584
585
586
587 public static boolean containsDirectDirectory(URL url, String directory) throws IOException {
588
589
590
591 File file = FileUtils.toFile(url);
592 String fileName = file.getAbsolutePath();
593 if (!file.exists()) {
594 return false;
595 }
596 if (isJar(fileName) || isZip(fileName)) {
597
598 if (log.isTraceEnabled()) {
599 log.trace("zip to search " + file);
600 }
601 ZipFile zipFile = new ZipFile(file);
602 try {
603 return zipFile.getEntry(directory + '/') != null;
604 } finally {
605 zipFile.close();
606 }
607 }
608 if (file.isDirectory()) {
609
610 if (log.isTraceEnabled()) {
611 log.trace("directory to search " + file);
612 }
613 return new File(file, directory).exists();
614 }
615
616 if (log.isWarnEnabled()) {
617 log.warn(t("nuitonutil.error.unknown.url.type", url));
618 }
619 return false;
620 }
621
622
623
624
625
626
627
628
629 protected static boolean isPattern(String str) {
630 return str.indexOf('*') != -1 || str.indexOf('?') != -1;
631 }
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647 public static List<URL> getResources(String pattern) throws IOException {
648 return getResources(pattern, null);
649 }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666 public static List<URL> getResources(String pattern,
667 ClassLoader classLoader) throws IOException {
668 if (classLoader == null) {
669 classLoader = ClassLoader.getSystemClassLoader();
670 }
671
672 List<URL> urlList;
673
674 if (isPattern(pattern)) {
675 urlList = getPatternRessources(pattern, classLoader);
676 } else {
677 urlList = new HashList<URL>();
678 Enumeration<URL> resourceUrls = classLoader.getResources(pattern);
679 while (resourceUrls.hasMoreElements()) {
680 URL url = resourceUrls.nextElement();
681 urlList.add(url);
682 }
683 }
684
685 return urlList;
686 }
687
688
689
690
691
692
693
694
695
696
697
698 protected static List<URL> getPatternRessources(String pattern,
699 ClassLoader classLoader) throws IOException {
700
701 List<URL> urlList = new HashList<URL>();
702
703
704
705
706
707 int prefixEnd = pattern.indexOf(":") + 1;
708 int rootDirEnd = pattern.length();
709 while (rootDirEnd > prefixEnd &&
710 isPattern(pattern.substring(prefixEnd, rootDirEnd))) {
711 rootDirEnd = pattern.lastIndexOf('/', rootDirEnd - 2) + 1;
712 }
713 if (rootDirEnd == 0) {
714 rootDirEnd = prefixEnd;
715 }
716 String rootDirPath = pattern.substring(0, rootDirEnd);
717 String subPattern = pattern.substring(rootDirPath.length());
718
719 Enumeration<URL> rootDirResources =
720 classLoader.getResources(rootDirPath);
721
722 while (rootDirResources.hasMoreElements()) {
723 URL rootDirResource = rootDirResources.nextElement();
724
725 if (isJarUrl(rootDirResource)) {
726
727 if (log.isDebugEnabled()) {
728 log.debug("jar to search " + rootDirResource);
729 }
730 urlList.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
731 } else {
732 urlList.addAll(doFindMatchingFileSystemResources(rootDirResource, subPattern));
733 }
734 }
735
736 return urlList;
737 }
738
739
740
741
742
743
744
745
746
747
748
749 public static boolean isJarUrl(URL url) {
750 String protocol = url.getProtocol();
751 return "jar".equals(protocol) ||
752 "zip".equals(protocol) ||
753 "wsjar".equals(protocol);
754 }
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770 protected static List<URL> doFindPathMatchingJarResources(URL rootDirResource,
771 String subPattern) throws IOException {
772
773 URLConnection con = rootDirResource.openConnection();
774 JarFile jarFile;
775 String jarFileUrl;
776 String rootEntryPath;
777 boolean newJarFile = false;
778
779 if (con instanceof JarURLConnection) {
780
781 JarURLConnection jarCon = (JarURLConnection) con;
782 jarCon.setUseCaches(false);
783 jarFile = jarCon.getJarFile();
784 jarFileUrl = jarCon.getJarFileURL().toExternalForm();
785 JarEntry jarEntry = jarCon.getJarEntry();
786 rootEntryPath = jarEntry != null ? jarEntry.getName() : "";
787 } else {
788
789
790
791
792 String urlFile = rootDirResource.getFile();
793 int separatorIndex = urlFile.indexOf("!/");
794 if (separatorIndex != -1) {
795 jarFileUrl = urlFile.substring(0, separatorIndex);
796 rootEntryPath = urlFile.substring(separatorIndex + "!/".length());
797
798 if (jarFileUrl.startsWith("file:")) {
799 jarFile = new JarFile(jarFileUrl.substring("file:".length()));
800 } else {
801 jarFile = new JarFile(jarFileUrl);
802 }
803 } else {
804 jarFile = new JarFile(urlFile);
805 jarFileUrl = urlFile;
806 rootEntryPath = "";
807 }
808 newJarFile = true;
809 }
810
811 try {
812 if (log.isDebugEnabled()) {
813 log.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
814 }
815 if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
816
817
818 rootEntryPath = rootEntryPath + "/";
819 }
820 List<URL> result = new HashList<URL>(8);
821 for (Enumeration<?> entries = jarFile.entries(); entries.hasMoreElements(); ) {
822 JarEntry entry = (JarEntry) entries.nextElement();
823 String entryPath = entry.getName();
824 if (entryPath.startsWith(rootEntryPath)) {
825 String relativePath = entryPath.substring(rootEntryPath.length());
826 if (relativePath.matches(subPattern)) {
827 URL entryURL = new URL(rootDirResource, relativePath);
828 result.add(entryURL);
829 }
830 }
831 }
832 return result;
833 } finally {
834
835
836 if (newJarFile) {
837 jarFile.close();
838 }
839 }
840 }
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856 protected static List<URL> doFindMatchingFileSystemResources(URL rootDirResource,
857 String subPattern)
858 throws IOException {
859
860 File rootDir;
861 try {
862 rootDir = new File(rootDirResource.toURI().getSchemeSpecificPart());
863 rootDir = rootDir.getAbsoluteFile();
864 } catch (URISyntaxException ex) {
865 return Collections.emptyList();
866 }
867 return retrieveMatchingFiles(rootDir, subPattern);
868 }
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884 protected static List<URL> retrieveMatchingFiles(File rootDir,
885 String pattern) throws IOException {
886 if (!rootDir.exists()) {
887 return Collections.emptyList();
888 }
889 if (!rootDir.isDirectory()) {
890 return Collections.emptyList();
891 }
892 if (!rootDir.canRead()) {
893 return Collections.emptyList();
894 }
895
896 String fullPattern = rootDir.getAbsolutePath().replace(File.separator, "/");
897 if (!pattern.startsWith("/")) {
898 fullPattern += "/";
899 }
900
901 fullPattern = fullPattern + pattern.replace(File.separator, "/");
902 List<URL> result = new HashList<URL>(8);
903 doRetrieveMatchingFiles(fullPattern, rootDir, result);
904 return result;
905 }
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921 protected static void doRetrieveMatchingFiles(String fullPattern,
922 File dir,
923 List<URL> result) throws IOException {
924 if (log.isDebugEnabled()) {
925 log.debug("Searching directory [" + dir.getAbsolutePath() +
926 "] for files matching pattern [" + fullPattern + "]");
927 }
928 File[] dirContents = dir.listFiles();
929 if (dirContents == null) {
930 if (log.isWarnEnabled()) {
931 log.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
932 }
933 return;
934 }
935 for (File content : dirContents) {
936
937 String currPath = content.getAbsolutePath().replace(File.separator, "/");
938
939 if (content.isDirectory() && (currPath + "/").matches(fullPattern + ".*")) {
940 if (!content.canRead()) {
941 if (log.isDebugEnabled()) {
942 log.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
943 "] because the application is not allowed to read the directory");
944 }
945 } else {
946 doRetrieveMatchingFiles(fullPattern, content, result);
947 }
948 }
949
950 if (currPath.matches(fullPattern)) {
951 result.add(content.toURI().toURL());
952 }
953 }
954 }
955
956 }