001: /*
002: * Copyright (C) 2006, 2007 XStream Committers.
003: * All rights reserved.
004: *
005: * The software in this package is published under the terms of the BSD
006: * style license a copy of which has been included with this distribution in
007: * the LICENSE.txt file.
008: *
009: * Created on 13. April 2006 by Joerg Schaible
010: */
011: package com.thoughtworks.xstream.converters.reflection;
012:
013: import com.thoughtworks.xstream.converters.ConversionException;
014: import com.thoughtworks.xstream.converters.MarshallingContext;
015: import com.thoughtworks.xstream.converters.UnmarshallingContext;
016: import com.thoughtworks.xstream.io.HierarchicalStreamReader;
017: import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
018: import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
019: import com.thoughtworks.xstream.mapper.CGLIBMapper;
020: import com.thoughtworks.xstream.mapper.Mapper;
021:
022: import net.sf.cglib.proxy.Callback;
023: import net.sf.cglib.proxy.Enhancer;
024: import net.sf.cglib.proxy.Factory;
025: import net.sf.cglib.proxy.MethodInterceptor;
026:
027: import java.lang.reflect.Field;
028: import java.util.ArrayList;
029: import java.util.HashMap;
030: import java.util.List;
031: import java.util.Map;
032:
033: /**
034: * Converts a proxy created by the CGLIB {@link Enhancer}. Such a proxy is recreated while deserializing the proxy. The
035: * converter does only work, if<br>
036: * <ul>
037: * <li>the DefaultNamingPolicy is used for the proxy's name</li>
038: * <li>only one CAllback is registered</li>
039: * <li>a possible super class has at least a protected default constructor</li>
040: * </ul>
041: * Note, that the this converter relies on the CGLIBMapper.
042: *
043: * @author Jörg Schaible
044: * @since 1.2
045: */
046: public class CGLIBEnhancedConverter extends SerializableConverter {
047:
048: // An alternative implementation is possible by using Enhancer.setCallbackType and
049: // Enhancer.createClass().
050: // In this case the converter must be derived from the AbstractReflectionConverter,
051: // the proxy info must be written/read in a separate structure first, then the
052: // Enhancer must create the type and at last the functionality of the ReflectionConverter
053: // must be used to create the instance. But let's see user feedback first.
054: // No support for multiple callbacks though ...
055:
056: private static String DEFAULT_NAMING_MARKER = "$$EnhancerByCGLIB$$";
057: private static String CALLBACK_MARKER = "CGLIB$CALLBACK_";
058: private transient Map fieldCache;
059:
060: public CGLIBEnhancedConverter(Mapper mapper,
061: ReflectionProvider reflectionProvider) {
062: super (mapper, new CGLIBFilteringReflectionProvider(
063: reflectionProvider));
064: this .fieldCache = new HashMap();
065: }
066:
067: public boolean canConvert(Class type) {
068: return (Enhancer.isEnhanced(type) && type.getName().indexOf(
069: DEFAULT_NAMING_MARKER) > 0)
070: || type == CGLIBMapper.Marker.class;
071: }
072:
073: public void marshal(Object source, HierarchicalStreamWriter writer,
074: MarshallingContext context) {
075: Class type = source.getClass();
076: boolean hasFactory = Factory.class.isAssignableFrom(type);
077: ExtendedHierarchicalStreamWriterHelper.startNode(writer,
078: "type", type);
079: context.convertAnother(type.getSuperclass());
080: writer.endNode();
081: writer.startNode("interfaces");
082: Class[] interfaces = type.getInterfaces();
083: for (int i = 0; i < interfaces.length; i++) {
084: if (interfaces[i] == Factory.class) {
085: continue;
086: }
087: ExtendedHierarchicalStreamWriterHelper.startNode(writer,
088: mapper.serializedClass(interfaces[i].getClass()),
089: interfaces[i].getClass());
090: context.convertAnother(interfaces[i]);
091: writer.endNode();
092: }
093: writer.endNode();
094: writer.startNode("hasFactory");
095: writer.setValue(String.valueOf(hasFactory
096: && type.getSuperclass() != Object.class));
097: writer.endNode();
098: Callback[] callbacks = hasFactory ? ((Factory) source)
099: .getCallbacks() : getCallbacks(source);
100: if (callbacks.length > 1) {
101: throw new ConversionException(
102: "Cannot handle CGLIB enhanced proxies with multiple callbacks");
103: }
104: boolean isInterceptor = MethodInterceptor.class
105: .isAssignableFrom(callbacks[0].getClass());
106:
107: ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper
108: .serializedClass(callbacks[0].getClass()), callbacks[0]
109: .getClass());
110: context.convertAnother(callbacks[0]);
111: writer.endNode();
112: try {
113: final Field field = type
114: .getDeclaredField("serialVersionUID");
115: field.setAccessible(true);
116: long serialVersionUID = field.getLong(null);
117: ExtendedHierarchicalStreamWriterHelper.startNode(writer,
118: "serialVersionUID", String.class);
119: writer.setValue(String.valueOf(serialVersionUID));
120: writer.endNode();
121: } catch (NoSuchFieldException e) {
122: // OK, ignore
123: } catch (IllegalAccessException e) {
124: // OK, ignore
125: }
126: if (isInterceptor && type.getSuperclass() != Object.class) {
127: writer.startNode("instance");
128: super .doMarshalConditionally(source, writer, context);
129: writer.endNode();
130: }
131: }
132:
133: private Callback[] getCallbacks(Object source) {
134: Class type = source.getClass();
135: List fields = (List) fieldCache.get(type.getName());
136: if (fields == null) {
137: fields = new ArrayList();
138: fieldCache.put(type.getName(), fields);
139: for (int i = 0; true; ++i) {
140: try {
141: Field field = type.getDeclaredField(CALLBACK_MARKER
142: + i);
143: field.setAccessible(true);
144: fields.add(field);
145: } catch (NoSuchFieldException e) {
146: break;
147: }
148: }
149: }
150: List list = new ArrayList();
151: for (int i = 0; i < fields.size(); ++i) {
152: try {
153: Field field = (Field) fields.get(i);
154: list.add(field.get(source));
155: } catch (IllegalAccessException e) {
156: throw new ObjectAccessException("Access to "
157: + type.getName() + "." + CALLBACK_MARKER + i
158: + " not allowed");
159: }
160: }
161: return (Callback[]) list.toArray(new Callback[list.size()]);
162: }
163:
164: public Object unmarshal(HierarchicalStreamReader reader,
165: UnmarshallingContext context) {
166: final Enhancer enhancer = new Enhancer();
167: reader.moveDown();
168: enhancer.setSuperclass((Class) context.convertAnother(null,
169: Class.class));
170: reader.moveUp();
171: reader.moveDown();
172: List interfaces = new ArrayList();
173: while (reader.hasMoreChildren()) {
174: reader.moveDown();
175: interfaces.add(context.convertAnother(null, mapper
176: .realClass(reader.getNodeName())));
177: reader.moveUp();
178: }
179: enhancer.setInterfaces((Class[]) interfaces
180: .toArray(new Class[interfaces.size()]));
181: reader.moveUp();
182: reader.moveDown();
183: enhancer.setUseFactory(Boolean.getBoolean(reader.getValue()));
184: reader.moveUp();
185: reader.moveDown();
186: enhancer.setCallback((Callback) context.convertAnother(null,
187: mapper.realClass(reader.getNodeName())));
188: reader.moveUp();
189: Object result = null;
190: while (reader.hasMoreChildren()) {
191: reader.moveDown();
192: if (reader.getNodeName().equals("serialVersionUID")) {
193: enhancer.setSerialVersionUID(Long.valueOf(reader
194: .getValue()));
195: } else if (reader.getNodeName().equals("instance")) {
196: result = enhancer.create();
197: super .doUnmarshalConditionally(result, reader, context);
198: }
199: reader.moveUp();
200: }
201: return serializationMethodInvoker
202: .callReadResolve(result == null ? enhancer.create()
203: : result);
204: }
205:
206: protected List hierarchyFor(Class type) {
207: List typeHierarchy = super .hierarchyFor(type);
208: // drop the CGLIB proxy
209: typeHierarchy.remove(typeHierarchy.size() - 1);
210: return typeHierarchy;
211: }
212:
213: private Object readResolve() {
214: fieldCache = new HashMap();
215: return this ;
216: }
217:
218: private static class CGLIBFilteringReflectionProvider extends
219: ReflectionProviderWrapper {
220:
221: public CGLIBFilteringReflectionProvider(
222: final ReflectionProvider reflectionProvider) {
223: super (reflectionProvider);
224: }
225:
226: public void visitSerializableFields(final Object object,
227: final Visitor visitor) {
228: wrapped.visitSerializableFields(object, new Visitor() {
229: public void visit(String name, Class type,
230: Class definedIn, Object value) {
231: if (!name.startsWith("CGLIB$")) {
232: visitor.visit(name, type, definedIn, value);
233: }
234: }
235: });
236: }
237: }
238: }
|