001package org.kuali.rice.krad.service.impl; 002 003import com.thoughtworks.xstream.XStream; 004import com.thoughtworks.xstream.converters.MarshallingContext; 005import com.thoughtworks.xstream.converters.UnmarshallingContext; 006import com.thoughtworks.xstream.converters.collections.CollectionConverter; 007import com.thoughtworks.xstream.converters.reflection.ObjectAccessException; 008import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; 009import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; 010import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; 011import com.thoughtworks.xstream.io.HierarchicalStreamReader; 012import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 013import com.thoughtworks.xstream.mapper.Mapper; 014import org.apache.commons.logging.Log; 015import org.apache.commons.logging.LogFactory; 016import org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl; 017import org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl; 018import org.kuali.rice.krad.service.KRADServiceLocator; 019import org.kuali.rice.krad.service.PersistenceService; 020import org.kuali.rice.krad.service.XmlObjectSerializerService; 021 022import java.lang.reflect.Field; 023import java.util.ArrayList; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Set; 028 029/** 030 * Created by sheiksalahudeenm on 16/4/15. 031 */ 032public class XmlObjectSerializerServiceImpl implements XmlObjectSerializerService { 033 private static final Log LOG = LogFactory.getLog(XmlObjectSerializerServiceImpl.class); 034 035 036 private PersistenceService persistenceService; 037 038 private XStream xstream; 039 040 public XmlObjectSerializerServiceImpl() { 041 xstream = new XStream(new ProxyAwareJavaReflectionProvider()); 042 xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider())); 043 xstream.addDefaultImplementation(ArrayList.class, ListProxyDefaultImpl.class); 044 045 // register converters so that ListProxyDefaultImpl and SetProxyDefaultImpl are 046 // serialized as ArrayLists and HashSets 047 xstream.registerConverter(new ListProxyDefaultImplConverter(xstream.getMapper())); 048 xstream.registerConverter(new SetProxyDefaultImplConverter(xstream.getMapper())); 049 } 050 051 /** 052 * @see org.kuali.rice.krad.service.XmlObjectSerializer#toXml(java.lang.Object) 053 * @see org.kuali.rice.krad.service.XmlObjectSerializerService#toXml(java.lang.Object) 054 */ 055 public String toXml(Object object) { 056 if (LOG.isDebugEnabled()) { 057 LOG.debug("toXml(" + object + ") : \n" + xstream.toXML(object)); 058 } 059 return xstream.toXML(object); 060 } 061 062 /** 063 * @see org.kuali.rice.krad.service.XmlObjectSerializer#fromXml(java.lang.String) 064 * @see org.kuali.rice.krad.service.XmlObjectSerializerService#fromXml(java.lang.String) 065 */ 066 public Object fromXml(String xml) { 067 if (LOG.isDebugEnabled()) { 068 LOG.debug("fromXml() : \n" + xml); 069 } 070 if (xml != null) { 071 xml = xml.replaceAll("--EnhancerByCGLIB--[0-9a-f]{0,8}", ""); 072 } 073 return xstream.fromXML(xml); 074 } 075 076 /** 077 * This custom converter only handles proxies for BusinessObjects. List-type proxies are handled by configuring XStream to treat 078 * ListProxyDefaultImpl as ArrayLists (see constructor for this service). 079 */ 080 public class ProxyConverter extends ReflectionConverter { 081 public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) { 082 super(mapper, reflectionProvider); 083 } 084 085 @Override 086 // since the ReflectionConverter supertype defines canConvert without using a parameterized Class type, we must declare 087 // the overridden version the same way 088 @SuppressWarnings("unchecked") 089 public boolean canConvert(Class clazz) { 090 return clazz.getName().contains("CGLIB"); 091 } 092 093 @Override 094 public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) { 095 super.marshal(getPersistenceService().resolveProxy(obj), writer, context); 096 } 097 098 // 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. 099 } 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}