com.thoughtworks.xstream.converters.reflection.CGLIBEnhancedConverter Maven / Gradle / Ivy
/*
* Copyright (C) 2006, 2007, 2008, 2010, 2011, 2013, 2014, 2015, 2016 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 13. April 2006 by Joerg Schaible
*/
package com.thoughtworks.xstream.converters.reflection;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.CGLIBMapper;
import com.thoughtworks.xstream.mapper.Mapper;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.NoOp;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Converts a proxy created by the CGLIB {@link Enhancer}. Such a proxy is recreated while
* deserializing the proxy. The converter does only work, if
*
* - the DefaultNamingPolicy is used for the proxy's name
* - the proxy uses a factory or only one Callback is registered
* - a possible super class has at least a protected default constructor
*
* Note, that the this converter relies on the CGLIBMapper.
*
* @author Jörg Schaible
* @since 1.2
*/
public class CGLIBEnhancedConverter extends SerializableConverter {
private static String DEFAULT_NAMING_MARKER = "$$EnhancerByCGLIB$$";
private static String CALLBACK_MARKER = "CGLIB$CALLBACK_";
private transient Map fieldCache;
/**
* Construct a CGLIBEnhancedConverter.
* @param mapper the mapper chain instance
* @param reflectionProvider the reflection provider
* @param classLoaderReference the reference to the {@link ClassLoader} of the XStream instance
* @since 1.4.5
*/
public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoaderReference classLoaderReference) {
super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), classLoaderReference);
this.fieldCache = new HashMap();
}
/**
* @deprecated As of 1.4.5 use {@link #CGLIBEnhancedConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
*/
public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider, ClassLoader classLoader) {
super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), classLoader);
this.fieldCache = new HashMap();
}
/**
* @deprecated As of 1.4 use {@link #CGLIBEnhancedConverter(Mapper, ReflectionProvider, ClassLoaderReference)}
*/
public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
this(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider), CGLIBEnhancedConverter.class.getClassLoader());
}
public boolean canConvert(Class type) {
return (Enhancer.isEnhanced(type) && type.getName().indexOf(DEFAULT_NAMING_MARKER) > 0)
|| type == CGLIBMapper.Marker.class;
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Class type = source.getClass();
boolean hasFactory = Factory.class.isAssignableFrom(type);
ExtendedHierarchicalStreamWriterHelper.startNode(writer, "type", type);
context.convertAnother(type.getSuperclass());
writer.endNode();
writer.startNode("interfaces");
Class[] interfaces = type.getInterfaces();
for (int i = 0; i < interfaces.length; i++ ) {
if (interfaces[i] == Factory.class) {
continue;
}
ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
.serializedClass(interfaces[i].getClass()), interfaces[i].getClass());
context.convertAnother(interfaces[i]);
writer.endNode();
}
writer.endNode();
writer.startNode("hasFactory");
writer.setValue(String.valueOf(hasFactory));
writer.endNode();
Map callbackIndexMap = null;
Callback[] callbacks = hasFactory
? ((Factory)source).getCallbacks()
: getCallbacks(source);
if (callbacks.length > 1) {
if (hasFactory) {
callbackIndexMap = createCallbackIndexMap((Factory)source);
} else {
ConversionException exception = new ConversionException(
"Cannot handle CGLIB enhanced proxies without factory that have multiple callbacks");
exception.add("proxy-superclass", type.getSuperclass().getName());
exception.add("number-of-callbacks", String.valueOf(callbacks.length));
throw exception;
}
writer.startNode("callbacks");
writer.startNode("mapping");
context.convertAnother(callbackIndexMap);
writer.endNode();
}
boolean hasInterceptor = false;
for (int i = 0; i < callbacks.length; i++ ) {
final Callback callback = callbacks[i];
if (callback == null) {
String name = mapper.serializedClass(null);
writer.startNode(name);
writer.endNode();
} else {
hasInterceptor = hasInterceptor
|| MethodInterceptor.class.isAssignableFrom(callback.getClass());
ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
.serializedClass(callback.getClass()), callback.getClass());
context.convertAnother(callback);
writer.endNode();
}
}
if (callbacks.length > 1) {
writer.endNode();
}
try {
final Field field = type.getDeclaredField("serialVersionUID");
if (!field.isAccessible()) {
field.setAccessible(true);
}
long serialVersionUID = field.getLong(null);
ExtendedHierarchicalStreamWriterHelper.startNode(
writer, "serialVersionUID", String.class);
writer.setValue(String.valueOf(serialVersionUID));
writer.endNode();
} catch (NoSuchFieldException e) {
// OK, ignore
} catch (IllegalAccessException e) {
ObjectAccessException exception = new ObjectAccessException("Cannot access field", e);
exception.add("field", type.getName() + ".serialVersionUID");
throw exception;
}
if (hasInterceptor) {
writer.startNode("instance");
super.doMarshalConditionally(source, writer, context);
writer.endNode();
}
}
private Callback[] getCallbacks(Object source) {
Class type = source.getClass();
List fields = (List)fieldCache.get(type.getName());
if (fields == null) {
fields = new ArrayList();
fieldCache.put(type.getName(), fields);
for (int i = 0; true; ++i) {
try {
Field field = type.getDeclaredField(CALLBACK_MARKER + i);
if (!field.isAccessible()) {
field.setAccessible(true);
}
fields.add(field);
} catch (NoSuchFieldException e) {
break;
}
}
}
List list = new ArrayList();
for (int i = 0; i < fields.size(); ++i) {
try {
Field field = (Field)fields.get(i);
Object callback = field.get(source);
list.add(callback);
} catch (IllegalAccessException e) {
ObjectAccessException exception = new ObjectAccessException("Cannot access field", e);
exception.add("field", type.getName() + "." + CALLBACK_MARKER + i);
throw exception;
}
}
return (Callback[])list.toArray(new Callback[list.size()]);
}
private Map createCallbackIndexMap(Factory source) {
Callback[] originalCallbacks = source.getCallbacks();
Callback[] reverseEngineeringCallbacks = new Callback[originalCallbacks.length];
Map callbackIndexMap = new HashMap();
int idxNoOp = -1;
for (int i = 0; i < originalCallbacks.length; i++ ) {
Callback callback = originalCallbacks[i];
if (callback == null) {
reverseEngineeringCallbacks[i] = null;
} else if (NoOp.class.isAssignableFrom(callback.getClass())) {
reverseEngineeringCallbacks[i] = NoOp.INSTANCE;
idxNoOp = i;
} else {
reverseEngineeringCallbacks[i] = createReverseEngineeredCallbackOfProperType(
callback, i, callbackIndexMap);
}
}
try {
source.setCallbacks(reverseEngineeringCallbacks);
final Set interfaces = new HashSet();
final Set methods = new HashSet();
Class type = source.getClass();
do {
methods.addAll(Arrays.asList(type.getDeclaredMethods()));
methods.addAll(Arrays.asList(type.getMethods()));
Class[] implementedInterfaces = type.getInterfaces();
interfaces.addAll(Arrays.asList(implementedInterfaces));
type = type.getSuperclass();
} while (type != null);
for (final Iterator iterator = interfaces.iterator(); iterator.hasNext();) {
type = (Class)iterator.next();
methods.addAll(Arrays.asList(type.getDeclaredMethods()));
}
for (final Iterator iter = methods.iterator(); iter.hasNext();) {
final Method method = (Method)iter.next();
if (!method.isAccessible()) {
method.setAccessible(true);
}
if (Factory.class.isAssignableFrom(method.getDeclaringClass())
|| (method.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) > 0) {
iter.remove();
continue;
}
Class[] parameterTypes = method.getParameterTypes();
Method calledMethod = method;
try {
if ((method.getModifiers() & Modifier.ABSTRACT) > 0) {
calledMethod = source.getClass().getMethod(
method.getName(), method.getParameterTypes());
}
callbackIndexMap.put(null, method);
calledMethod.invoke(source, parameterTypes == null
? (Object[])null
: createNullArguments(parameterTypes));
} catch (IllegalAccessException e) {
ObjectAccessException exception = new ObjectAccessException("Cannot access method", e);
exception.add("method", calledMethod.toString());
throw exception;
} catch (InvocationTargetException e) {
// OK, ignore
} catch (NoSuchMethodException e) {
ConversionException exception = new ConversionException(
"CGLIB enhanced proxies wit abstract nethod that has not been implemented");
exception.add("proxy-superclass", type.getSuperclass().getName());
exception.add("method", method.toString());
throw exception;
}
if (callbackIndexMap.containsKey(method)) {
iter.remove();
}
}
if (idxNoOp >= 0) {
Integer idx = new Integer(idxNoOp);
for (final Iterator iter = methods.iterator(); iter.hasNext();) {
callbackIndexMap.put(iter.next(), idx);
}
}
} finally {
source.setCallbacks(originalCallbacks);
}
callbackIndexMap.remove(null);
return callbackIndexMap;
}
private Object[] createNullArguments(Class[] parameterTypes) {
Object[] arguments = new Object[parameterTypes.length];
for (int i = 0; i < arguments.length; i++ ) {
Class type = parameterTypes[i];
if (type.isPrimitive()) {
if (type == byte.class) {
arguments[i] = new Byte((byte)0);
} else if (type == short.class) {
arguments[i] = new Short((short)0);
} else if (type == int.class) {
arguments[i] = new Integer(0);
} else if (type == long.class) {
arguments[i] = new Long(0);
} else if (type == float.class) {
arguments[i] = new Float(0);
} else if (type == double.class) {
arguments[i] = new Double(0);
} else if (type == char.class) {
arguments[i] = new Character('\0');
} else {
arguments[i] = Boolean.FALSE;
}
}
}
return arguments;
}
private Callback createReverseEngineeredCallbackOfProperType(Callback callback, int index,
Map callbackIndexMap) {
Class iface = null;
Class[] interfaces = callback.getClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++ ) {
if (Callback.class.isAssignableFrom(interfaces[i])) {
iface = interfaces[i];
if (iface == Callback.class) {
ConversionException exception = new ConversionException(
"Cannot handle CGLIB callback");
exception.add("CGLIB-callback-type", callback.getClass().getName());
throw exception;
}
interfaces = iface.getInterfaces();
if (Arrays.asList(interfaces).contains(Callback.class)) {
break;
}
i = -1;
}
}
return (Callback)Proxy.newProxyInstance(
iface.getClassLoader(), new Class[]{iface},
new ReverseEngineeringInvocationHandler(index, callbackIndexMap));
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
final Enhancer enhancer = new Enhancer();
reader.moveDown();
enhancer.setSuperclass((Class)context.convertAnother(null, Class.class));
reader.moveUp();
reader.moveDown();
List interfaces = new ArrayList();
while (reader.hasMoreChildren()) {
reader.moveDown();
interfaces
.add(context.convertAnother(null, mapper.realClass(reader.getNodeName())));
reader.moveUp();
}
enhancer.setInterfaces((Class[])interfaces.toArray(new Class[interfaces.size()]));
reader.moveUp();
reader.moveDown();
boolean useFactory = Boolean.valueOf(reader.getValue()).booleanValue();
enhancer.setUseFactory(useFactory);
reader.moveUp();
List callbacksToEnhance = new ArrayList();
List callbacks = new ArrayList();
Map callbackIndexMap = null;
reader.moveDown();
if ("callbacks".equals(reader.getNodeName())) {
reader.moveDown();
callbackIndexMap = (Map)context.convertAnother(null, HashMap.class);
reader.moveUp();
while (reader.hasMoreChildren()) {
reader.moveDown();
readCallback(reader, context, callbacksToEnhance, callbacks);
reader.moveUp();
}
} else {
readCallback(reader, context, callbacksToEnhance, callbacks);
}
enhancer.setCallbacks((Callback[])callbacksToEnhance
.toArray(new Callback[callbacksToEnhance.size()]));
if (callbackIndexMap != null) {
enhancer.setCallbackFilter(new ReverseEngineeredCallbackFilter(callbackIndexMap));
}
reader.moveUp();
Object result = null;
while (reader.hasMoreChildren()) {
reader.moveDown();
if (reader.getNodeName().equals("serialVersionUID")) {
enhancer.setSerialVersionUID(Long.valueOf(reader.getValue()));
} else if (reader.getNodeName().equals("instance")) {
result = create(enhancer, callbacks, useFactory);
super.doUnmarshalConditionally(result, reader, context);
}
reader.moveUp();
}
if (result == null) {
result = create(enhancer, callbacks, useFactory);
}
return serializationMembers.callReadResolve(result);
}
private void readCallback(HierarchicalStreamReader reader, UnmarshallingContext context,
List callbacksToEnhance, List callbacks) {
Callback callback = (Callback)context.convertAnother(null, mapper.realClass(reader
.getNodeName()));
callbacks.add(callback);
if (callback == null) {
callbacksToEnhance.add(NoOp.INSTANCE);
} else {
callbacksToEnhance.add(callback);
}
}
private Object create(final Enhancer enhancer, List callbacks, boolean useFactory) {
Object result = enhancer.create();
if (useFactory) {
((Factory)result).setCallbacks((Callback[])callbacks.toArray(new Callback[callbacks
.size()]));
}
return result;
}
protected List hierarchyFor(Class type) {
List typeHierarchy = super.hierarchyFor(type);
// drop the CGLIB proxy
typeHierarchy.remove(typeHierarchy.size() - 1);
return typeHierarchy;
}
protected Object readResolve() {
super.readResolve();
fieldCache = new HashMap();
return this;
}
private static class CGLIBFilteringReflectionProvider extends ReflectionProviderWrapper {
public CGLIBFilteringReflectionProvider(final ReflectionProvider reflectionProvider) {
super(reflectionProvider);
}
public void visitSerializableFields(final Object object, final Visitor visitor) {
wrapped.visitSerializableFields(object, new Visitor() {
public void visit(String name, Class type, Class definedIn, Object value) {
if (!name.startsWith("CGLIB$")) {
visitor.visit(name, type, definedIn, value);
}
}
});
}
}
private static final class ReverseEngineeringInvocationHandler implements InvocationHandler {
private final Integer index;
private final Map indexMap;
public ReverseEngineeringInvocationHandler(int index, Map indexMap) {
this.indexMap = indexMap;
this.index = new Integer(index);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
indexMap.put(indexMap.get(null), index);
return null;
}
}
private static class ReverseEngineeredCallbackFilter implements CallbackFilter {
private final Map callbackIndexMap;
public ReverseEngineeredCallbackFilter(Map callbackIndexMap) {
this.callbackIndexMap = callbackIndexMap;
}
public int accept(Method method) {
if (!callbackIndexMap.containsKey(method)) {
ConversionException exception = new ConversionException(
"CGLIB callback not detected in reverse engineering");
exception.add("CGLIB-callback", method.toString());
throw exception;
}
return ((Integer)callbackIndexMap.get(method)).intValue();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy