KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > beans > CachedIntrospectionResults


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.beans;
18
19 import java.beans.BeanInfo JavaDoc;
20 import java.beans.IntrospectionException JavaDoc;
21 import java.beans.Introspector JavaDoc;
22 import java.beans.PropertyDescriptor JavaDoc;
23 import java.lang.ref.Reference JavaDoc;
24 import java.lang.ref.WeakReference JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import java.util.WeakHashMap JavaDoc;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35
36 /**
37  * Internal class that caches JavaBeans {@link java.beans.PropertyDescriptor}
38  * information for a Java class. Not intended for direct use by application code.
39  *
40  * <p>Necessary as {@link java.beans.Introspector#getBeanInfo()} in JDK 1.3 will
41  * return a new deep copy of the BeanInfo every time we ask for it. We take the
42  * opportunity to cache property descriptors by method name for fast lookup.
43  * Furthermore, we do our own caching of descriptors here, rather than rely on
44  * the JDK's system-wide BeanInfo cache (to avoid leaks on ClassLoader shutdown).
45  *
46  * <p>Information is cached statically, so we don't need to create new
47  * objects of this class for every JavaBean we manipulate. Hence, this class
48  * implements the factory design pattern, using a private constructor and
49  * a static {@link #forClass(Class)} factory method to obtain instances.
50  *
51  * @author Rod Johnson
52  * @author Juergen Hoeller
53  * @since 05 May 2001
54  * @see #acceptClassLoader(ClassLoader)
55  * @see #clearClassLoader(ClassLoader)
56  * @see #forClass(Class)
57  */

58 public class CachedIntrospectionResults {
59
60     private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class);
61
62     /**
63      * Set of ClassLoaders that this CachedIntrospectionResults class will always
64      * accept classes from, even if the classes do not qualify as cache-safe.
65      */

66     static final Set JavaDoc acceptedClassLoaders = Collections.synchronizedSet(new HashSet JavaDoc());
67
68     /**
69      * Map keyed by class containing CachedIntrospectionResults.
70      * Needs to be a WeakHashMap with WeakReferences as values to allow
71      * for proper garbage collection in case of multiple class loaders.
72      */

73     static final Map JavaDoc classCache = Collections.synchronizedMap(new WeakHashMap JavaDoc());
74
75
76     /**
77      * Accept the given ClassLoader as cache-safe, even if its classes would
78      * not qualify as cache-safe in this CachedIntrospectionResults class.
79      * <p>This configuration method is only relevant in scenarios where the Spring
80      * classes reside in a 'common' ClassLoader (e.g. the system ClassLoader)
81      * whose lifecycle is not coupled to the application. In such a scenario,
82      * CachedIntrospectionResults would by default not cache any of the application's
83      * classes, since they would create a leak in the common ClassLoader.
84      * <p>Any <code>acceptClassLoader</code> call at application startup should
85      * be paired with a {@link #clearClassLoader} call at application shutdown.
86      * @param classLoader the ClassLoader to accept
87      */

88     public static void acceptClassLoader(ClassLoader JavaDoc classLoader) {
89         if (classLoader != null) {
90             acceptedClassLoaders.add(classLoader);
91         }
92     }
93
94     /**
95      * Clear the introspection cache for the given ClassLoader, removing the
96      * introspection results for all classes underneath that ClassLoader,
97      * and deregistering the ClassLoader (and any of its children) from the
98      * acceptance list.
99      * @param classLoader the ClassLoader to clear the cache for
100      */

101     public static void clearClassLoader(ClassLoader JavaDoc classLoader) {
102         if (classLoader == null) {
103             return;
104         }
105         synchronized (classCache) {
106             for (Iterator JavaDoc it = classCache.keySet().iterator(); it.hasNext();) {
107                 Class JavaDoc beanClass = (Class JavaDoc) it.next();
108                 if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) {
109                     it.remove();
110                 }
111             }
112         }
113         synchronized (acceptedClassLoaders) {
114             for (Iterator JavaDoc it = acceptedClassLoaders.iterator(); it.hasNext();) {
115                 ClassLoader JavaDoc registeredLoader = (ClassLoader JavaDoc) it.next();
116                 if (isUnderneathClassLoader(registeredLoader, classLoader)) {
117                     it.remove();
118                 }
119             }
120         }
121     }
122
123     /**
124      * Create CachedIntrospectionResults for the given bean class.
125      * <P>We don't want to use synchronization here. Object references are atomic,
126      * so we can live with doing the occasional unnecessary lookup at startup only.
127      * @param beanClass the bean class to analyze
128      * @return the corresponding CachedIntrospectionResults
129      * @throws BeansException in case of introspection failure
130      */

131     static CachedIntrospectionResults forClass(Class JavaDoc beanClass) throws BeansException {
132         CachedIntrospectionResults results = null;
133         Object JavaDoc value = classCache.get(beanClass);
134         if (value instanceof Reference JavaDoc) {
135             Reference JavaDoc ref = (Reference JavaDoc) value;
136             results = (CachedIntrospectionResults) ref.get();
137         }
138         else {
139             results = (CachedIntrospectionResults) value;
140         }
141         if (results == null) {
142             // can throw BeansException
143
results = new CachedIntrospectionResults(beanClass);
144             if (isCacheSafe(beanClass) || isClassLoaderAccepted(beanClass.getClassLoader())) {
145                 classCache.put(beanClass, results);
146             }
147             else {
148                 if (logger.isDebugEnabled()) {
149                     logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
150                 }
151                 classCache.put(beanClass, new WeakReference JavaDoc(results));
152             }
153         }
154         else {
155             if (logger.isTraceEnabled()) {
156                 logger.trace("Using cached introspection results for class [" + beanClass.getName() + "]");
157             }
158         }
159         return results;
160     }
161
162     /**
163      * Check whether this CachedIntrospectionResults class is configured
164      * to accept the given ClassLoader.
165      * @param classLoader the ClassLoader to check
166      * @return whether the given ClassLoader is accepted
167      * @see #acceptClassLoader
168      */

