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