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 }