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  package ch.elca.el4j.util.codingsupport;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.aopalliance.aop.Advice;
24  import org.springframework.aop.Advisor;
25  import org.springframework.aop.IntroductionAdvisor;
26  import org.springframework.aop.IntroductionInterceptor;
27  import org.springframework.aop.framework.Advised;
28  import org.springframework.aop.framework.ProxyFactory;
29  import org.springframework.aop.support.AopUtils;
30  import org.springframework.aop.support.DefaultIntroductionAdvisor;
31  import org.springframework.util.ClassUtils;
32  
33  /**
34   * Similar to Spring's AOPUtil class, with some more features:
35   *  <ul>
36   *    <li> convenience methods to easily add advice
37   *     (interceptors or mixins) programmatically to a class.
38   *     This can be practical to make a quick test or for code that you would
39   *     like not to require spring configuration.
40   *
41   *  </ul>
42   *
43   *  Code sample:  <pre> <code>
44   *   DefaultPerson p = new DefaultPerson();
45   *   p = addAdvice(p, 0, new AMixin(),
46   *                       mySharedCInterceptor,
47   *                       new BMixin());
48   *
49   * 	 // the next call now uses the 3 advices set up
50   *   p.setAge(11);
51   *  </code> </pre>
52   *
53   *
54   * Remark: We delegate sometimes to the AOPUtil of Spring
55   *    (we duplicated the methods here for simplicity).
56   *
57   * @svnLink $Revision: 4068 $;$Date: 2010-01-05 09:38:21 +0100 (Di, 05. 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/codingsupport/AopHelper.java $
58   *
59   * @author Philipp Oser (POS)
60   */
61  public class AopHelper {
62  
63  	/**
64  	 * Check whether the given object is a JDK dynamic proxy or a CGLIB proxy.
65  	 * @param object the object to check
66  	 * @see #isJdkDynamicProxy
67  	 * @see #isCglibProxy
68  	 *
69  	 *  <br> delegates to the same method of Spring's AopUtil
70  	 */
71  	public static boolean isAopProxy(Object object) {
72  		return AopUtils.isAopProxy(object);
73  	}
74  
75  	/**
76  	 * Check whether the given object is a JDK dynamic proxy.
77  	 * @param object the object to check
78  	 * @see java.lang.reflect.Proxy#isProxyClass
79  	 *
80  	 *  <br> delegates to the same method of Spring's AopUtil
81  	 */
82  	public static boolean isJdkDynamicProxy(Object object) {
83  		return AopUtils.isJdkDynamicProxy(object);
84  	}
85  
86  	/**
87  	 * Check whether the given object is a CGLIB proxy.
88  	 * @param object the object to check
89  	 *
90  	 *  <br> delegates to the same method of Spring's AopUtil
91  	 */
92  	public static boolean isCglibProxy(Object object) {
93  		return AopUtils.isCglibProxy(object);
94  	}
95  
96  	/**
97  	 * Check whether the specified class is a CGLIB-generated class.
98  	 * @param clazz the class to check
99  	 *
100 	 *  <br> delegates to the same method of Spring's AopUtil
101 	 */
102 	public static boolean isCglibProxyClass(Class<?> clazz) {
103 		return AopUtils.isCglibProxyClass(clazz);
104 	}
105 
106 	/**
107 	 * Return the class name (String) of a CGLIB-generated class.
108 	 * @param clazz the class to work on
109 	 * @return null if it is not a proxy class
110 	 */
111 	public static String getClassNameOfCglibProxyClass(Class clazz) {
112 		if (AopUtils.isCglibProxyClass(clazz)) {
113 			int pos = clazz.getName().indexOf(ClassUtils.CGLIB_CLASS_SEPARATOR);
114 			return clazz.getName().substring(0, pos);
115 		}
116 		return null;
117 	}	
118 	
119 	/**
120 	 * Return the class (Class) of a CGLIB-generated class. 
121 	 * @param clazz
122 	 * @return null if no class was found or if it's not a proxy class
123 	 */
124 	public static Class getClassOfCglibProxyClass(Class clazz) {
125 		String className = getClassNameOfCglibProxyClass(clazz);
126 		if (className != null){
127 			try {
128 				return clazz.getClassLoader().loadClass(className);
129 			} catch (ClassNotFoundException e) { 
130 				// ignore error in loading class, just return null
131 			} 
132 		}
133 		return null;
134 	}	
135 	
136 	/**
137 	 * Determine the target class of the given bean instance,
138 	 * which might be an AOP proxy.
139 	 * <p>Returns the target class for an AOP proxy and the plain class else.
140 	 * @param candidate the instance to check (might be an AOP proxy)
141 	 * @return the target class (or the plain class of the given object as fallback)
142 	 * @see org.springframework.aop.TargetClassAware#getTargetClass()
143 	 *
144 	 *  <br> delegates to the same method of Spring's AopUtil
145 	 */
146 	public static Class<?> getTargetClass(Object candidate) {
147 		return AopUtils.getTargetClass(candidate);
148 	}
149 
150 	/**
151 	 * Add the advice(s) to the object. The method adds a Spring proxy if needed.
152 	 * If there is already a Spring proxy, it repackages the object in a new
153 	 * Spring proxy. The new advise is added in position 0 of the advice chain. <br> <br>
154 	 *
155 	 *  CAVEAT: In case "object" is already wrapped (=proxied), the type of the
156 	 *   target source must be assignable to T. <br>
157 	 *
158 	 *  Take care that Mixins should typically be instanciated as Spring
159 	 *  prototypes (i.e. 1 mixin instance per underlying object),
160 	 *  Interceptors can typically be shared among all the objects they apply to.
161 	 *   When you add an advice that implement IntroductionInterceptor
162 	 *    (i.e. are Mixins) this class automatically wraps them in a
163 	 *     DefaultIntroductionAdvisor. <br> <br>
164 	 *
165 	 *  For sample code please refer to the javadoc of {@link AopHelper}. <br> <br>
166 	 *
167 	 *  The method allows to add n advice at once (for performance:
168 	 *   it does not require n times a re-proxying (for mixins)). <br>
169 	 *
170 	 * @param object the object to wrap
171 	 * @param position  where in the advice chain this new advice should be
172 	 *    placed. 0 means at the beginning (=called first).
173 	 * @param advices the advise(s) (=the interceptors and mixins) to add
174 	 * @return the same object wrapped with a spring proxy (if needed) that
175 	 *  has in addition the given advice(s) <br> <br>
176 	 */
177 	@SuppressWarnings("unchecked")
178 	public static <T> T addAdvice(T object, int position, Advice... advices) {
179 		// idea to optimize: could we keep the same proxy in case
180 		//  we just want to add new interceptors?
181 		Advisor[] existingAdvisors = null;
182 		
183 		T result = object;
184 		
185 		if (result instanceof Advised) {
186 			Advised advised = (Advised)result;
187 			existingAdvisors = advised.getAdvisors();
188 			
189 			try { // replace object by the wrapped object
190 				result = (T)advised.getTargetSource().getTarget();
191 			} catch (Exception e) {
192 				e.printStackTrace();
193 			}
194 		}
195 		ProxyFactory proxyFactory = new ProxyFactory (result);
196 				
197 		proxyFactory.setProxyTargetClass(true);
198 		proxyFactory.setExposeProxy(true);
199 		
200 		if (existingAdvisors != null) {
201 			for (Advisor a : existingAdvisors){
202 				proxyFactory.addAdvisor(a);
203 			}
204 		}
205 		
206 		//  inverse order of advices
207 		List<Advice> advicesAsList = Arrays.asList(advices);
208 		Collections.reverse(advicesAsList);
209 
210 		for (Advice advice : advicesAsList) {
211 			if (advice instanceof IntroductionInterceptor) {
212 				IntroductionAdvisor ii = new DefaultIntroductionAdvisor(advice);
213 				proxyFactory.addAdvisor(position, ii);
214 			} else { // just an interceptor
215 				proxyFactory.addAdvice(position, advice);
216 			}
217 		}
218 	
219 		result = (T)proxyFactory.getProxy();
220 		return result;
221 	}
222 	
223 	/**
224 	 * Convenience method. See {@link #addAdvice(Object, int, Advice)}
225 	 *  The position is always set to 0 (=first position)
226 	 */
227 	public static <T> T addAdvice(T object, Advice... advices) {
228 		return (T)addAdvice(object, 0, advices);
229 	}
230 	
231 	/**
232 	 * Remove all advice (and the proxy) that were added via Spring AOP.
233 	 *
234 	 * <br><br> CAVEAT: this method does NOT remove hibernate proxies!
235 	 * @param object
236 	 * @return the object with all advice removed. <a>
237 	 */
238 	@SuppressWarnings("unchecked")
239 	public static <T> T removeAllAdvice(T object) {
240 		if (object instanceof Advised) {
241 			Advised advised = (Advised)object;
242 						
243 			try {
244 				return (T) advised.getTargetSource().getTarget();
245 			} catch (Exception e) {
246 				e.printStackTrace();
247 			}
248 		}
249 		return (T) object;
250 	}
251 	
252 	/**
253 	 * Does the given object have Spring AOP support (i.e.
254 	 *  a proxy with AOP methods on them)? <br>
255 	 *
256 	 * @param object
257 	 * @return whether the object is proxied.
258 	 */
259 	public static boolean isProxied(Object object) {
260 		return (object instanceof Advised);
261 	}
262 	
263 	/**
264 	 * Is the given object advised by an instance of the given advice class?
265 	 * @param object    the adviced object
266 	 * @param cls       the advice class
267 	 * @return          <code>true</code> if object is adviced by given advice class
268 	 */
269 	public static boolean isProxiedBy(Object object, Class<? extends Advice> cls) {
270 		if (object instanceof Advised) {
271 			Advised advised = (Advised) object;
272 			for (Advisor advisor : advised.getAdvisors()) {
273 				if (advisor.getAdvice().getClass() == cls) {
274 					return true;
275 				}
276 			} 
277 		}
278 		return false;
279 	}
280 
281 	
282 	// Remark: we could add here a remove advice method (it could
283 	//  take the class of the advices to remove)
284 }