/** * Copyright OPS4J * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author nmw * @version $Id: $Id */ package org.ops4j.pax.wicket.util.proxy; import java.io.InvalidClassException; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.List; import net.sf.cglib.core.DefaultNamingPolicy; import net.sf.cglib.core.Predicate; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.apache.wicket.Application; import org.apache.wicket.application.IClassResolver; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.settings.IApplicationSettings; import org.apache.wicket.util.io.IClusterable; import org.ops4j.pax.wicket.spi.ProxyTarget; import org.ops4j.pax.wicket.spi.ProxyTargetLocator; import org.ops4j.pax.wicket.spi.ReleasableProxyTarget; public class LazyInitProxyFactory { private static final List<?> BUILTINS = Arrays.asList(new Class[]{ String.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Character.class, Boolean.class }); /** * <p>createProxy.</p> * * @param type a {@link java.lang.Class} object. * @param locator a {@link org.ops4j.pax.wicket.spi.ProxyTargetLocator} object. * @return a {@link java.lang.Object} object. */ public static Object createProxy(final Class<?> type, final ProxyTargetLocator locator) { if (type.isPrimitive() || BUILTINS.contains(type) || Enum.class.isAssignableFrom(type)) { // We special-case primitives as sometimes people use these as // SpringBeans (WICKET-603, WICKET-906). Go figure. Object proxy = locator.locateProxyTarget(); Object realTarget = getRealTarget(proxy); if (proxy instanceof ReleasableProxyTarget) { // This is not so nice... but with a primitive this should'nt matter at all... ((ReleasableProxyTarget) proxy).releaseTarget(); } return realTarget; } else if (type.isInterface()) { JdkHandler handler = new JdkHandler(type, locator); try { return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{ type, Serializable.class, ILazyInitProxy.class, IWriteReplace.class }, handler); } catch (IllegalArgumentException e) { // While in the original Wicket Environment this is a failure of the context-classloader in PAX-WICKET // this is always an error of missing imports into the classloader. Right now we can do nothing here but // inform the user about the problem and throw an IllegalStateException instead wrapping up and // presenting the real problem. throw new IllegalStateException("The real problem is that the used wrapper classes are not imported " + "by the bundle using injection", e); } } else { CGLibInterceptor handler = new CGLibInterceptor(type, locator); Enhancer e = new Enhancer(); e.setInterfaces(new Class[]{ Serializable.class, ILazyInitProxy.class, IWriteReplace.class }); e.setSuperclass(type); e.setCallback(handler); //e.setClassLoader(LazyInitProxyFactory.class.getClassLoader()); e.setNamingPolicy(new DefaultNamingPolicy() { @Override public String getClassName(final String prefix, final String source, final Object key, final Predicate names) { return super.getClassName("WICKET_" + prefix, source, key, names); } }); return e.create(); } } protected static interface IWriteReplace { Object writeReplace() throws ObjectStreamException; } static class ProxyReplacement implements IClusterable { private static final long serialVersionUID = 1L; private final ProxyTargetLocator locator; private final String type; public ProxyReplacement(String type, ProxyTargetLocator locator) { this.type = type; this.locator = locator; } private Object readResolve() throws ObjectStreamException { Class<?> clazz; try { Application application = WebApplication.get(); IApplicationSettings appSettings = application.getApplicationSettings(); IClassResolver classResolver = appSettings.getClassResolver(); clazz = classResolver.resolveClass(type); } catch (ClassNotFoundException e) { throw new InvalidClassException(type, "could not resolve class [" + type + "] when deserializing proxy"); } ClassLoader currentClassloader = Thread.currentThread().getContextClassLoader(); try { ClassLoader classLoader = clazz.getClassLoader(); if (locator != null && locator.getParent() != null) { classLoader = locator.getParent().getClassLoader(); } if (classLoader != null) { Thread.currentThread().setContextClassLoader(classLoader); } return LazyInitProxyFactory.createProxy(clazz, locator); } finally { Thread.currentThread().setContextClassLoader(currentClassloader); } } } private static class CGLibInterceptor implements MethodInterceptor, ILazyInitProxy, Serializable, IWriteReplace { private static final long serialVersionUID = 1L; private final ProxyTargetLocator locator; private final String typeName; private transient Object target; public CGLibInterceptor(Class<?> type, ProxyTargetLocator locator) { super(); typeName = type.getName(); this.locator = locator; } public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { if (isFinalizeMethod(method)) { // swallow finalize call return null; } else if (isEqualsMethod(method)) { return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE; } else if (isHashCodeMethod(method)) { return new Integer(hashCode()); } else if (isToStringMethod(method)) { return toString(); } else if (isWriteReplaceMethod(method)) { return writeReplace(); } else if (method.getDeclaringClass().equals(ILazyInitProxy.class)) { return getObjectLocator(); } if (target == null) { target = locator.locateProxyTarget(); } Object invoke; try { invoke = proxy.invoke(getRealTarget(target), args); } finally { if (target instanceof ReleasableProxyTarget) { target = ((ReleasableProxyTarget) target).releaseTarget(); } } return invoke; } public ProxyTargetLocator getObjectLocator() { return locator; } public Object writeReplace() throws ObjectStreamException { return new ProxyReplacement(typeName, locator); } } /** * Invocation handler for proxies representing interface based object. For interface backed objects dynamic jdk * proxies are used. * * @author Igor Vaynberg (ivaynberg) */ private static class JdkHandler implements InvocationHandler, ILazyInitProxy, Serializable, IWriteReplace { private static final long serialVersionUID = 1L; private final ProxyTargetLocator locator; private final String typeName; private transient Object target; /** * Constructor * * @param type class of object this handler will represent * @param locator object locator used to locate the object this proxy represents */ public JdkHandler(Class<?> type, ProxyTargetLocator locator) { super(); this.locator = locator; typeName = type.getName(); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (isFinalizeMethod(method)) { // swallow finalize call return null; } else if (isEqualsMethod(method)) { return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE; } else if (isHashCodeMethod(method)) { return new Integer(hashCode()); } else if (isToStringMethod(method)) { return toString(); } else if (method.getDeclaringClass().equals(ILazyInitProxy.class)) { return getObjectLocator(); } else if (isWriteReplaceMethod(method)) { return writeReplace(); } if (target == null) { target = locator.locateProxyTarget(); } try { Object invoke; try { invoke = method.invoke(getRealTarget(target), args); } finally { if (target instanceof ReleasableProxyTarget) { target = ((ReleasableProxyTarget) target).releaseTarget(); } } return invoke; } catch (InvocationTargetException e) { throw e.getTargetException(); } } public ProxyTargetLocator getObjectLocator() { return locator; } public Object writeReplace() throws ObjectStreamException { return new ProxyReplacement(typeName, locator); } } /** * Checks if the method is derived from Object.equals() * * @param method method being tested * @return true if the method is derived from Object.equals(), false otherwise */ protected static boolean isEqualsMethod(Method method) { return method.getReturnType() == boolean.class && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == Object.class && method.getName().equals("equals"); } /** * Check if the object is of the special type {@link org.ops4j.pax.wicket.spi.ReleasableProxyTarget} and return the target of this interface * * @param target a {@link java.lang.Object} object. * @return the parameter target or the target of the {@link org.ops4j.pax.wicket.spi.ReleasableProxyTarget} if present */ public static Object getRealTarget(Object target) { if (target instanceof ProxyTarget) { return ((ProxyTarget) target).getTarget(); } return target; } /** * Checks if the method is derived from Object.hashCode() * * @param method method being tested * @return true if the method is defined from Object.hashCode(), false otherwise */ protected static boolean isHashCodeMethod(Method method) { return method.getReturnType() == int.class && method.getParameterTypes().length == 0 && method.getName().equals("hashCode"); } /** * Checks if the method is derived from Object.toString() * * @param method method being tested * @return true if the method is defined from Object.toString(), false otherwise */ protected static boolean isToStringMethod(Method method) { return method.getReturnType() == String.class && method.getParameterTypes().length == 0 && method.getName().equals("toString"); } /** * Checks if the method is derived from Object.finalize() * * @param method method being tested * @return true if the method is defined from Object.finalize(), false otherwise */ protected static boolean isFinalizeMethod(Method method) { return method.getReturnType() == void.class && method.getParameterTypes().length == 0 && method.getName().equals("finalize"); } /** * Checks if the method is the writeReplace method * * @param method method being tested * @return true if the method is the writeReplace method, false otherwise */ protected static boolean isWriteReplaceMethod(Method method) { return method.getReturnType() == Object.class && method.getParameterTypes().length == 0 && method.getName().equals("writeReplace"); } }