View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2005 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  
18  package ch.elca.el4j.util.metadata;
19  
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Proxy;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.springframework.aop.support.AopUtils;
30  import org.springframework.core.BridgeMethodResolver;
31  import org.springframework.util.Assert;
32  
33  import ch.elca.el4j.util.codingsupport.CollectionUtils;
34  
35  /**
36   * The default implementation of the GenericAttributeSource interface.
37   *
38   * @svnLink $Revision: 4091 $;$Date: 2010-01-15 12:21:07 +0100 (Fr, 15. Jan 2010) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/core/src/main/java/ch/elca/el4j/util/metadata/DefaultGenericMetaDataSource.java $
39   *
40   * @author Raphael Boog (RBO)
41   * @author Martin Zeltner (MZE)
42   */
43  public class DefaultGenericMetaDataSource implements GenericMetaDataSource {
44  	/**
45  	 * Canonical value held in cache to indicate that no metadata was found for
46  	 * a method. So we don't need to look for metadata again.
47  	 */
48  	protected static final Collection NULL_METADATA = Collections.EMPTY_LIST;
49  	
50  	/**
51  	 * Cache of Attributes, keyed by Method and target class.
52  	 */
53  	protected final Map<String, Collection> m_cache
54  		= Collections.synchronizedMap(new HashMap<String, Collection>());
55  
56  	/**
57  	 * Used to delegate metadata requests.
58  	 */
59  	private Attributes m_metaDataDelegator;
60  
61  	/**
62  	 * Are the metadata types this source is made for.
63  	 */
64  	private List<Class> m_interceptingMetaData;
65  
66  	/**
67  	 * Calculates the cache key of a certain method in a certain class.
68  	 *
69  	 * @param method
70  	 *            method for the current invocation.
71  	 * @param targetClass
72  	 *            targetClass for this invocation. May be null.
73  	 * @return Returns the cache key string.
74  	 */
75  	protected String getCacheKey(Method method, Class targetClass) {
76  		// Class may be null, method can't
77  		// Must not produce same key for overloaded methods
78  		// Must produce same key for different instances of the same method
79  		StringBuffer key = new StringBuffer();
80  		if (targetClass != null) {
81  			if (Proxy.isProxyClass(targetClass)) {
82  				key.append("JdkProxy");
83  			} else if (AopUtils.isCglibProxyClass(targetClass)) {
84  				key.append("CglibProxy");
85  			} else {
86  				key.append(targetClass);
87  			}
88  		} else {
89  			key.append("unknown");
90  		}
91  		key.append(';');
92  		key.append(method);
93  		return key.toString();
94  	}
95  
96  	/**
97  	 * {@inheritDoc}
98  	 */
99  	public Collection getMetaData(Method method, Class targetClass) {
100 		// First, see if we have a cached value
101 		String cacheKey = getCacheKey(method, targetClass);
102 		Collection cachedMetaData = m_cache.get(cacheKey);
103 
104 		if (cachedMetaData != null) {
105 			// Value will either be canonical value indicating there is no
106 			// metadata or an actual metadata
107 			return cachedMetaData == NULL_METADATA ? null : cachedMetaData;
108 		} else {
109 			// We need to work it out
110 			Collection metaData = computeMetaData(method, targetClass);
111 			// Put it in the cache
112 			m_cache.put(cacheKey, metaData == null ? NULL_METADATA : metaData);
113 			// Return the metadata collection
114 			return metaData;
115 		}
116 	}
117 
118 	/**
119 	 * Same as method {@link #getMetaData(Method, Class)} but without caching
120 	 * of the result.
121 	 *
122 	 * @param method
123 	 *            Is the method for the current invocation. Must not be null.
124 	 * @param targetClass
125 	 *            target class for this invocation. May be null.
126 	 * @return Returns a collection of the matching meta data for the given
127 	 *         method and targetClass.
128 	 */
129 	@SuppressWarnings("unchecked")
130 	protected Collection computeMetaData(Method method, Class targetClass) {
131 		Assert.notNull(method);
132 		
133 		// Helps to find the bridge method. This is needed if the given method
134 		// is currently on a proxy.
135 		Method bridgeMethod = BridgeMethodResolver.findBridgedMethod(method);
136 		// Then look for the most specific method on given target class.
137 		Method specificMethod = AopUtils.getMostSpecificMethod(
138 			bridgeMethod, targetClass);
139 
140 		// Collect the metadata from method.
141 		Collection metaDataOnMethod = new ArrayList();
142 		
143 		// Try the bridge method.
144 		CollectionUtils.nullSaveAddAll(metaDataOnMethod,
145 			filterMetaData(findAllAttributes(specificMethod)));
146 		
147 		if (metaDataOnMethod.isEmpty() && !specificMethod.equals(method)) {
148 			// Fallback is to look at the original method
149 			CollectionUtils.nullSaveAddAll(metaDataOnMethod,
150 				filterMetaData(findAllAttributes(method)));
151 		}
152 		
153 		// Collect the metadata from class.
154 		Collection metaDataOnClass = new ArrayList();
155 		
156 		// Try as first the given target class
157 		if (targetClass != null) {
158 			CollectionUtils.nullSaveAddAll(metaDataOnClass,
159 				filterMetaData(findAllAttributes(targetClass)));
160 		}
161 		
162 		if (metaDataOnClass.isEmpty()) {
163 			// Try as second the declaring class of found bridge method.
164 			CollectionUtils.nullSaveAddAll(metaDataOnClass,
165 				filterMetaData(
166 					findAllAttributes(specificMethod.getDeclaringClass())));
167 		}
168 
169 		if (metaDataOnClass.isEmpty() && !specificMethod.equals(method)) {
170 			// Last fallback is the class of the original method
171 			CollectionUtils.nullSaveAddAll(metaDataOnClass,
172 				filterMetaData(findAllAttributes(
173 					method.getDeclaringClass())));
174 		}
175 		
176 		Collection result = new ArrayList();
177 		result.addAll(metaDataOnMethod);
178 		result.addAll(metaDataOnClass);
179 		return result.isEmpty() ? null : result;
180 	}
181 
182 	/**
183 	 * @param m Is the method to retrieve metadata for.
184 	 * @return Returns all found metadata associated with the given method or
185 	 *         <code>null</code> if no metadata could be found.
186 	 */
187 	protected Collection findAllAttributes(Method m) {
188 		return getMetaDataDelegator().getAttributes(m);
189 	}
190 
191 	/**
192 	 * @param clazz Is the class to retrieve metadata for.
193 	 * @return Returns all found metadata associated with the given class or
194 	 *         <code>null</code> if no metadata could be found.
195 	 */
196 	protected Collection findAllAttributes(Class clazz) {
197 		return getMetaDataDelegator().getAttributes(clazz);
198 	}
199 
200 	/**
201 	 * @param metaData Are the metadata to filter.
202 	 * @return Returns the filtered collection of metadata or <code>null</code>
203 	 *         if the returned collection would be empty. Only intercepting
204 	 *         metadata will endure the filter process.
205 	 */
206 	@SuppressWarnings("unchecked")
207 	protected Collection filterMetaData(Collection metaData) {
208 		List<Class> interceptingMetaData = getInterceptingMetaData();
209 		Assert.notEmpty(interceptingMetaData,
210 				"There is no metadata defined to be used for interception.");
211 
212 		if (metaData == null || metaData.isEmpty()) {
213 			return null;
214 		}
215 		
216 		List filteredMetaData = new ArrayList();
217 		for (Class metaDataClass : interceptingMetaData) {
218 			for (Object metaDataObject : metaData) {
219 				if (metaDataClass.isInstance(metaDataObject)) {
220 					filteredMetaData.add(metaDataObject);
221 				}
222 			}
223 		}
224 		return filteredMetaData.isEmpty() ? null : filteredMetaData;
225 	}
226 
227 	/**
228 	 * @return Returns the metaDataDelegator.
229 	 */
230 	public Attributes getMetaDataDelegator() {
231 		return m_metaDataDelegator;
232 	}
233 
234 	/**
235 	 * @param metaDataDelegator Is the metaDataDelegator to set.
236 	 */
237 	public void setMetaDataDelegator(Attributes metaDataDelegator) {
238 		m_metaDataDelegator = metaDataDelegator;
239 	}
240 
241 	/**
242 	 * {@inheritDoc}
243 	 */
244 	public List<Class> getInterceptingMetaData() {
245 		return m_interceptingMetaData;
246 	}
247 
248 	/**
249 	 * {@inheritDoc}
250 	 */
251 	@Override
252 	public void setInterceptingMetaData(List<Class> interceptedAttributes) {
253 		m_interceptingMetaData = interceptedAttributes;
254 	}
255 }