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.lang.reflect.InvocationTargetException;
27 import java.util.Map;
28
29 import org.apache.commons.collections4.map.ReferenceMap;
30
31 import net.sf.jasperreports.engine.JRException;
32
33
34 /**
35  * Utility to use as a soft cache of singleton instances.
36  * 
37  * @author Lucian Chirita (lucianc@users.sourceforge.net)
38  */

39 public class JRSingletonCache<T>
40 {
41     private static final Object CONTEXT_KEY_NULL = new Object();
42     public static final String EXCEPTION_MESSAGE_KEY_CLASS_NOT_COMPATIBLE = "util.singleton.cache.class.not.compatible";
43     public static final String EXCEPTION_MESSAGE_KEY_CLASS_NOT_FOUND = "util.singleton.cache.class.not.found";
44     public static final String EXCEPTION_MESSAGE_KEY_INSTANCE_ERROR = "util.singleton.cache.instance.error";
45     
46     private final ReferenceMap<Object, Map<String,T>> cache;
47     private final Class<T> itf;
48
49     /**
50      * Creates a cache of singleton instances.
51      * 
52      * @param itf a interface or class that should be implemented by all classes cached by this object
53      */

54     public JRSingletonCache(Class<T> itf)
55     {
56         cache = new ReferenceMap<Object, Map<String,T>>(ReferenceMap.ReferenceStrength.WEAK, ReferenceMap.ReferenceStrength.SOFT);
57         this.itf = itf;
58     }
59
60     /**
61      * Returns the singleton instance corresponding to a class.
62      * <p>
63      * The instance is first searched into the cache and created if not found.
64      * <p>
65      * The class is expected to have a no-argument constructor.
66      * 
67      * @param className
68      * @return the singleton instance corresponding to a class
69      * @throws JRException
70      */

71     public synchronized T getCachedInstance(String className) throws JRException
72     {
73         Map<String,T> contextCache = getContextInstanceCache();
74         T instance = contextCache.get(className);
75         if (instance == null)
76         {
77             instance = createInstance(className);
78             contextCache.put(className, instance);
79         }
80         return instance;
81     }
82
83     protected T createInstance(String className) throws JRException
84     {
85         try
86         {
87             @SuppressWarnings("unchecked")
88             Class<? extends T> clazz = (Class<? extends T>) JRClassLoader.loadClassForName(className);
89             if (itf != null && !itf.isAssignableFrom(clazz))
90             {
91                 throw 
92                     new JRException(
93                         EXCEPTION_MESSAGE_KEY_CLASS_NOT_COMPATIBLE,
94                         new Object[]{className, itf.getName()});
95             }
96
97             return clazz.getDeclaredConstructor().newInstance();
98         }
99         catch (ClassNotFoundException e)
100         {
101             throw 
102                 new JRException(
103                     EXCEPTION_MESSAGE_KEY_CLASS_NOT_FOUND,
104                     new Object[]{className},
105                     e);
106         }
107         catch (InstantiationException | IllegalAccessException 
108             | NoSuchMethodException | InvocationTargetException e)
109         {
110             throw 
111                 new JRException(
112                     EXCEPTION_MESSAGE_KEY_INSTANCE_ERROR,
113                     new Object[]{className},
114                     e);
115         }
116     }
117
118     protected Map<String,T> getContextInstanceCache()
119     {
120         Object contextKey = getContextKey();
121         Map<String,T> contextCache = cache.get(contextKey);
122         if (contextCache == null)
123         {
124             contextCache = new ReferenceMap<String,T>();
125             cache.put(contextKey, contextCache);
126         }
127         return contextCache;
128     }
129     
130     protected Object getContextKey()
131     {
132         Object key = Thread.currentThread().getContextClassLoader();
133         if (key == null)
134         {
135             key = CONTEXT_KEY_NULL;
136         }
137         return key;
138     }
139 }
140