View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.service.impl;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Field;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  
24  import javax.persistence.Transient;
25  
26  import org.kuali.rice.krad.document.Document;
27  import org.kuali.rice.krad.service.DocumentSerializerService;
28  import org.kuali.rice.krad.service.LegacyDataAdapter;
29  import org.kuali.rice.krad.service.SerializerService;
30  import org.kuali.rice.krad.service.XmlObjectSerializerService;
31  import org.kuali.rice.krad.service.util.DateTimeConverter;
32  import org.kuali.rice.krad.util.documentserializer.AlwaysTruePropertySerializibilityEvaluator;
33  import org.kuali.rice.krad.util.documentserializer.PropertySerializabilityEvaluator;
34  import org.kuali.rice.krad.util.documentserializer.PropertyType;
35  import org.kuali.rice.krad.util.documentserializer.SerializationState;
36  import org.springframework.beans.factory.annotation.Required;
37  import org.springframework.util.AutoPopulatingList;
38  
39  import com.thoughtworks.xstream.XStream;
40  import com.thoughtworks.xstream.converters.MarshallingContext;
41  import com.thoughtworks.xstream.converters.UnmarshallingContext;
42  import com.thoughtworks.xstream.converters.collections.CollectionConverter;
43  import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
44  import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
45  import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
46  import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
47  import com.thoughtworks.xstream.io.HierarchicalStreamReader;
48  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
49  import com.thoughtworks.xstream.mapper.Mapper;
50  
51  /**
52   * Default implementation of the {@link DocumentSerializerService}.  If no <workflowProperties> have been defined in the
53   * data dictionary for a document type (i.e. {@link Document#getDocumentPropertySerizabilityEvaluator()} returns an instance of
54   * {@link AlwaysTruePropertySerializibilityEvaluator}), then this service will revert to using the {@link XmlObjectSerializerService}
55   * bean, which was the old way of serializing a document for routing.  If workflowProperties are defined, then this implementation
56   * will selectively serialize items.
57   *
58   * @author Kuali Rice Team (rice.collab@kuali.org)
59   */
60  public abstract class SerializerServiceBase implements SerializerService  {
61  
62  	protected LegacyDataAdapter legacyDataAdapter;
63      protected XmlObjectSerializerService xmlObjectSerializerService;
64  
65      protected XStream xstream;
66      protected ThreadLocal<SerializationState> serializationStates;
67      protected ThreadLocal<PropertySerializabilityEvaluator> evaluators;
68  
69      public SerializerServiceBase() {
70          serializationStates = new ThreadLocal<SerializationState>();
71          evaluators = new ThreadLocal<PropertySerializabilityEvaluator>();
72  
73          xstream = new XStream(new ProxyAndStateAwareJavaReflectionProvider());
74          xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
75          try {
76          	Class<?> objListProxyClass = Class.forName("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
77              xstream.addDefaultImplementation(ArrayList.class, objListProxyClass);
78              xstream.addDefaultImplementation(AutoPopulatingList.class, objListProxyClass);
79          } catch ( Exception ex ) {
80          	// Do nothing - this will blow if the OJB class does not exist, which it won't in some installs
81          }
82          xstream.registerConverter(new AutoPopulatingListConverter(xstream.getMapper()));
83          xstream.registerConverter(new DateTimeConverter());
84      }
85  
86      public class ProxyConverter extends ReflectionConverter {
87          public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
88              super(mapper, reflectionProvider);
89          }
90          @Override
91  		public boolean canConvert(Class clazz) {
92              return clazz.getName().contains("CGLIB") || clazz.getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
93          }
94  
95          @Override
96  		public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
97              if (obj.getClass().getName().equals("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl")) {
98                  List copiedList = new ArrayList();
99                  List proxiedList = (List) obj;
100                 for (Iterator iter = proxiedList.iterator(); iter.hasNext();) {
101                     copiedList.add(iter.next());
102                 }
103                 context.convertAnother( copiedList );
104             }
105             else {
106                 super.marshal(legacyDataAdapter.resolveProxy(obj), writer, context);
107             }
108         }
109 
110         @Override
111 		public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
112             return null;
113         }
114     }
115 
116     public class ProxyAndStateAwareJavaReflectionProvider extends PureJavaReflectionProvider {
117         @Override
118         public void visitSerializableFields(Object object, Visitor visitor) {
119             SerializationState state = serializationStates.get();
120             PropertySerializabilityEvaluator evaluator = evaluators.get();
121 
122             for (Iterator iterator = fieldDictionary.serializableFieldsFor(object.getClass()); iterator.hasNext();) {
123                 Field field = (Field) iterator.next();
124                 if (!fieldModifiersSupported(field)) {
125                     continue;
126                 }
127 
128                 if (ignoreField(field)) {
129                     continue;
130                 }
131 
132                 validateFieldAccess(field);
133 
134                 initializeField(object, field);
135 
136                 Object value = null;
137                 try {
138                     value = field.get(object);
139                 } catch (IllegalArgumentException e) {
140                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
141                 } catch (IllegalAccessException e) {
142                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
143                 }
144 
145                 if (evaluator.isPropertySerializable(state, object, field.getName(), value)) {
146                     if (value != null && legacyDataAdapter.isProxied(value)) {
147                         // resolve proxies after we determine that it's serializable
148                         value = legacyDataAdapter.resolveProxy(value);
149                     }
150                     PropertyType propertyType = evaluator.determinePropertyType(value);
151                     state.addSerializedProperty(field.getName(), propertyType);
152                     visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
153                     state.removeSerializedProperty();
154                 }
155             }
156         }
157 
158         protected boolean ignoreField(Field field) {
159         	// Ignore @Transient annotated fields when saving to XML
160         	Annotation transientAnnotation = field.getAnnotation(Transient.class);
161         	if ( transientAnnotation != null ) {
162         		return true;
163         	}
164             return false;
165         }
166 
167         protected void initializeField(Object object, Field field) {
168         }
169     }
170 
171     public class AutoPopulatingListConverter extends CollectionConverter {
172 
173     	public AutoPopulatingListConverter(Mapper mapper){
174     		super(mapper);
175     	}
176 
177         @Override
178     	public boolean canConvert(Class clazz) {
179     		return clazz.equals(AutoPopulatingList.class);
180         }
181 
182     }
183 
184 
185     protected XmlObjectSerializerService getXmlObjectSerializerService() {
186         return this.xmlObjectSerializerService;
187     }
188 
189     @Required
190     public void setXmlObjectSerializerService(XmlObjectSerializerService xmlObjectSerializerService) {
191         this.xmlObjectSerializerService = xmlObjectSerializerService;
192     }
193 
194     protected SerializationState createNewDocumentSerializationState(Document document) {
195         return new SerializationState();
196     }
197 
198     @Required
199 	public void setLegacyDataAdapter(LegacyDataAdapter legacyDataAdapter) {
200 		this.legacyDataAdapter = legacyDataAdapter;
201 	}
202 }
203