001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.krad.service.impl; 017 018import com.thoughtworks.xstream.XStream; 019import com.thoughtworks.xstream.converters.MarshallingContext; 020import com.thoughtworks.xstream.converters.UnmarshallingContext; 021import com.thoughtworks.xstream.converters.collections.CollectionConverter; 022import com.thoughtworks.xstream.converters.reflection.ObjectAccessException; 023import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; 024import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; 025import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; 026import com.thoughtworks.xstream.io.HierarchicalStreamReader; 027import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 028import com.thoughtworks.xstream.mapper.Mapper; 029import org.kuali.rice.krad.document.Document; 030import org.kuali.rice.krad.service.DocumentSerializerService; 031import org.kuali.rice.krad.service.LegacyDataAdapter; 032import org.kuali.rice.krad.service.SerializerService; 033import org.kuali.rice.krad.service.XmlObjectSerializerService; 034import org.kuali.rice.krad.service.util.DateTimeConverter; 035import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator; 036import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator; 037import org.kuali.rice.krad.util.documentserializer.PropertyType; 038import org.kuali.rice.krad.util.documentserializer.SerializationState; 039import org.springframework.beans.factory.annotation.Required; 040import org.springframework.util.AutoPopulatingList; 041 042import java.lang.reflect.Field; 043import java.util.ArrayList; 044import java.util.Iterator; 045import 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 */ 056public 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