1 /*
2 * JasperReports - Free Java Reporting Library.
3 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4 * http://www.jaspersoft.com
5 *
6 * Unless you have purchased a commercial license agreement from Jaspersoft,
7 * the following license terms apply:
8 *
9 * This program is part of JasperReports.
10 *
11 * JasperReports is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * JasperReports is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23 */
24 package net.sf.jasperreports.engine.util;
25
26 import java.io.File;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLStreamHandler;
30 import java.net.URLStreamHandlerFactory;
31 import java.nio.file.InvalidPathException;
32 import java.nio.file.Paths;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.MissingResourceException;
36 import java.util.ResourceBundle;
37 import java.util.function.Function;
38
39 import org.apache.commons.logging.Log;
40 import org.apache.commons.logging.LogFactory;
41
42 import net.sf.jasperreports.engine.JRException;
43 import net.sf.jasperreports.engine.JasperReportsContext;
44 import net.sf.jasperreports.repo.RepositoryContext;
45 import net.sf.jasperreports.repo.RepositoryResourceContext;
46 import net.sf.jasperreports.repo.RepositoryUtil;
47 import net.sf.jasperreports.repo.ResourceBundleResource;
48 import net.sf.jasperreports.repo.SimpleRepositoryContext;
49
50
51 /**
52 * Provides methods for resource resolution via class loaders or URL stream handlers.
53 *
54 * @author Lucian Chirita (lucianc@users.sourceforge.net)
55 */
56 public final class JRResourcesUtil
57 {
58
59 private static final Log log = LogFactory.getLog(JRResourcesUtil.class);
60
61 /**
62 *
63 */
64 private static final String PROPERTIES_FILE_EXTENSION = ".properties";
65
66 /**
67 *
68 */
69 private static ClassLoader globalClassLoader;
70 /**
71 *
72 */
73 private static ThreadLocalStack localClassLoaderStack = new ThreadLocalStack();
74
75
76 /**
77 * Tries to parse a <code>String</code> as an URL.
78 *
79 * @param spec the <code>String</code> to parse
80 * @param urlHandlerFactory an URL stream handler factory to use
81 * @return an URL if the parsing is successful
82 * @see #getURLHandler(String, URLStreamHandlerFactory)
83 */
84 public static URL createURL(String spec, URLStreamHandlerFactory urlHandlerFactory)
85 {
86 URLStreamHandler handler = getURLHandler(spec, urlHandlerFactory);
87 URL url;
88 try
89 {
90 if (handler == null)
91 {
92 url = new URL(spec);
93 }
94 else
95 {
96 url = new URL(null, spec, handler);
97 }
98 }
99 catch (MalformedURLException e)
100 {
101 url = null;
102 }
103 return url;
104 }
105
106
107 /**
108 * Returns an URL stream handler for an URL specified as a <code>String</code>.
109 *
110 * @param spec the <code>String</code> to parse as an URL
111 * @param urlHandlerFact an URL stream handler factory
112 * @return an URL stream handler if one was found for the protocol of the URL
113 */
114 public static URLStreamHandler getURLHandler(String spec, URLStreamHandlerFactory urlHandlerFact)
115 {
116 URLStreamHandlerFactory urlHandlerFactory = urlHandlerFact;//getURLHandlerFactory(urlHandlerFact);
117
118 URLStreamHandler handler = null;
119 if (urlHandlerFactory != null)
120 {
121 String protocol = getURLProtocol(spec);
122 if (protocol != null)
123 {
124 handler = urlHandlerFactory.createURLStreamHandler(protocol);
125 }
126 }
127 return handler;
128 }
129
130
131 private static String getURLProtocol(String urlSpec)
132 {
133 String protocol = null;
134
135 String spec = urlSpec.trim();
136 int colon = spec.indexOf(':');
137 if (colon > 0)
138 {
139 String proto = spec.substring(0, colon);
140 if (protocolValid(proto))
141 {
142 protocol = proto;
143 }
144 }
145
146 return protocol;
147 }
148
149 private static boolean protocolValid(String protocol)
150 {
151 int length = protocol.length();
152 if (length < 1)
153 {
154 return false;
155 }
156
157 if (!Character.isLetter(protocol.charAt(0)))
158 {
159 return false;
160 }
161
162 for (int i = 1; i < length; ++i)
163 {
164 char c = protocol.charAt(i);
165 if (!(Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.'))
166 {
167 return false;
168 }
169 }
170
171 return true;
172 }
173
174
175 /**
176 * Attempts to find a file using a file resolver.
177 *
178 * @param location file name
179 * @param fileRes a file resolver
180 * @return the file, if found
181 * @deprecated To be removed.
182 */
183 public static File resolveFile(String location, FileResolver fileRes)
184 {
185 FileResolver fileResolver = fileRes;//getFileResolver(fileRes);
186
187 if (fileResolver != null)
188 {
189 return fileResolver.resolveFile(location);
190 }
191
192 return resolveFile(null, location);
193 }
194
195
196 public static File resolveFile(RepositoryContext context, String location)
197 {
198 return resolveFile(context, location, JRResourcesUtil::defaultLocateFile);
199 }
200
201 public static File resolveFile(RepositoryContext context, String location, Function<String, File> rootLocator)
202 {
203 File file = locateFile(context == null ? null : context.getResourceContext(), location, rootLocator);
204 if (file != null && file.isFile())
205 {
206 return file;
207 }
208
209 return null;
210 }
211
212 protected static File defaultLocateFile(String location)
213 {
214 File file = new File(location);
215 if (file.exists())
216 {
217 return file;
218 }
219
220 return null;
221 }
222
223 protected static File locateFile(RepositoryResourceContext resourceContext, String location, Function<String, File> rootLocator)
224 {
225 File file = rootLocator.apply(location);
226 if (file != null)
227 {
228 return file;
229 }
230
231 if (resourceContext != null)
232 {
233 RepositoryResourceContext context = resourceContext;
234 while (context != null)
235 {
236 File contextDir = locateContextDirectory(context, rootLocator);
237 if (contextDir != null)
238 {
239 file = new File(contextDir, location);
240 if (file.exists())
241 {
242 if (log.isDebugEnabled())
243 {
244 log.debug("resolved location " + location + " relative to the context " + contextDir);
245 }
246
247 return file;
248 }
249 }
250
251 context = context.getFallbackContext();
252 }
253 }
254
255 return null;
256 }
257
258 protected static File locateContextDirectory(RepositoryResourceContext resourceContext, Function<String, File> rootLocator)
259 {
260 String contextLocation = resourceContext.getContextLocation();
261 if (contextLocation != null)
262 {
263 try
264 {
265 Paths.get(contextLocation);//valid patch check
266 File contextDir = rootLocator.apply(contextLocation);
267 if (contextDir != null && contextDir.isDirectory())
268 {
269 return contextDir;
270 }
271 }
272 catch (InvalidPathException e)
273 {
274 if (log.isDebugEnabled())
275 {
276 log.debug("location \"" + contextLocation + "\" is not a file path: " + e);
277 }
278 }
279 }
280 return null;
281 }
282
283
284 /**
285 * Returns a class loader.
286 * <p/>
287 * The first not null value from the following is returned:
288 * <ul>
289 * <li>the value of the parameter</li>
290 * <li>the thread local class loader</li>
291 * <li>the global class loader</li>
292 * </ul>
293 *
294 * @param clsLoader a class loader that will be returned if not null
295 * @return a class loader.
296 * @see #setGlobalClassLoader(ClassLoader)
297 * @see #setThreadClassLoader(ClassLoader)
298 */
299 public static ClassLoader getClassLoader(ClassLoader clsLoader)
300 {
301 ClassLoader classLoader = clsLoader;
302 if (classLoader == null)
303 {
304 classLoader = getThreadClassLoader();
305 if (classLoader == null)
306 {
307 classLoader = globalClassLoader;
308 }
309 }
310 return classLoader;
311 }
312
313
314 /**
315 * Returns the global class loader.
316 *
317 * @return the global class loader.
318 * @see #setGlobalClassLoader(ClassLoader)
319 */
320 public static ClassLoader getGlobalClassLoader()
321 {
322 return globalClassLoader;
323 }
324
325
326 /**
327 * Returns the thread local class loader.
328 *
329 * @return the thread local class loader.
330 * @see #setThreadClassLoader(ClassLoader)
331 */
332 public static ClassLoader getThreadClassLoader()
333 {
334 return (ClassLoader) localClassLoaderStack.top();
335 }
336
337
338 /**
339 * Sets the thread local class loader.
340 *
341 * @param classLoader a class loader
342 * @see #getClassLoader(ClassLoader)
343 */
344 public static void setThreadClassLoader(ClassLoader classLoader)
345 {
346 localClassLoaderStack.push(classLoader);
347 }
348
349
350 /**
351 * Resets the the thread local class loader to its previous value.
352 */
353 public static void resetClassLoader()
354 {
355 localClassLoaderStack.pop();
356 }
357
358
359 /**
360 * Sets a global class loader to be used for resource resolution.
361 *
362 * @param classLoader the class loader
363 * @see #getClassLoader(ClassLoader)
364 */
365 public static void setGlobalClassLoader(ClassLoader classLoader)
366 {
367 globalClassLoader = classLoader;
368 }
369
370
371 /**
372 * Attempts to find a resource using a class loader.
373 * <p/>
374 * The following sources are tried:
375 * <ul>
376 * <li>the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}</li>
377 * <li>the context class loader</li>
378 * <li><code>clazz.getClassLoader()</code></li>
379 * <li><code>clazz.getResource()</code></li>
380 * </ul>
381 *
382 * @param location the resource name
383 * @param clsLoader a class loader
384 * @param clazz a class
385 * @return the resource URL if found
386 * @deprecated Replaced by {@link #findClassLoaderResource(String, ClassLoader)}.
387 */
388 public static URL findClassLoaderResource(String location, ClassLoader clsLoader, Class<?> clazz)
389 {
390 ClassLoader classLoader = getClassLoader(clsLoader);
391
392 URL url = null;
393
394 if (classLoader != null)
395 {
396 url = classLoader.getResource(location);
397 }
398
399 if (url == null)
400 {
401 classLoader = Thread.currentThread().getContextClassLoader();
402
403 if (classLoader != null)
404 {
405 url = classLoader.getResource(location);
406 }
407
408 if (url == null)
409 {
410 classLoader = clazz.getClassLoader();
411 if (classLoader == null)
412 {
413 url = clazz.getResource("/" + location);
414 }
415 else
416 {
417 url = classLoader.getResource(location);
418 }
419 }
420 }
421
422 return url;
423 }
424
425 /**
426 * Attempts to find a resource using a class loader.
427 * <p/>
428 * The following sources are tried:
429 * <ul>
430 * <li>the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}</li>
431 * <li>the context class loader</li>
432 * <li><code>JRLoader.class.getClassLoader()</code></li>
433 * <li><code>JRLoader.class.getResource()</code></li>
434 * </ul>
435 *
436 * @param location the resource name
437 * @param clsLoader a class loader
438 * @return the resource URL if found
439 */
440 public static URL findClassLoaderResource(String location, ClassLoader clsLoader)
441 {
442 ClassLoader classLoader = getClassLoader(clsLoader);
443
444 URL url = null;
445
446 if (classLoader != null)
447 {
448 url = classLoader.getResource(location);
449 }
450
451 if (url == null)
452 {
453 classLoader = Thread.currentThread().getContextClassLoader();
454
455 if (classLoader != null)
456 {
457 url = classLoader.getResource(location);
458 }
459
460 if (url == null)
461 {
462 classLoader = JRLoader.class.getClassLoader();
463 if (classLoader == null)
464 {
465 url = JRLoader.class.getResource("/" + location);
466 }
467 else
468 {
469 url = classLoader.getResource(location);
470 }
471 }
472 }
473
474 return url;
475 }
476
477 /**
478 * Loads a resource bundle for a given base name and locale.
479 *
480 * <p>
481 * This methods calls {@link #loadResourceBundle(String, Locale, ClassLoader)} with a null classloader.
482 * </p>
483 *
484 * @param baseName the base name
485 * @param locale the locale
486 * @return the resource bundle for the given base name and locale
487 */
488 public static ResourceBundle loadResourceBundle(JasperReportsContext jasperReportsContext, String baseName, Locale locale)
489 {
490 return loadResourceBundle(SimpleRepositoryContext.of(jasperReportsContext), baseName, locale);
491 }
492
493 public static ResourceBundle loadResourceBundle(RepositoryContext repositoryContext, String baseName, Locale locale)
494 {
495 ResourceBundle resourceBundle = null;
496 MissingResourceException ex = null;
497 try
498 {
499 resourceBundle = loadResourceBundle(baseName, locale, null);
500 }
501 catch (MissingResourceException e)
502 {
503 ex = e;
504 }
505
506 if (resourceBundle == null)
507 {
508 CustomControl control = new CustomControl();
509 List<Locale> locales = control.getCandidateLocales(baseName, locale);
510 for (Locale lc : locales)
511 {
512 String suffix = lc.toString();
513 suffix = (suffix.trim().length() > 0 ? "_" : "") + suffix;
514 ResourceBundleResource resourceBundleResource = null;
515 try
516 {
517 resourceBundleResource =
518 RepositoryUtil.getInstance(repositoryContext).getResourceFromLocation(
519 baseName + suffix + PROPERTIES_FILE_EXTENSION,
520 ResourceBundleResource.class
521 );
522 }
523 catch (JRException e)
524 {
525 }
526 if (resourceBundleResource != null)
527 {
528 resourceBundle = resourceBundleResource.getResourceBundle();
529 break;
530 }
531 }
532 }
533
534 if (resourceBundle == null)
535 {
536 throw ex;
537 }
538
539 return resourceBundle;
540 }
541
542 /**
543 * Loads a resource bundle for a given base name and locale.
544 *
545 * <p>
546 * This methods calls {@link #loadResourceBundle(String, Locale, ClassLoader)} with a null classloader.
547 * </p>
548 *
549 * @param baseName the base name
550 * @param locale the locale
551 * @return the resource bundle for the given base name and locale
552 */
553 public static ResourceBundle loadResourceBundle(String baseName, Locale locale)
554 {
555 return loadResourceBundle(baseName, locale, null);
556 }
557
558 /**
559 * Loads a resource bundle for a given base name and locale.
560 *
561 * <p>
562 * The method attempts to load the resource bundle using the following classloaders
563 * (and stops at the first successful attempt):
564 * <ul>
565 * <li>the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}</li>
566 * <li>the context class loader</li>
567 * <li><code>JRClassLoader.class.getClassLoader()</code></li>
568 * </ul>
569 * </p>
570 *
571 * @param baseName the base name
572 * @param locale the locale
573 * @param clsLoader
574 * @return the resource bundle for the given base name and locale
575 * @see ResourceBundle#getBundle(String, Locale, ClassLoader)
576 */
577 public static ResourceBundle loadResourceBundle(String baseName, Locale locale, ClassLoader clsLoader)
578 {
579 ResourceBundle resourceBundle = null;
580
581 ClassLoader classLoader = getClassLoader(clsLoader);
582 if (classLoader != null)
583 {
584 try
585 {
586 resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
587 }
588 catch (MissingResourceException e)
589 {
590 }
591 }
592
593 if (resourceBundle == null)
594 {
595 classLoader = Thread.currentThread().getContextClassLoader();
596 if (classLoader != null)
597 {
598 try
599 {
600 resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
601 }
602 catch (MissingResourceException e)
603 {
604 }
605 }
606 }
607
608 if (resourceBundle == null)
609 {
610 classLoader = JRClassLoader.class.getClassLoader();
611 if (classLoader == null)
612 {
613 resourceBundle = ResourceBundle.getBundle(baseName, locale);
614 }
615 else
616 {
617 resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
618 }
619 }
620
621 return resourceBundle;
622 }
623
624
625 private JRResourcesUtil()
626 {
627 }
628 }
629
630
631 /**
632 *
633 */
634 class CustomControl extends ResourceBundle.Control
635 {
636 public CustomControl()
637 {
638 }
639 }
640