001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.wicket.proxy;
018:
019: import java.io.InvalidClassException;
020: import java.io.ObjectStreamException;
021: import java.io.Serializable;
022: import java.lang.reflect.InvocationHandler;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.lang.reflect.Proxy;
026:
027: import org.apache.wicket.IClusterable;
028: import org.apache.wicket.model.IModel;
029:
030: import net.sf.cglib.proxy.Enhancer;
031: import net.sf.cglib.proxy.MethodInterceptor;
032: import net.sf.cglib.proxy.MethodProxy;
033:
034: /**
035: * A factory class that creates lazy init proxies given a type and a
036: * {@link IProxyTargetLocator} used to retrieve the object the proxy will
037: * represent.
038: * <p>
039: * A lazy init proxy waits until the first method invocation before it uses the
040: * {@link IProxyTargetLocator} to retrieve the object to which the method
041: * invocation will be forwarded.
042: * <p>
043: * This factory creates two kinds of proxies: A standard dynamic proxy when the
044: * specified type is an interface, and a CGLib proxy when the specified type is
045: * a concrete class.
046: * <p>
047: * The general use case for such a proxy is to represent a dependency that
048: * should not be serialized with a wicket page or {@link IModel}. The solution
049: * is to serialize the proxy and the {@link IProxyTargetLocator} instead of the
050: * dependency, and be able to look up the target object again when the proxy is
051: * deserialized and accessed. A good strategy for achieving this is to have a
052: * static lookup in the {@link IProxyTargetLocator}, this keeps its size small
053: * and makes it safe to serialize.
054: * <p>
055: * Example:
056: *
057: * <pre>
058: * class UserServiceLocator implements IProxyTargetLocator
059: * {
060: *
061: * public static final IProxyTargetLocator INSTANCE = new UserServiceLocator();
062: *
063: * Object locateProxyObject()
064: * {
065: * MyApplication app = (MyApplication) Application.get();
066: * return app.getUserService();
067: * }
068: * }
069: *
070: * class UserDetachableModel extends LoadableModel
071: * {
072: * private UserService svc;
073: *
074: * private long userId;
075: *
076: * public UserDetachableModel(long userId, UserService svc)
077: * {
078: * this.userId = userId;
079: * this.svc = svc;
080: * }
081: *
082: * public Object load()
083: * {
084: * return svc.loadUser(userId);
085: * }
086: * }
087: *
088: * UserService service = LazyInitProxyFactory.createProxy(UserService.class,
089: * UserServiceLocator.INSTANCE);
090: *
091: * UserDetachableModel model = new UserDetachableModel(10, service);
092: *
093: * </pre>
094: *
095: * The detachable model in the example above follows to good citizen pattern and
096: * is easy to unit test. These are the advantages gained through the use of the
097: * lazy init proxies.
098: *
099: * @author Igor Vaynberg (ivaynberg)
100: *
101: */
102: public class LazyInitProxyFactory {
103: /**
104: * Create a lazy init proxy for the specified type. The target object will
105: * be located using the provided locator upon first method invocation.
106: *
107: * @param type
108: * type that proxy will represent
109: *
110: * @param locator
111: * object locator that will locate the object the proxy
112: * represents
113: *
114: * @return lazily initializable proxy
115: */
116: public static Object createProxy(Class type,
117: IProxyTargetLocator locator) {
118: if (type == String.class) {
119: // We special-case Strings as sometimes people use these as SpringBeans (WICKET-603).
120: return locator.locateProxyTarget();
121: } else if (type.isInterface()) {
122: JdkHandler handler = new JdkHandler(type, locator);
123:
124: try {
125: return Proxy.newProxyInstance(Thread.currentThread()
126: .getContextClassLoader(), new Class[] { type,
127: Serializable.class, ILazyInitProxy.class,
128: IWriteReplace.class }, handler);
129: } catch (IllegalArgumentException e) {
130: /*
131: * STW: In some clustering environments it appears the context
132: * classloader fails to load the proxied interface (currently
133: * seen in BEA WLS 9.x clusters). If this happens, we can try
134: * and fall back to the classloader (current) that actually
135: * loaded this class.
136: */
137: return Proxy.newProxyInstance(
138: LazyInitProxyFactory.class.getClassLoader(),
139: new Class[] { type, Serializable.class,
140: ILazyInitProxy.class,
141: IWriteReplace.class }, handler);
142: }
143:
144: } else {
145: CGLibInterceptor handler = new CGLibInterceptor(type,
146: locator);
147:
148: Enhancer e = new Enhancer();
149: e.setInterfaces(new Class[] { Serializable.class,
150: ILazyInitProxy.class, IWriteReplace.class });
151: e.setSuperclass(type);
152: e.setCallback(handler);
153:
154: return e.create();
155:
156: }
157:
158: }
159:
160: /**
161: * This interface is used to make the proxy forward writeReplace() call to
162: * the handler instead of invoking it on itself. This allows us to serialize
163: * the replacement objet instead of the proxy itself in case the proxy
164: * subclass is deserialized on a VM that does not have it created.
165: *
166: * @see ProxyReplacement
167: *
168: * @author Igor Vaynberg (ivaynberg)
169: *
170: */
171: protected static interface IWriteReplace {
172: /**
173: * write replace method as defined by Serializable
174: *
175: * @return object that will replace this object in serialized state
176: * @throws ObjectStreamException
177: */
178: Object writeReplace() throws ObjectStreamException;
179: }
180:
181: /**
182: * Object that replaces the proxy when it is serialized. Upon
183: * deserialization this object will create a new proxy with the same
184: * locator.
185: *
186: * @author Igor Vaynberg (ivaynberg)
187: *
188: */
189: static class ProxyReplacement implements IClusterable {
190: private static final long serialVersionUID = 1L;
191:
192: private IProxyTargetLocator locator;
193:
194: private String type;
195:
196: /**
197: * Constructor
198: *
199: * @param type
200: * @param locator
201: */
202: public ProxyReplacement(String type, IProxyTargetLocator locator) {
203: this .type = type;
204: this .locator = locator;
205: }
206:
207: private Object readResolve() throws ObjectStreamException {
208: Class clazz;
209: try {
210: clazz = Class.forName(type);
211: } catch (ClassNotFoundException e) {
212: throw new InvalidClassException(type,
213: "could not resolve class [" + type
214: + "] when deserializing proxy");
215: }
216:
217: return LazyInitProxyFactory.createProxy(clazz, locator);
218: }
219: }
220:
221: /**
222: * Method interceptor for proxies representing concrete object not backed by
223: * an interface. These proxies are representing by cglib proxies.
224: *
225: * @author Igor Vaynberg (ivaynberg)
226: *
227: */
228: private static class CGLibInterceptor implements MethodInterceptor,
229: ILazyInitProxy, Serializable, IWriteReplace {
230: private static final long serialVersionUID = 1L;
231:
232: private IProxyTargetLocator locator;
233:
234: private String typeName;
235:
236: private transient Object target;
237:
238: /**
239: * Constructor
240: *
241: * @param type
242: * class of the object this proxy was created for
243: *
244: * @param locator
245: * object locator used to locate the object this proxy
246: * represents
247: */
248: public CGLibInterceptor(Class type, IProxyTargetLocator locator) {
249: super ();
250: this .typeName = type.getName();
251: this .locator = locator;
252: }
253:
254: /**
255: * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
256: * java.lang.reflect.Method, java.lang.Object[],
257: * net.sf.cglib.proxy.MethodProxy)
258: */
259: public Object intercept(Object object, Method method,
260: Object[] args, MethodProxy proxy) throws Throwable {
261: if (isFinalizeMethod(method)) {
262: // swallow finalize call
263: return null;
264: } else if (isEqualsMethod(method)) {
265: return (equals(args[0])) ? Boolean.TRUE : Boolean.FALSE;
266: } else if (isHashCodeMethod(method)) {
267: return new Integer(this .hashCode());
268: } else if (isToStringMethod(method)) {
269: return toString();
270: } else if (isWriteReplaceMethod(method)) {
271: return writeReplace();
272: } else if (method.getDeclaringClass().equals(
273: ILazyInitProxy.class)) {
274: return getObjectLocator();
275: }
276:
277: if (target == null) {
278: target = locator.locateProxyTarget();
279: }
280: return proxy.invoke(target, args);
281: }
282:
283: /**
284: * @see org.apache.wicket.proxy.ILazyInitProxy#getObjectLocator()
285: */
286: public IProxyTargetLocator getObjectLocator() {
287: return locator;
288: }
289:
290: /**
291: * @see org.apache.wicket.proxy.LazyInitProxyFactory.IWriteReplace#writeReplace()
292: */
293: public Object writeReplace() throws ObjectStreamException {
294: return new ProxyReplacement(typeName, locator);
295: }
296:
297: }
298:
299: /**
300: * Invocation handler for proxies representing interface based object. For
301: * interface backed objects dynamic jdk proxies are used.
302: *
303: * @author Igor Vaynberg (ivaynberg)
304: *
305: */
306: private static class JdkHandler implements InvocationHandler,
307: ILazyInitProxy, Serializable, IWriteReplace {
308: private static final long serialVersionUID = 1L;
309:
310: private IProxyTargetLocator locator;
311:
312: private String typeName;
313:
314: private transient Object target;
315:
316: /**
317: * Constructor
318: *
319: * @param type
320: * class of object this handler will represent
321: *
322: * @param locator
323: * object locator used to locate the object this proxy
324: * represents
325: */
326: public JdkHandler(Class type, IProxyTargetLocator locator) {
327: super ();
328: this .locator = locator;
329: this .typeName = type.getName();
330:
331: }
332:
333: /**
334: * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
335: * java.lang.reflect.Method, java.lang.Object[])
336: */
337: public Object invoke(Object proxy, Method method, Object[] args)
338: throws Throwable {
339: if (isFinalizeMethod(method)) {
340: // swallow finalize call
341: return null;
342: } else if (isEqualsMethod(method)) {
343: return (equals(args[0])) ? Boolean.TRUE : Boolean.FALSE;
344: } else if (isHashCodeMethod(method)) {
345: return new Integer(this .hashCode());
346: } else if (isToStringMethod(method)) {
347: return toString();
348: } else if (method.getDeclaringClass().equals(
349: ILazyInitProxy.class)) {
350: return getObjectLocator();
351: } else if (isWriteReplaceMethod(method)) {
352: return writeReplace();
353: }
354:
355: if (target == null) {
356:
357: target = locator.locateProxyTarget();
358: }
359: try {
360: return method.invoke(target, args);
361: } catch (InvocationTargetException e) {
362: throw e.getTargetException();
363: }
364: }
365:
366: /**
367: * @see org.apache.wicket.proxy.ILazyInitProxy#getObjectLocator()
368: */
369: public IProxyTargetLocator getObjectLocator() {
370: return locator;
371: }
372:
373: /**
374: * @see org.apache.wicket.proxy.LazyInitProxyFactory.IWriteReplace#writeReplace()
375: */
376: public Object writeReplace() throws ObjectStreamException {
377: return new ProxyReplacement(typeName, locator);
378: }
379:
380: }
381:
382: /**
383: * Checks if the method is derived from Object.equals()
384: *
385: * @param method
386: * method being tested
387: * @return true if the method is derived from Object.equals(), false
388: * otherwise
389: */
390: protected static boolean isEqualsMethod(Method method) {
391: return method.getReturnType() == boolean.class
392: && method.getParameterTypes().length == 1
393: && method.getParameterTypes()[0] == Object.class
394: && method.getName().equals("equals");
395: }
396:
397: /**
398: * Checks if the method is derived from Object.hashCode()
399: *
400: * @param method
401: * method being tested
402: * @return true if the method is defined from Object.hashCode(), false
403: * otherwise
404: */
405: protected static boolean isHashCodeMethod(Method method) {
406: return method.getReturnType() == int.class
407: && method.getParameterTypes().length == 0
408: && method.getName().equals("hashCode");
409: }
410:
411: /**
412: * Checks if the method is derived from Object.toString()
413: *
414: * @param method
415: * method being tested
416: * @return true if the method is defined from Object.toString(), false
417: * otherwise
418: */
419: protected static boolean isToStringMethod(Method method) {
420: return method.getReturnType() == String.class
421: && method.getParameterTypes().length == 0
422: && method.getName().equals("toString");
423: }
424:
425: /**
426: * Checks if the method is derived from Object.finalize()
427: *
428: * @param method
429: * method being tested
430: * @return true if the method is defined from Object.finalize(), false
431: * otherwise
432: */
433: protected static boolean isFinalizeMethod(Method method) {
434: return method.getReturnType() == void.class
435: && method.getParameterTypes().length == 0
436: && method.getName().equals("finalize");
437: }
438:
439: /**
440: * Checks if the method is the writeReplace method
441: *
442: * @param method
443: * method being tested
444: * @return true if the method is the writeReplace method, false otherwise
445: */
446: protected static boolean isWriteReplaceMethod(Method method) {
447: return method.getReturnType() == Object.class
448: && method.getParameterTypes().length == 0
449: && method.getName().equals("writeReplace");
450: }
451:
452: }
|