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