169     private static boolean isClassLoaderAccepted(ClassLoader JavaDoc classLoader) {
170         // Iterate over array copy in order to avoid synchronization for the entire
171
// ClassLoader check (avoiding a synchronized acceptedClassLoaders Iterator).
172
Object JavaDoc[] acceptedLoaderArray = acceptedClassLoaders.toArray();
173         for (int i = 0; i < acceptedLoaderArray.length; i++) {
174             ClassLoader JavaDoc registeredLoader = (ClassLoader JavaDoc) acceptedLoaderArray[i];
175             if (isUnderneathClassLoader(classLoader, registeredLoader)) {
176                 return true;
177             }
178         }
179         return false;
180     }
181
182     /**
183      * Check whether the given class is cache-safe,
184      * i.e. whether it is loaded by the same class loader as the
185      * CachedIntrospectionResults class or a parent of it.
186      * <p>Many thanks to Guillaume Poirier for pointing out the
187      * garbage collection issues and for suggesting this solution.
188      * @param clazz the class to analyze
189      */

190     private static boolean isCacheSafe(Class JavaDoc clazz) {
191         ClassLoader JavaDoc target = clazz.getClassLoader();
192         if (target == null) {
193             return false;
194         }
195         ClassLoader JavaDoc cur = CachedIntrospectionResults.class.getClassLoader();
196         if (cur == target) {
197             return true;
198         }
199         while (cur != null) {
200             cur = cur.getParent();
201             if (cur == target) {
202                 return true;
203             }
204         }
205         return false;
206     }
207
208     /**
209      * Check whether the given ClassLoader is underneath the given parent,
210      * that is, whether the parent is within the candidate's hierarchy.
211      * @param candidate the candidate ClassLoader to check
212      * @param parent the parent ClassLoader to check for
213      */

214     private static boolean isUnderneathClassLoader(ClassLoader JavaDoc candidate, ClassLoader JavaDoc parent) {
215         if (candidate == null) {
216             return false;
217         }
218         if (candidate == parent) {
219             return true;
220         }
221         ClassLoader JavaDoc classLoaderToCheck = candidate;
222         while (classLoaderToCheck != null) {
223             classLoaderToCheck = classLoaderToCheck.getParent();
224             if (classLoaderToCheck == parent) {
225                 return true;
226             }
227         }
228         return false;
229     }
230
231
232     /** The BeanInfo object for the introspected bean class */
233     private final BeanInfo JavaDoc beanInfo;
234
235     /** PropertyDescriptor objects keyed by property name String */
236     private final Map JavaDoc propertyDescriptorCache;
237
238
239     /**
240      * Create a new CachedIntrospectionResults instance for the given class.
241      * @param beanClass the bean class to analyze
242      * @throws BeansException in case of introspection failure
243      */

244     private CachedIntrospectionResults(Class JavaDoc beanClass) throws BeansException {
245         try {
246             if (logger.isTraceEnabled()) {
247                 logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
248             }
249             this.beanInfo = Introspector.getBeanInfo(beanClass);
250
251             // Immediately remove class from Introspector cache, to allow for proper
252
// garbage collection on class loader shutdown - we cache it here anyway,
253
// in a GC-friendly manner. In contrast to CachedIntrospectionResults,
254
// Introspector does not use WeakReferences as values of its WeakHashMap!
255
Class JavaDoc classToFlush = beanClass;
256             do {
257                 Introspector.flushFromCaches(classToFlush);
258                 classToFlush = classToFlush.getSuperclass();
259             }
260             while (classToFlush != null);
261
262             if (logger.isTraceEnabled()) {
263                 logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
264             }
265             this.propertyDescriptorCache = new HashMap JavaDoc();
266
267             // This call is slow so we do it once.
268
PropertyDescriptor JavaDoc[] pds = this.beanInfo.getPropertyDescriptors();
269             for (int i = 0; i < pds.length; i++) {
270                 PropertyDescriptor JavaDoc pd = pds[i];
271                 if (logger.isTraceEnabled()) {
272                     logger.trace("Found bean property '" + pd.getName() + "'" +
273                             (pd.getPropertyType() != null ?
274                             " of type [" + pd.getPropertyType().getName() + "]" : "") +
275                             (pd.getPropertyEditorClass() != null ?
276                             "; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
277                 }
278                 this.propertyDescriptorCache.put(pd.getName(), pd);
279             }
280         }
281         catch (IntrospectionException JavaDoc ex) {
282             throw new FatalBeanException("Cannot get BeanInfo for object of class [" + beanClass.getName() + "]", ex);
283         }
284     }
285
286     BeanInfo JavaDoc getBeanInfo() {
287         return this.beanInfo;
288     }
289
290     Class JavaDoc getBeanClass() {
291         return this.beanInfo.getBeanDescriptor().getBeanClass();
292     }
293
294     PropertyDescriptor JavaDoc getPropertyDescriptor(String JavaDoc propertyName) {
295         return (PropertyDescriptor JavaDoc) this.propertyDescriptorCache.get(propertyName);
296     }
297
298 }
299
Popular Tags