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