001 /**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.service.impl;
017
018 import com.thoughtworks.xstream.XStream;
019 import com.thoughtworks.xstream.converters.MarshallingContext;
020 import com.thoughtworks.xstream.converters.UnmarshallingContext;
021 import com.thoughtworks.xstream.converters.collections.CollectionConverter;
022 import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
023 import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
024 import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
025 import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
026 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
027 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
028 import com.thoughtworks.xstream.mapper.Mapper;
029 import org.kuali.rice.krad.document.Document;
030 import org.kuali.rice.krad.service.DocumentSerializerService;
031 import org.kuali.rice.krad.service.LegacyDataAdapter;
032 import org.kuali.rice.krad.service.SerializerService;
033 import org.kuali.rice.krad.service.XmlObjectSerializerService;
034 import org.kuali.rice.krad.service.util.DateTimeConverter;
035 import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
036 import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
037 import org.kuali.rice.krad.util.documentserializer.PropertyType;
038 import org.kuali.rice.krad.util.documentserializer.SerializationState;
039 import org.springframework.beans.factory.annotation.Required;
040 import org.springframework.util.AutoPopulatingList;
041
042 import java.lang.reflect.Field;
043 import java.util.ArrayList;
044 import java.util.Iterator;
045 import java.util.List;
046
047 /**
048 * Default implementation of the {@link DocumentSerializerService}. If no <workflowProperties> have been defined in the
049 * data dictionary for a document type (i.e. {@link Document#getDocumentPropertySerizabilityEvaluator()} returns an instance of
050 * {@link AlwaysTruePropertySerializibilityEvaluator}), then this service will revert to using the {@link XmlObjectSerializerService}
051 * bean, which was the old way of serializing a document for routing. If workflowProperties are defined, then this implementation
052 * will selectively serialize items.
053 *
054 * @author Kuali Rice Team (rice.collab@kuali.org)
055 */
056 public abstract class SerializerServiceBase implements SerializerService {
057
058 protected LegacyDataAdapter legacyDataAdapter;
059 protected XmlObjectSerializerService xmlObjectSerializerService;
060
061 protected XStream xstream;
062 protected ThreadLocal<SerializationState> serializationStates;
063 protected ThreadLocal<PropertySerializabilityEvaluator> evaluators;
064
065 public SerializerServiceBase() {
066 serializationStates = new ThreadLocal<SerializationState>();
067 evaluators = new ThreadLocal<PropertySerializabilityEvaluator>();
068
069 xstream = new XStream(new ProxyAndStateAwareJavaReflectionProvider());
070 xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
071 try {
072 Class<?> objListProxyClass = Class.forName("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
073 xstream.addDefaultImplementation(ArrayList.class, objListProxyClass);
074 xstream.addDefaultImplementation(AutoPopulatingList.class, objListProxyClass);
075 } catch ( Exception ex ) {
076 // Do nothing - this will blow if the OJB class does not exist, which it won't in some installs
077 }
078 xstream.registerConverter(new AutoPopulatingListConverter(xstream.getMapper()));
079 xstream.registerConverter(new DateTimeConverter());
080 }
081
082 /**
083 * @see org.kuali.rice.krad.service.DocumentSerializerService#serializeDocumentToXmlForRouting(org.kuali.rice.krad.document.Document)
084 */
085 public String serializeBusinessObjectToXml(Object businessObject) {
086 PropertySerializabilityEvaluator propertySerizabilityEvaluator =
087 getPropertySerizabilityEvaluator(businessObject);
088 evaluators.set(propertySerizabilityEvaluator);
089 SerializationState state = new SerializationState(); //createNewDocumentSerializationState(document);
090 serializationStates.set(state);
091
092 //Object xmlWrapper = null;//wrapDocumentWithMetadata(document);
093 String xml;
094 if (propertySerizabilityEvaluator instanceof AlwaysTruePropertySerializibilityEvaluator) {
095 xml = getXmlObjectSerializerService().toXml(businessObject);
096 } else {
097 xml = xstream.toXML(businessObject);
098 }
099
100 evaluators.set(null);
101 serializationStates.set(null);
102 return xml;
103 }
104
105 /**
106 * Method called by the ProxyAndStateAwareJavaReflectionProvider during serialization to determine if a field
107 * should be omitted from the serialized form.
108 *
109 * <p>This is a short circuit check that will avoid more expensive calls in to the PropertySerializabilityEvaluator
110 * if it returns true.</p>
111 *
112 * @param field the field
113 * @return true if the field should be omitted
114 */
115 protected boolean ignoreField(Field field) {
116 return false;
117 }
118
119 /**
120 * Get the appropriate {@link PropertySerializabilityEvaluator} for the given dataObject.
121 *
122 * @param dataObject the data object
123 * @return the evaluator
124 */
125 protected abstract PropertySerializabilityEvaluator getPropertySerizabilityEvaluator(Object dataObject);
126
127 public class ProxyConverter extends ReflectionConverter {
128 public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
129 super(mapper, reflectionProvider);
130 }
131 @Override
132 public boolean canConvert(Class clazz) {
133 return clazz.getName().contains("CGLIB") || clazz.getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
134 }
135
136 @Override
137 public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
138 if (obj.getClass().getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl")) {
139 List copiedList = new ArrayList();
140 List proxiedList = (List) obj;
141 for (Iterator iter = proxiedList.iterator(); iter.hasNext();) {
142 copiedList.add(iter.next());
143 }
144 context.convertAnother( copiedList );
145 }
146 else {
147 super.marshal(legacyDataAdapter.resolveProxy(obj), writer, context);
148 }
149 }
150
151 @Override
152 public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
153 return null;
154 }
155 }
156
157 public class ProxyAndStateAwareJavaReflectionProvider extends PureJavaReflectionProvider {
158 @Override
159 public void visitSerializableFields(Object object, Visitor visitor) {
160 SerializationState state = serializationStates.get();
161 PropertySerializabilityEvaluator evaluator = evaluators.get();
162
163 for (Iterator iterator = fieldDictionary.serializableFieldsFor(object.getClass()); iterator.hasNext();) {
164 Field field = (Field) iterator.next();
165 if (!fieldModifiersSupported(field)) {
166 continue;
167 }
168
169 if (ignoreField(field)) {
170 continue;
171 }
172
173 validateFieldAccess(field);
174
175 initializeField(object, field);
176
177 Object value = null;
178 try {
179 value = field.get(object);
180 } catch (IllegalArgumentException e) {
181 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
182 } catch (IllegalAccessException e) {
183 throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
184 }
185
186 if (evaluator.isPropertySerializable(state, object, field.getName(), value)) {
187 if (value != null && legacyDataAdapter.isProxied(value)) {
188 // resolve proxies after we determine that it's serializable
189 value = legacyDataAdapter.resolveProxy(value);
190 }
191 PropertyType propertyType = evaluator.determinePropertyType(value);
192 state.addSerializedProperty(field.getName(), propertyType);
193 visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
194 state.removeSerializedProperty();
195 }
196 }
197 }
198
199 protected void initializeField(Object object, Field field) {
200 }
201 }
202
203 public class AutoPopulatingListConverter extends CollectionConverter {
204
205 public AutoPopulatingListConverter(Mapper mapper){
206 super(mapper);
207 }
208
209 @Override
210 public boolean canConvert(Class clazz) {
211 return clazz.equals(AutoPopulatingList.class);
212 }
213
214 }
215
216 protected XmlObjectSerializerService getXmlObjectSerializerService() {
217 return this.xmlObjectSerializerService;
218 }
219
220 @Required
221 public void setXmlObjectSerializerService(XmlObjectSerializerService xmlObjectSerializerService) {
222 this.xmlObjectSerializerService = xmlObjectSerializerService;
223 }
224
225 protected SerializationState createNewDocumentSerializationState(Document document) {
226 return new SerializationState();
227 }
228
229 @Required
230 public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) {
231 this.legacyDataAdapter = legacyDataAdapter;
232 }
233 }
234