View Javadoc
1   package org.kuali.rice.krad.service.impl;
2   
3   import com.thoughtworks.xstream.XStream;
4   import com.thoughtworks.xstream.converters.MarshallingContext;
5   import com.thoughtworks.xstream.converters.UnmarshallingContext;
6   import com.thoughtworks.xstream.converters.collections.CollectionConverter;
7   import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
8   import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
9   import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
10  import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
11  import com.thoughtworks.xstream.io.HierarchicalStreamReader;
12  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
13  import com.thoughtworks.xstream.mapper.Mapper;
14  import org.apache.commons.logging.Log;
15  import org.apache.commons.logging.LogFactory;
16  import org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl;
17  import org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl;
18  import org.kuali.rice.krad.service.KRADServiceLocator;
19  import org.kuali.rice.krad.service.PersistenceService;
20  import org.kuali.rice.krad.service.XmlObjectSerializerService;
21  
22  import java.lang.reflect.Field;
23  import java.util.*;
24  
25  /**
26   * Created by sheiksalahudeenm on 16/4/15.
27   */
28  public class XmlObjectSerializerServiceImpl implements XmlObjectSerializerService {
29      private static final Log LOG = LogFactory.getLog(XmlObjectSerializerServiceImpl.class);
30  
31  
32      private PersistenceService persistenceService;
33  
34      private XStream xstream;
35  
36      public XmlObjectSerializerServiceImpl() {
37          xstream = new XStream(new ProxyAwareJavaReflectionProvider());
38          xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider()));
39          xstream.addDefaultImplementation(ArrayList.class, ListProxyDefaultImpl.class);
40  
41          // register converters so that ListProxyDefaultImpl and SetProxyDefaultImpl are
42          // serialized as ArrayLists and HashSets
43          xstream.registerConverter(new ListProxyDefaultImplConverter(xstream.getMapper()));
44          xstream.registerConverter(new SetProxyDefaultImplConverter(xstream.getMapper()));
45      }
46  
47      /**
48       * @see org.kuali.rice.krad.service.XmlObjectSerializer#toXml(Object)
49       * @see org.kuali.rice.krad.service.XmlObjectSerializerService#toXml(Object)
50       */
51      public String toXml(Object object) {
52          if (LOG.isDebugEnabled()) {
53              LOG.debug("toXml(" + object + ") : \n" + xstream.toXML(object));
54          }
55          return xstream.toXML(object);
56      }
57  
58      /**
59       * @see org.kuali.rice.krad.service.XmlObjectSerializer#fromXml(String)
60       * @see org.kuali.rice.krad.service.XmlObjectSerializerService#fromXml(String)
61       */
62      public Object fromXml(String xml) {
63          if (LOG.isDebugEnabled()) {
64              LOG.debug("fromXml() : \n" + xml);
65          }
66          if (xml != null) {
67              xml = xml.replaceAll("--EnhancerByCGLIB--[0-9a-f]{0,8}", "");
68          }
69          return xstream.fromXML(xml);
70      }
71  
72      /**
73       * This custom converter only handles proxies for BusinessObjects.  List-type proxies are handled by configuring XStream to treat
74       * ListProxyDefaultImpl as ArrayLists (see constructor for this service).
75       */
76      public class ProxyConverter extends ReflectionConverter {
77          public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
78              super(mapper, reflectionProvider);
79          }
80  
81          @Override
82          // since the ReflectionConverter supertype defines canConvert without using a parameterized Class type, we must declare
83          // the overridden version the same way
84          @SuppressWarnings("unchecked")
85          public boolean canConvert(Class clazz) {
86              return clazz.getName().contains("CGLIB");
87          }
88  
89          @Override
90          public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
91              super.marshal(getPersistenceService().resolveProxy(obj), writer, context);
92          }
93  
94          // we shouldn't need an unmarshal method because all proxy metadata is taken out of the XML, so we'll reserialize as a base BO.
95      }
96  
97      public class ProxyAwareJavaReflectionProvider extends PureJavaReflectionProvider {
98  
99          public ProxyAwareJavaReflectionProvider() {
100             super();
101         }
102 
103         /**
104          * @see com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider#visitSerializableFields(Object, com.thoughtworks.xstream.converters.reflection.ReflectionProvider.Visitor)
105          */
106         @Override
107         public void visitSerializableFields(Object object, Visitor visitor) {
108             for (Iterator iterator = fieldDictionary.fieldsFor(object.getClass()); iterator.hasNext(); ) {
109                 Field field = (Field) iterator.next();
110                 if (!fieldModifiersSupported(field)) {
111                     continue;
112                 }
113                 validateFieldAccess(field);
114                 Object value = null;
115                 try {
116                     value = field.get(object);
117                     if (value != null && getPersistenceService().isProxied(value)) {
118                         value = getPersistenceService().resolveProxy(value);
119                     }
120                 } catch (IllegalArgumentException e) {
121                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
122                 } catch (IllegalAccessException e) {
123                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
124                 }
125                 visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
126             }
127         }
128 
129 
130     }
131 
132     public PersistenceService getPersistenceService() {
133         if (persistenceService == null) {
134             persistenceService = KRADServiceLocator.getPersistenceService();
135         }
136         return persistenceService;
137     }
138 
139 
140     /**
141      * Custom {@link com.thoughtworks.xstream.converters.Converter} that moves elements from a
142      * {@link org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl} into an {@link java.util.ArrayList} and marshals that instead.
143      */
144     private static class ListProxyDefaultImplConverter extends CollectionConverter {
145 
146         ListProxyDefaultImplConverter(Mapper mapper) {
147             super(mapper);
148         }
149 
150         @Override
151         @SuppressWarnings("unchecked")
152         public boolean canConvert(Class type) {
153             return ListProxyDefaultImpl.class.equals(type);
154         }
155 
156         /**
157          * moves elements from a {@link org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl} into an {@link java.util.ArrayList} and marshals that instead.
158          */
159         @Override
160         @SuppressWarnings("unchecked")
161         public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
162 
163             // move data to an ArrayList
164             ArrayList altered = new ArrayList((List) source);
165             // and marshal that
166             super.marshal(altered, writer, context);
167         }
168 
169         /**
170          * NOTE: using this class to unmarshal a ListProxyDefaultImpl inside Rice is unexpected, since those should
171          * have been converted to ArrayLists during marshalling. However, in the interest of attempting to
172          * provide a full Converter implementation here, we'll attempt to unmarshal it to an ArrayList by throwing
173          * away all the funny fields that a ListProxyDefaultImpl contains (Helpfully, their names all start with "_")'
174          */
175         @Override
176         @SuppressWarnings("unchecked")
177         public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
178 
179             ArrayList result = null;
180 
181             while (reader.hasMoreChildren()) {
182                 try {
183                     reader.moveDown();
184 
185                     if (reader.getNodeName().startsWith("_")) {
186                         // do nothing
187                     } else {
188                         if (result == null) {
189                             result = new ArrayList();
190                         } // lazy init
191                         addCurrentElementToCollection(reader, context, result, result);
192                     }
193                 } finally {
194                     reader.moveUp();
195                 }
196             }
197 
198             return result;
199         }
200     }
201 
202 
203     /**
204      * Custom {@link com.thoughtworks.xstream.converters.Converter} that moves elements from a
205      * {@link org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl} into a {@link java.util.HashSet} and marshals that instead.
206      */
207     private static class SetProxyDefaultImplConverter extends CollectionConverter {
208 
209         SetProxyDefaultImplConverter(Mapper mapper) {
210             super(mapper);
211         }
212 
213         @Override
214         @SuppressWarnings("unchecked")
215         public boolean canConvert(Class type) {
216             return SetProxyDefaultImpl.class.equals(type);
217         }
218 
219         /**
220          * moves elements from a {@link org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl} into a {@link java.util.HashSet} and marshals that instead.
221          */
222         @Override
223         @SuppressWarnings("unchecked")
224         public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
225 
226             // move data to a HashSet
227             HashSet altered = new HashSet((Set) source);
228             // and marshal that
229             super.marshal(altered, writer, context);
230         }
231 
232         /**
233          * NOTE: using this class to unmarshal a SetProxyDefaultImpl inside Rice is unexpected, since those should
234          * have been converted to HashSets during marshalling. However, in the interest of attempting to
235          * provide a full Converter implementation here, we'll attempt to unmarshal it to an HashSet by throwing
236          * away all the funny fields that a SetProxyDefaultImpl contains (Helpfully, their names all start with "_")'
237          */
238         @Override
239         @SuppressWarnings("unchecked")
240         public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
241 
242             HashSet result = null;
243 
244             while (reader.hasMoreChildren()) {
245                 try {
246                     reader.moveDown();
247 
248                     if (reader.getNodeName().startsWith("_")) {
249                         // do nothing
250                     } else {
251                         if (result == null) {
252                             result = new HashSet();
253                         } // lazy init
254                         addCurrentElementToCollection(reader, context, result, result);
255                     }
256                 } finally {
257                     reader.moveUp();
258                 }
259             }
260 
261             return result;
262         }
263     }
264 }