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 */ 017package org.apache.wicket.proxy; 018 019import java.io.ObjectStreamException; 020import java.io.Serializable; 021import java.lang.reflect.Method; 022import java.util.Arrays; 023import java.util.List; 024 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.core.util.lang.WicketObjects; 027import org.apache.wicket.model.IModel; 028import org.apache.wicket.proxy.bytebuddy.ByteBuddyProxyFactory; 029import org.apache.wicket.proxy.cglib.CglibProxyFactory; 030import org.apache.wicket.proxy.jdk.JdkProxyFactory; 031import org.apache.wicket.util.io.IClusterable; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import net.sf.cglib.core.DefaultNamingPolicy; 036import net.sf.cglib.proxy.MethodInterceptor; 037import net.sf.cglib.proxy.MethodProxy; 038import net.sf.cglib.proxy.NoOp; 039 040/** 041 * A factory class that creates lazy init proxies given a type and a {@link IProxyTargetLocator} 042 * used to retrieve the object the proxy will represent. 043 * <p> 044 * A lazy init proxy waits until the first method invocation before it uses the 045 * {@link IProxyTargetLocator} to retrieve the object to which the method invocation will be 046 * forwarded. 047 * <p> 048 * This factory creates two kinds of proxies: A standard dynamic proxy when the specified type is an 049 * interface, and a cglib proxy when the specified type is a concrete class. 050 * <p> 051 * The general use case for such a proxy is to represent a dependency that should not be serialized 052 * with a wicket page or {@link IModel}. The solution is to serialize the proxy and the 053 * {@link IProxyTargetLocator} instead of the dependency, and be able to look up the target object 054 * again when the proxy is deserialized and accessed. A good strategy for achieving this is to have 055 * a static lookup in the {@link IProxyTargetLocator}, this keeps its size small and makes it safe 056 * to serialize. 057 * <p> 058 * Example: 059 * 060 * <pre> 061 * class UserServiceLocator implements IProxyTargetLocator 062 * { 063 * public static final IProxyTargetLocator INSTANCE = new UserServiceLocator(); 064 * 065 * Object locateProxyObject() 066 * { 067 * MyApplication app = (MyApplication)Application.get(); 068 * return app.getUserService(); 069 * } 070 * } 071 * 072 * class UserDetachableModel extends LoadableDetachableModel 073 * { 074 * private UserService svc; 075 * 076 * private long userId; 077 * 078 * public UserDetachableModel(long userId, UserService svc) 079 * { 080 * this.userId = userId; 081 * this.svc = svc; 082 * } 083 * 084 * public Object load() 085 * { 086 * return svc.loadUser(userId); 087 * } 088 * } 089 * 090 * UserService service = LazyInitProxyFactory.createProxy(UserService.class, 091 * UserServiceLocator.INSTANCE); 092 * 093 * UserDetachableModel model = new UserDetachableModel(10, service); 094 * 095 * </pre> 096 * 097 * The detachable model in the example above follows to good citizen pattern and is easy to unit 098 * test. These are the advantages gained through the use of the lazy init proxies. 099 * 100 * @author Igor Vaynberg (ivaynberg) 101 * 102 */ 103public class LazyInitProxyFactory 104{ 105 private static final Logger log = LoggerFactory.getLogger(LazyInitProxyFactory.class); 106 107 /** 108 * Primitive java types and their object wrappers 109 */ 110 private static final List<Class<?>> PRIMITIVES = Arrays.asList(String.class, byte.class, Byte.class, 111 short.class, Short.class, int.class, Integer.class, long.class, Long.class, float.class, 112 Float.class, double.class, Double.class, char.class, Character.class, boolean.class, 113 Boolean.class); 114 115 private static final IProxyFactory JDK_PROXY_FACTORY = new JdkProxyFactory(); 116 private static final IProxyFactory CLASS_PROXY_FACTORY = initProxyFactory(); 117 118 private static IProxyFactory initProxyFactory() { 119 IProxyFactory proxyFactory; 120 121 if (Boolean.getBoolean("wicket.ioc.useByteBuddy")) 122 { 123 log.info("Using Byte Buddy proxy factory"); 124 proxyFactory = new ByteBuddyProxyFactory(); 125 } 126 else 127 { 128 proxyFactory = new CglibProxyFactory(); 129 } 130 131 return proxyFactory; 132 } 133 134 /** 135 * Create a lazy init proxy for the specified type. The target object will be located using the 136 * provided locator upon first method invocation. 137 * 138 * @param type 139 * type that proxy will represent 140 * 141 * @param locator 142 * object locator that will locate the object the proxy represents 143 * 144 * @return lazily initializable proxy 145 */ 146 public static <T> T createProxy(final Class<T> type, final IProxyTargetLocator locator) 147 { 148 if (PRIMITIVES.contains(type) || Enum.class.isAssignableFrom(type)) 149 { 150 // We special-case primitives as sometimes people use these as 151 // SpringBeans (WICKET-603, WICKET-906). Go figure. 152 return (T) locator.locateProxyTarget(); 153 } 154 else if (type.isInterface()) 155 { 156 return JDK_PROXY_FACTORY.createProxy(type, locator); 157 } 158 else 159 { 160 return CLASS_PROXY_FACTORY.createProxy(type, locator); 161 } 162 } 163 164 /** 165 * This interface is used to make the proxy forward writeReplace() call to the handler instead 166 * of invoking it on itself. This allows us to serialize the replacement object instead of the 167 * proxy itself in case the proxy subclass is deserialized on a VM that does not have it 168 * created. 169 * 170 * @see LazyInitProxyFactory.ProxyReplacement 171 * 172 * @author Igor Vaynberg (ivaynberg) 173 * 174 */ 175 public interface IWriteReplace 176 { 177 /** 178 * write replace method as defined by Serializable 179 * 180 * @return object that will replace this object in serialized state 181 * @throws ObjectStreamException 182 */ 183 Object writeReplace() throws ObjectStreamException; 184 } 185 186 /** 187 * Object that replaces the proxy when it is serialized. Upon deserialization this object will 188 * create a new proxy with the same locator. 189 * 190 * @author Igor Vaynberg (ivaynberg) 191 * 192 */ 193 public static class ProxyReplacement implements IClusterable 194 { 195 private static final long serialVersionUID = 1L; 196 197 private final IProxyTargetLocator locator; 198 199 private final String type; 200 201 /** 202 * Constructor 203 * 204 * @param type 205 * @param locator 206 */ 207 public ProxyReplacement(final String type, final IProxyTargetLocator locator) 208 { 209 this.type = type; 210 this.locator = locator; 211 } 212 213 private Object readResolve() throws ObjectStreamException 214 { 215 Class<?> clazz = WicketObjects.resolveClass(type); 216 if (clazz == null) 217 { 218 try 219 { 220 clazz = Class.forName(type, false, Thread.currentThread().getContextClassLoader()); 221 } 222 catch (ClassNotFoundException ignored1) 223 { 224 try 225 { 226 clazz = Class.forName(type, false, LazyInitProxyFactory.class.getClassLoader()); 227 } 228 catch (ClassNotFoundException ignored2) 229 { 230 ClassNotFoundException cause = new ClassNotFoundException( 231 "Could not resolve type [" + type + 232 "] with the currently configured org.apache.wicket.application.IClassResolver"); 233 throw new WicketRuntimeException(cause); 234 } 235 } 236 } 237 return LazyInitProxyFactory.createProxy(clazz, locator); 238 } 239 } 240 241 /** 242 * Method interceptor for proxies representing concrete object not backed by an interface. These 243 * proxies are represented by cglib proxies. 244 * 245 * @author Igor Vaynberg (ivaynberg) 246 * 247 */ 248 @Deprecated(forRemoval = true) 249 public abstract static class AbstractCGLibInterceptor 250 implements 251 MethodInterceptor, 252 ILazyInitProxy, 253 Serializable, 254 IWriteReplace 255 { 256 private static final long serialVersionUID = 1L; 257 258 protected final IProxyTargetLocator locator; 259 260 protected final String typeName; 261 262 private transient Object target; 263 264 /** 265 * Constructor 266 * 267 * @param type 268 * class of the object this proxy was created for 269 * 270 * @param locator 271 * object locator used to locate the object this proxy represents 272 */ 273 public AbstractCGLibInterceptor(final Class<?> type, final IProxyTargetLocator locator) 274 { 275 super(); 276 typeName = type.getName(); 277 this.locator = locator; 278 } 279 280 /** 281 * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, 282 * java.lang.reflect.Method, java.lang.Object[], net.sf.cglib.proxy.MethodProxy) 283 */ 284 @Override 285 public Object intercept(final Object object, final Method method, final Object[] args, 286 final MethodProxy proxy) throws Throwable 287 { 288 if (isFinalizeMethod(method)) 289 { 290 // swallow finalize call 291 return null; 292 } 293 else if (isEqualsMethod(method)) 294 { 295 return (equals(args[0])) ? Boolean.TRUE : Boolean.FALSE; 296 } 297 else if (isHashCodeMethod(method)) 298 { 299 return hashCode(); 300 } 301 else if (isToStringMethod(method)) 302 { 303 return toString(); 304 } 305 else if (isWriteReplaceMethod(method)) 306 { 307 return writeReplace(); 308 } 309 else if (method.getDeclaringClass().equals(ILazyInitProxy.class)) 310 { 311 return getObjectLocator(); 312 } 313 314 if (target == null) 315 { 316 target = locator.locateProxyTarget(); 317 } 318 return proxy.invoke(target, args); 319 } 320 321 /** 322 * @see org.apache.wicket.proxy.ILazyInitProxy#getObjectLocator() 323 */ 324 @Override 325 public IProxyTargetLocator getObjectLocator() 326 { 327 return locator; 328 } 329 330 @Override 331 public Object writeReplace() throws ObjectStreamException 332 { 333 return new ProxyReplacement(typeName, locator); 334 } 335 } 336 337 /** 338 * Method interceptor for proxies representing concrete object not backed by an interface. These 339 * proxies are represented by cglib proxies. 340 * 341 * @author Igor Vaynberg (ivaynberg) 342 * 343 */ 344 public static class CGLibInterceptor extends AbstractCGLibInterceptor 345 { 346 private static final long serialVersionUID = 1L; 347 348 /** 349 * Constructor 350 * 351 * @param type 352 * class of the object this proxy was created for 353 * 354 * @param locator 355 * object locator used to locate the object this proxy represents 356 */ 357 public CGLibInterceptor(final Class<?> type, final IProxyTargetLocator locator) 358 { 359 super(type, locator); 360 } 361 } 362 363 /** 364 * @deprecated no longer used 365 */ 366 @Deprecated(forRemoval = true) 367 public static class SerializableNoOpCallback implements NoOp, Serializable 368 { 369 public static final NoOp INSTANCE = new SerializableNoOpCallback(); 370 } 371 372 /** 373 * Checks if the method is derived from Object.equals() 374 * 375 * @param method 376 * method being tested 377 * @return true if the method is derived from Object.equals(), false otherwise 378 */ 379 public static boolean isEqualsMethod(final Method method) 380 { 381 return (method.getReturnType() == boolean.class) && 382 (method.getParameterTypes().length == 1) && 383 (method.getParameterTypes()[0] == Object.class) && method.getName().equals("equals"); 384 } 385 386 /** 387 * Checks if the method is derived from Object.hashCode() 388 * 389 * @param method 390 * method being tested 391 * @return true if the method is defined from Object.hashCode(), false otherwise 392 */ 393 public static boolean isHashCodeMethod(final Method method) 394 { 395 return (method.getReturnType() == int.class) && (method.getParameterTypes().length == 0) && 396 method.getName().equals("hashCode"); 397 } 398 399 /** 400 * Checks if the method is derived from Object.toString() 401 * 402 * @param method 403 * method being tested 404 * @return true if the method is defined from Object.toString(), false otherwise 405 */ 406 public static boolean isToStringMethod(final Method method) 407 { 408 return (method.getReturnType() == String.class) && 409 (method.getParameterTypes().length == 0) && method.getName().equals("toString"); 410 } 411 412 /** 413 * Checks if the method is derived from Object.finalize() 414 * 415 * @param method 416 * method being tested 417 * @return true if the method is defined from Object.finalize(), false otherwise 418 */ 419 public static boolean isFinalizeMethod(final Method method) 420 { 421 return (method.getReturnType() == void.class) && (method.getParameterTypes().length == 0) && 422 method.getName().equals("finalize"); 423 } 424 425 /** 426 * Checks if the method is the writeReplace method 427 * 428 * @param method 429 * method being tested 430 * @return true if the method is the writeReplace method, false otherwise 431 */ 432 public static boolean isWriteReplaceMethod(final Method method) 433 { 434 return (method.getReturnType() == Object.class) && 435 (method.getParameterTypes().length == 0) && method.getName().equals("writeReplace"); 436 } 437 438 /** 439 * @deprecated no longer used 440 */ 441 @Deprecated(forRemoval = true) 442 public static class WicketNamingPolicy extends DefaultNamingPolicy 443 { 444 public static final WicketNamingPolicy INSTANCE = new WicketNamingPolicy(); 445 446 private WicketNamingPolicy() 447 { 448 } 449 } 450}