1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.service.impl;
17
18 import com.thoughtworks.xstream.XStream;
19 import com.thoughtworks.xstream.converters.ConverterLookup;
20 import com.thoughtworks.xstream.converters.MarshallingContext;
21 import com.thoughtworks.xstream.converters.UnmarshallingContext;
22 import com.thoughtworks.xstream.converters.collections.CollectionConverter;
23 import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
24 import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
25 import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
26 import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
27 import com.thoughtworks.xstream.core.ReferenceByXPathMarshallingStrategy;
28 import com.thoughtworks.xstream.core.TreeMarshaller;
29 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
30 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
31 import com.thoughtworks.xstream.io.path.PathTracker;
32 import com.thoughtworks.xstream.mapper.Mapper;
33 import org.apache.commons.lang.StringUtils;
34 import org.apache.commons.lang.reflect.FieldUtils;
35 import org.kuali.rice.krad.document.Document;
36 import org.kuali.rice.krad.service.DocumentSerializerService;
37 import org.kuali.rice.krad.service.LegacyDataAdapter;
38 import org.kuali.rice.krad.service.SerializerService;
39 import org.kuali.rice.krad.service.XmlObjectSerializerService;
40 import org.kuali.rice.krad.service.util.DateTimeConverter;
41 import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
42 import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
43 import org.kuali.rice.krad.util.documentserializer.PropertyType;
44 import org.kuali.rice.krad.util.documentserializer.SerializationState;
45 import org.springframework.beans.factory.annotation.Required;
46 import org.springframework.util.AutoPopulatingList;
47
48 import java.lang.reflect.Field;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.Iterator;
52 import java.util.List;
53 import java.util.Map;
54
55
56
57
58
59
60
61
62
63
64 public abstract class SerializerServiceBase implements SerializerService {
65
66 protected LegacyDataAdapter legacyDataAdapter;
67 protected XmlObjectSerializerService xmlObjectSerializerService;
68
69 protected XStream xstream;
70
71
72
73 protected ThreadLocal<PropertySerializabilityEvaluator> evaluators;
74 protected ThreadLocal<Map<String, SerializationState>> pathToSerializationState;
75 protected ThreadLocal<PathTracker> currentPathTracker;
76
77 public SerializerServiceBase() {
78 evaluators = new ThreadLocal<>();
79 currentPathTracker = new ThreadLocal<>();
80 pathToSerializationState = new ThreadLocal<>();
81
82 xstream = new XStream(new ProxyAndStateAwareJavaReflectionProvider());
83 xstream.setMarshallingStrategy(new PathTrackerSmugglingMarshallingStrategy(currentPathTracker));
84 xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
85 try {
86 Class<?> objListProxyClass = Class.forName("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
87 xstream.addDefaultImplementation(ArrayList.class, objListProxyClass);
88 xstream.addDefaultImplementation(AutoPopulatingList.class, objListProxyClass);
89 } catch ( Exception ex ) {
90
91 }
92 xstream.registerConverter(new AutoPopulatingListConverter(xstream.getMapper()));
93 xstream.registerConverter(new DateTimeConverter());
94 }
95
96
97
98
99
100 protected <T> String doSerialization(PropertySerializabilityEvaluator evaluator, T object, Serializer<T> serializer) {
101 try {
102 evaluators.set(evaluator);
103 pathToSerializationState.set(new HashMap<String, SerializationState>());
104 currentPathTracker.set(null);
105 return serializer.serialize(object);
106 } finally {
107 evaluators.set(null);
108 pathToSerializationState.set(null);
109 currentPathTracker.set(null);
110 }
111 }
112
113 public String serializeBusinessObjectToXml(Object businessObject) {
114 final PropertySerializabilityEvaluator propertySerizabilityEvaluator =
115 getPropertySerizabilityEvaluator(businessObject);
116 return doSerialization(propertySerizabilityEvaluator, businessObject, new Serializer<Object>() {
117 @Override
118 public String serialize(Object object) {
119 String xml;
120 if (propertySerizabilityEvaluator instanceof AlwaysTruePropertySerializibilityEvaluator) {
121 xml = getXmlObjectSerializerService().toXml(object);
122 } else {
123 xml = xstream.toXML(object);
124 }
125 return xml;
126 }
127 });
128 }
129
130
131
132
133
134
135
136
137
138
139
140 protected boolean ignoreField(Field field) {
141 return false;
142 }
143
144
145
146
147
148
149
150 protected abstract PropertySerializabilityEvaluator getPropertySerizabilityEvaluator(Object dataObject);
151
152 protected PathTracker getCurrentPathTracker() {
153 PathTracker pathTracker = currentPathTracker.get();
154 if (pathTracker == null) {
155 throw new IllegalStateException("No XStream PathTracker is bound to the current thread");
156 }
157 return pathTracker;
158 }
159
160
161
162
163
164
165
166
167 private String parseParentPath(String pathString) {
168 int indexOfLastSlash = pathString.lastIndexOf("/");
169 if (indexOfLastSlash == -1) {
170 throw new IllegalArgumentException("Expected a path");
171 }
172 return pathString.substring(0, indexOfLastSlash);
173 }
174
175
176
177
178 private SerializationState determineSerializationState(String pathString) {
179 if (pathToSerializationState.get().isEmpty()) {
180 pathToSerializationState.get().put(pathString, new SerializationState());
181 }
182 return searchSerializationState(pathString, pathString);
183 }
184
185
186
187
188 private SerializationState searchSerializationState(String pathString, String originalPath) {
189 if (StringUtils.isBlank(pathString)) {
190 throw new IllegalStateException("Failed to find existing SerializationState for path: " + originalPath);
191 }
192 SerializationState state = pathToSerializationState.get().get(pathString);
193 return state != null ? state : searchSerializationState(parseParentPath(pathString), originalPath);
194 }
195
196
197
198
199 private void registerSerializationStateForField(SerializationState state, String fieldName, PropertyType propertyType, String parentPath) {
200 String path = parentPath + "/" + fieldName;
201 if (pathToSerializationState.get().get(path) == null) {
202 SerializationState newState = new SerializationState(state);
203 newState.addSerializedProperty(fieldName, propertyType);
204 pathToSerializationState.get().put(path, newState);
205 }
206 }
207
208
209
210
211 protected interface Serializer<T> {
212 String serialize(T object);
213 }
214
215 public class ProxyConverter extends ReflectionConverter {
216 public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
217 super(mapper, reflectionProvider);
218 }
219 @Override
220 public boolean canConvert(Class clazz) {
221 return clazz.getName().contains("CGLIB") || clazz.getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
222 }
223
224 @Override
225 public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
226 if (obj.getClass().getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl")) {
227 List copiedList = new ArrayList();
228 List proxiedList = (List) obj;
229 for (Iterator iter = proxiedList.iterator(); iter.hasNext();) {
230 copiedList.add(iter.next());
231 }
232 context.convertAnother( copiedList );
233 }
234 else {
235 super.marshal(legacyDataAdapter.resolveProxy(obj), writer, context);
236 }
237 }
238
239 @Override
240 public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
241 return null;
242 }
243 }
244
245 public class ProxyAndStateAwareJavaReflectionProvider extends PureJavaReflectionProvider {
246 @Override
247 public void visitSerializableFields(Object object, Visitor visitor) {
248 PathTracker pathTracker = getCurrentPathTracker();
249 PropertySerializabilityEvaluator evaluator = evaluators.get();
250 String currentPath = pathTracker.getPath().toString();
251 SerializationState state = determineSerializationState(currentPath);
252
253
254 for (Iterator iterator = fieldDictionary.serializableFieldsFor(object.getClass()); iterator.hasNext();) {
255 Field field = (Field) iterator.next();
256 if (!fieldModifiersSupported(field)) {
257 continue;
258 }
259
260 if (ignoreField(field)) {
261 continue;
262 }
263
264 validateFieldAccess(field);
265
266 initializeField(object, field);
267
268 Object value = null;
269 try {
270 value = field.get(object);
271 } catch (IllegalArgumentException e) {
272 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
273 } catch (IllegalAccessException e) {
274 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
275 }
276
277 if (evaluator.isPropertySerializable(state, object, field.getName(), value)) {
278 if (value != null && legacyDataAdapter.isProxied(value)) {
279
280 value = legacyDataAdapter.resolveProxy(value);
281 }
282 PropertyType propertyType = evaluator.determinePropertyType(value);
283 registerSerializationStateForField(state, field.getName(), propertyType, currentPath);
284 visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
285 }
286 }
287 }
288
289 protected void initializeField(Object object, Field field) {
290 }
291 }
292
293 public class AutoPopulatingListConverter extends CollectionConverter {
294
295 public AutoPopulatingListConverter(Mapper mapper){
296 super(mapper);
297 }
298
299 @Override
300 public boolean canConvert(Class clazz) {
301 return clazz.equals(AutoPopulatingList.class);
302 }
303
304 }
305
306 private static class PathTrackerSmugglingMarshallingStrategy extends ReferenceByXPathMarshallingStrategy {
307
308 private final ThreadLocal<PathTracker> pathTrackerThreadLocal;
309
310 public PathTrackerSmugglingMarshallingStrategy(ThreadLocal<PathTracker> pathTrackerThreadLocal) {
311 super(ReferenceByXPathMarshallingStrategy.RELATIVE);
312 this.pathTrackerThreadLocal = pathTrackerThreadLocal;
313 }
314
315 @Override
316 protected TreeMarshaller createMarshallingContext(HierarchicalStreamWriter writer, ConverterLookup converterLookup, Mapper mapper) {
317 TreeMarshaller treeMarshaller = super.createMarshallingContext(writer, converterLookup, mapper);
318 smugglePathTracker(treeMarshaller);
319 return treeMarshaller;
320 }
321
322
323
324
325
326
327
328
329 private void smugglePathTracker(TreeMarshaller treeMarshaller) {
330 try {
331 PathTracker pathTracker = (PathTracker)FieldUtils.readField(treeMarshaller, "pathTracker", true);
332 if (pathTracker == null) {
333 throw new IllegalStateException("The pathTracker on xstream marshaller is null");
334 }
335 this.pathTrackerThreadLocal.set(pathTracker);
336 } catch (IllegalAccessException e) {
337 throw new IllegalStateException(e);
338 }
339 }
340
341 }
342
343 protected XmlObjectSerializerService getXmlObjectSerializerService() {
344 return this.xmlObjectSerializerService;
345 }
346
347 @Required
348 public void setXmlObjectSerializerService(XmlObjectSerializerService xmlObjectSerializerService) {
349 this.xmlObjectSerializerService = xmlObjectSerializerService;
350 }
351
352 @Required
353 public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) {
354 this.legacyDataAdapter = legacyDataAdapter;
355 }
356 }
357