/* * Copyright (C) 2006, 2007, 2008 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.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<br> * <ul> * <li>the DefaultNamingPolicy is used for the proxy's name</li> * <li>the proxy uses a factory or only one Callback is registered</li> * <li>a possible super class has at least a protected default constructor</li> * </ul> * 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; public CGLIBEnhancedConverter(Mapper mapper, ReflectionProvider reflectionProvider) { super(mapper, new CGLIBFilteringReflectionProvider(reflectionProvider)); this.fieldCache = new HashMap(); } public boolean canConvert(Class type) { // return (Enhancer.isEnhanced(type) && type.getName().indexOf(DEFAULT_NAMING_MARKER) > 0) // || type == CGLIBMapper.Marker.class; return false; } public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { Class type = source.getClass(); // boolean hasFactory = Factory.class.isAssignableFrom(type); boolean hasFactory = false; 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"); 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) { throw new ObjectAccessException("Access to serialverionUID of " + type.getName() + " not allowed"); } 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); // 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) { // throw new ObjectAccessException("Access to " // + type.getName() // + "." // + CALLBACK_MARKER // + i // + " not allowed"); // } // } // 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(); // 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) { // throw new ObjectAccessException("Access to " // + calledMethod // + " not allowed"); // } 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 serializationMethodInvoker.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; } private Object 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(); // } // // } }