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.extensions;
25
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.IdentityHashMap;
31 import java.util.Iterator;
32 import java.util.LinkedHashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36
37 import org.apache.commons.collections4.map.ReferenceMap;
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40
41 import net.sf.jasperreports.annotations.properties.Property;
42 import net.sf.jasperreports.annotations.properties.PropertyScope;
43 import net.sf.jasperreports.engine.JRPropertiesMap;
44 import net.sf.jasperreports.engine.JRPropertiesUtil;
45 import net.sf.jasperreports.engine.JRPropertiesUtil.PropertySuffix;
46 import net.sf.jasperreports.engine.util.ClassLoaderResource;
47 import net.sf.jasperreports.engine.util.ClassUtils;
48 import net.sf.jasperreports.engine.util.JRLoader;
49 import net.sf.jasperreports.engine.util.ObjectUtils;
50 import net.sf.jasperreports.properties.PropertyConstants;
51
52 /**
53  * The default {@link ExtensionsRegistry extension registry} implementation.
54  *
55  * <p>
56  * The implementation builds an extension registry by scanning the context
57  * classloader for resources named <code>jasperreports_extension.properties</code>.
58  * 
59  * <p>
60  * Each such resource is loaded as a properties file, and properties that start
61  * with {@link #PROPERTY_REGISTRY_FACTORY_PREFIX net.sf.jasperreports.extension.registry.factory.} are identified.
62  * 
63  * <p>
64  * Each such property should have as value the name of a 
65  * {@link ExtensionsRegistryFactory} implementation.  The registry factory class is
66  * instantiated, and
67  * {@link ExtensionsRegistryFactory#createRegistry(String, JRPropertiesMap)}
68  * is called on it, using the propery suffix as registry ID and passing the
69  * properties map.  The registry factory can collect properties that apply to the
70  * specific registry by using a property prefix obtain by appending the registry ID
71  * to "{@link #PROPERTY_REGISTRY_PREFIX net.sf.jasperreports.extension.}".
72  * 
73  * <p>
74  * If instantiating an extension registry results in an exception, the registry
75  * is skipped and an error message is logged.
76  * 
77  * @author Lucian Chirita (lucianc@users.sourceforge.net)
78  */

79 public class DefaultExtensionsRegistry implements ExtensionsRegistry
80 {
81     
82     private final Log log = LogFactory.getLog(DefaultExtensionsRegistry.class);
83     
84     /**
85      * The name of property file resources that are used to load JasperReports 
86      * extensions.
87      */

88     public final static String EXTENSION_RESOURCE_NAME = 
89         "jasperreports_extension.properties";
90     
91     /**
92      * The property prefix of extension registry factories.
93      */

94     @Property(
95             name = "net.sf.jasperreports.extension.registry.factory.{arbitrary_name}",
96             category = PropertyConstants.CATEGORY_EXTENSIONS,
97             scopes = {PropertyScope.EXTENSION},
98             sinceVersion = PropertyConstants.VERSION_3_1_0
99             )
100     public final static String PROPERTY_REGISTRY_FACTORY_PREFIX = 
101             JRPropertiesUtil.PROPERTY_PREFIX + "extension.registry.factory.";
102     
103     /**
104      * A prefix that can be used to provide registry-specific properties,
105      * by appending the registry ID and a fixed property suffix to it. 
106      */

107     @Property(
108             name = "net.sf.jasperreports.extension.{registry_id}.{property_suffix}",
109             category = PropertyConstants.CATEGORY_EXTENSIONS,
110             scopes = {PropertyScope.EXTENSION},
111             sinceVersion = PropertyConstants.VERSION_3_1_0
112             )
113     public static final String PROPERTY_REGISTRY_PREFIX = 
114             JRPropertiesUtil.PROPERTY_PREFIX + "extension.";
115
116     private final ReferenceMap<Object, List<ExtensionsRegistry>> registrySetCache = 
117         new ReferenceMap<Object, List<ExtensionsRegistry>>(
118             ReferenceMap.ReferenceStrength.WEAK, ReferenceMap.ReferenceStrength.HARD
119             );
120     
121     private final ReferenceMap<ClassLoader, Map<URL, URLRegistries>> registryCache = 
122         new ReferenceMap<ClassLoader, Map<URL, URLRegistries>>(
123             ReferenceMap.ReferenceStrength.WEAK, ReferenceMap.ReferenceStrength.HARD
124             );
125
126     @Override
127     public <T> List<T> getExtensions(Class<T> extensionType)
128     {
129         List<ExtensionsRegistry> registries = getRegistries();
130         List<T> extensions = new ArrayList<T>(registries.size());
131         for (Iterator<ExtensionsRegistry> it = registries.iterator(); it.hasNext();)
132         {
133             ExtensionsRegistry registry = it.next();
134             List<T> registryExtensions = registry.getExtensions(extensionType);
135             if (registryExtensions != null && !registryExtensions.isEmpty())
136             {
137                 extensions.addAll(registryExtensions);
138             }
139         }
140         return extensions;
141     }
142     
143     protected List<ExtensionsRegistry> getRegistries()
144     {
145         List<ExtensionsRegistry> registries;
146         Object cacheKey = ExtensionsEnvironment.getExtensionsCacheKey();
147         synchronized (registrySetCache)
148         {
149             registries = registrySetCache.get(cacheKey);
150             if (registries == null)
151             {
152                 if (log.isDebugEnabled())
153                 {
154                     log.debug("Loading registries for cache key " + cacheKey);
155                 }
156                 
157                 registries = loadRegistries();
158                 registrySetCache.put(cacheKey, registries);
159             }
160         }
161         return registries;
162     }
163     
164     protected List<ExtensionsRegistry> loadRegistries()
165     {
166         //there is no identity linked hash map/set, using separate map and list
167         IdentityHashMap<ExtensionsRegistry, Object> registrySet = new IdentityHashMap<>();
168         List<ExtensionsRegistry> allRegistries = new ArrayList<ExtensionsRegistry>();
169         
170         List<ClassLoaderResource> extensionResources = loadExtensionPropertyResources();
171         for (ClassLoaderResource extensionResource : extensionResources)
172         {
173             ClassLoader classLoader = extensionResource.getClassLoader();
174             Map<URL, URLRegistries> classLoaderRegistries = getClassLoaderRegistries(classLoader);
175             
176             URL url = extensionResource.getUrl();
177             List<ExtensionsRegistry> registries;
178             Map<String, Exception> registryExceptions = new LinkedHashMap<String, Exception>();
179             synchronized (classLoaderRegistries)
180             {
181                 URLRegistries urlRegistries = classLoaderRegistries.get(url);
182                 if (urlRegistries == null)
183                 {
184                     if (log.isDebugEnabled())
185                     {
186                         log.debug("Loading JasperReports extension properties resource " 
187                                 + url);
188                     }
189                     
190                     JRPropertiesMap properties = JRPropertiesMap.loadProperties(url);
191                     URL duplicateURL = detectDuplicate(properties, classLoaderRegistries);//search across classloaders?
192                     if (duplicateURL == null)
193                     {
194                         registries = loadRegistries(properties, registryExceptions);
195                     }
196                     else
197                     {
198                         log.warn("Extension resource " + url + " was found to be a duplicate of "
199                                 + duplicateURL + " in classloader " + classLoader);
200                         registries = Collections.emptyList();
201                     }
202                     
203                     classLoaderRegistries.put(url, new URLRegistries(properties, registries));
204                 }
205                 else
206                 {
207                     registries = urlRegistries.registries;
208                 }
209             }
210             
211             for (Map.Entry<String, Exception> entry : registryExceptions.entrySet())
212             {
213                 log.error("Error instantiating extensions registry for " 
214                         + entry.getKey() + " from " + url, entry.getValue());
215             }
216             
217             for (ExtensionsRegistry extensionsRegistry : registries)
218             {
219                 //detecting identity duplicates
220                 boolean added = registrySet.put(extensionsRegistry, Boolean.FALSE) == null;
221                 if (added)
222                 {
223                     allRegistries.add(extensionsRegistry);
224                 }
225                 else if (log.isDebugEnabled())
226                 {
227                     log.debug("Found duplicate extension registry " + extensionsRegistry);
228                 }
229             }
230         }
231         return allRegistries;
232     }
233
234     protected List<ClassLoaderResource> loadExtensionPropertyResources()
235     {
236         return JRLoader.getClassLoaderResources(
237                 EXTENSION_RESOURCE_NAME);
238     }
239
240     protected Map<URL, URLRegistries> getClassLoaderRegistries(ClassLoader classLoader)
241     {
242         synchronized (registryCache)
243         {
244             Map<URL, URLRegistries> registries = registryCache.get(classLoader);
245             if (registries == null)
246             {
247                 registries = new HashMap<URL, URLRegistries>();
248                 registryCache.put(classLoader, registries);
249             }
250             return registries;
251         }
252     }
253     
254     protected List<ExtensionsRegistry> loadRegistries(JRPropertiesMap properties, 
255             Map<String, Exception> registryExceptions)
256     {
257         List<ExtensionsRegistry> registries = new ArrayList<ExtensionsRegistry>();
258         List<PropertySuffix> factoryProps = JRPropertiesUtil.getProperties(properties, 
259                 PROPERTY_REGISTRY_FACTORY_PREFIX);
260         for (Iterator<PropertySuffix> it = factoryProps.iterator(); it.hasNext();)
261         {
262             PropertySuffix factoryProp = it.next();
263             String registryId = factoryProp.getSuffix();
264             String factoryClass = factoryProp.getValue();
265             
266             if (log.isDebugEnabled())
267             {
268                 log.debug("Instantiating registry of type " + factoryClass 
269                         + for property " + factoryProp.getKey());
270             }
271             
272             try
273             {
274                 ExtensionsRegistry registry = instantiateRegistry(
275                         properties, registryId, factoryClass);
276                 registries.add(registry);
277             }
278             catch (Exception e)
279             {
280                 //skip this registry
281                 //error logging is deferred after the registries are cached to avoid a loop from JRRuntimeException.resolveMessage
282                 registryExceptions.put(registryId, e);
283             }
284         }
285         return registries;
286     }
287
288     protected ExtensionsRegistry instantiateRegistry(
289             JRPropertiesMap props, String registryId, String factoryClass)
290     {
291         if (log.isDebugEnabled())
292         {
293             log.debug("Instantiating extensions registry for " + registryId
294                     + " using factory class " + factoryClass);
295         }
296         
297         ExtensionsRegistryFactory factory = (ExtensionsRegistryFactory) 
298                 ClassUtils.instantiateClass(factoryClass, ExtensionsRegistryFactory.class);
299         return factory.createRegistry(registryId, props);
300     }
301     
302     protected URL detectDuplicate(JRPropertiesMap properties, Map<URL, URLRegistries> registries)
303     {
304         URL duplicateURL = null;
305         for (Entry<URL, URLRegistries> registryEntry : registries.entrySet())
306         {
307             JRPropertiesMap entryProperties = registryEntry.getValue().properties;
308             if (ObjectUtils.equals(properties, entryProperties))
309             {
310                 duplicateURL = registryEntry.getKey();
311                 break;
312             }
313         }
314         return duplicateURL;
315     }
316     
317     protected static class URLRegistries
318     {
319         JRPropertiesMap properties;
320         List<ExtensionsRegistry> registries;
321         
322         public URLRegistries(JRPropertiesMap properties, List<ExtensionsRegistry> registries)
323         {
324             this.properties = properties;
325             this.registries = registries;
326         }
327     }
328
329 }
330