View Javadoc

1   /*
2    * Copyright 2005-2007 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  
17  package org.kuali.rice.krad.service.impl;
18  
19  import com.thoughtworks.xstream.XStream;
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.io.HierarchicalStreamReader;
28  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
29  import com.thoughtworks.xstream.mapper.Mapper;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl;
33  import org.apache.ojb.broker.core.proxy.SetProxyDefaultImpl;
34  import org.kuali.rice.krad.service.KRADServiceLocator;
35  import org.kuali.rice.krad.service.PersistenceService;
36  import org.kuali.rice.krad.service.XmlObjectSerializerService;
37  
38  import java.lang.reflect.Field;
39  import java.util.ArrayList;
40  import java.util.HashSet;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Set;
44  
45  /**
46   * This class is the service implementation for the XmlObjectSerializer structure. This is the default implementation that gets
47   * delivered with Kuali. It utilizes the XStream open source libraries and framework.
48   * 
49   * 
50   */
51  public class XmlObjectSerializerServiceImpl implements XmlObjectSerializerService {
52  	private static final Log LOG = LogFactory.getLog(XmlObjectSerializerServiceImpl.class);
53  
54  	private PersistenceService persistenceService;
55  	
56  	private XStream xstream;
57  	
58  	public XmlObjectSerializerServiceImpl() {
59  		xstream = new XStream(new ProxyAwareJavaReflectionProvider());
60  		xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
61  
62          // register converters so that ListProxyDefaultImpl and SetProxyDefaultImpl are
63          // serialized as ArrayLists and HashSets
64          xstream.registerConverter(new ListProxyDefaultImplConverter(xstream.getMapper()));
65          xstream.registerConverter(new SetProxyDefaultImplConverter(xstream.getMapper()));
66  	}
67  	
68      /**
69       * @see org.kuali.rice.krad.service.XmlObjectSerializerService#toXml(java.lang.Object)
70       */
71      public String toXml(Object object) {
72      	if ( LOG.isDebugEnabled() ) {
73      		LOG.debug( "toXml(" + object + ") : \n" + xstream.toXML(object) );
74      	}
75          return xstream.toXML(object);
76      }
77  
78      /**
79       * @see org.kuali.rice.krad.service.XmlObjectSerializerService#fromXml(java.lang.String)
80       */
81      public Object fromXml(String xml) {
82      	if ( LOG.isDebugEnabled() ) {
83      		LOG.debug( "fromXml() : \n" + xml );
84      	}
85      	if ( xml != null ) {
86      		xml = xml.replaceAll( "--EnhancerByCGLIB--[0-9a-f]{0,8}", "" );
87      	}
88          return xstream.fromXML(xml);
89      }
90  
91      /**
92       * This custom converter only handles proxies for BusinessObjects.  List-type proxies are handled by configuring XStream to treat
93       * ListProxyDefaultImpl as ArrayLists (see constructor for this service). 
94       */
95      public class ProxyConverter extends ReflectionConverter {
96          public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
97              super(mapper, reflectionProvider);
98          }
99          
100         @Override
101         // since the ReflectionConverter supertype defines canConvert without using a parameterized Class type, we must declare
102         // the overridden version the same way
103         @SuppressWarnings("unchecked")
104         public boolean canConvert(Class clazz) {
105             return clazz.getName().contains("CGLIB");
106         }
107 
108         @Override
109         public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
110             super.marshal(getPersistenceService().resolveProxy(obj), writer, context);
111         }
112         
113         // 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. 
114     }
115     
116     public class ProxyAwareJavaReflectionProvider extends PureJavaReflectionProvider {
117 
118     	public ProxyAwareJavaReflectionProvider() {
119     		super();
120     	}
121         /**
122          * @see com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider#visitSerializableFields(java.lang.Object, com.thoughtworks.xstream.converters.reflection.ReflectionProvider.Visitor)
123          */
124         @Override
125         public void visitSerializableFields(Object object, Visitor visitor) {
126             for (Iterator iterator = fieldDictionary.fieldsFor(object.getClass()); iterator.hasNext();) {
127                 Field field = (Field) iterator.next();
128                 if (!fieldModifiersSupported(field)) {
129                     continue;
130                 }
131                 validateFieldAccess(field);
132                 Object value = null;
133                 try {
134                     value = field.get(object);
135                     if (value != null && getPersistenceService().isProxied(value)) {
136                         value = getPersistenceService().resolveProxy(value);
137                     }
138                 } catch (IllegalArgumentException e) {
139                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
140                 } catch (IllegalAccessException e) {
141                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
142                 }
143                 visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
144             }
145         }
146 
147     }
148 
149 	public PersistenceService getPersistenceService() {
150 		if ( persistenceService == null ) {
151 			persistenceService = KRADServiceLocator.getPersistenceService();
152 		}
153 		return persistenceService;
154 	}
155 
156     /**
157      * Custom {@link com.thoughtworks.xstream.converters.Converter} that moves elements from a
158      * {@link ListProxyDefaultImpl} into an {@link ArrayList} and marshals that instead.
159      */
160     private static class ListProxyDefaultImplConverter extends CollectionConverter {
161 
162         ListProxyDefaultImplConverter(Mapper mapper) {
163             super(mapper);
164         }
165 
166         @Override @SuppressWarnings("unchecked")
167         public boolean canConvert(Class type) {
168             return ListProxyDefaultImpl.class.equals(type);
169         }
170 
171         /**
172          * moves elements from a {@link ListProxyDefaultImpl} into an {@link ArrayList} and marshals that instead.
173          */
174         @Override @SuppressWarnings("unchecked")
175         public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
176 
177             // move data to an ArrayList
178             ArrayList altered = new ArrayList((List)source);
179             // and marshal that
180             super.marshal(altered, writer, context);
181         }
182 
183         /**
184          * NOTE: using this class to unmarshal a ListProxyDefaultImpl inside Rice is unexpected, since those should
185          * have been converted to ArrayLists during marshalling. However, in the interest of attempting to
186          * provide a full Converter implementation here, we'll attempt to unmarshal it to an ArrayList by throwing
187          * away all the funny fields that a ListProxyDefaultImpl contains (Helpfully, their names all start with "_")'
188          */
189         @Override @SuppressWarnings("unchecked")
190         public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
191 
192             ArrayList result = null;
193 
194             while (reader.hasMoreChildren()) {
195                 try {
196                     reader.moveDown();
197 
198                     if (reader.getNodeName().startsWith("_")) {
199                         // do nothing
200                     } else {
201                         if (result == null) { result = new ArrayList(); } // lazy init
202                         addCurrentElementToCollection(reader, context, result, result);
203                     }
204                 } finally {
205                     reader.moveUp();
206                 }
207             }
208 
209             return result;
210         }
211     }
212 
213 
214     /**
215      * Custom {@link com.thoughtworks.xstream.converters.Converter} that moves elements from a
216      * {@link SetProxyDefaultImpl} into a {@link HashSet} and marshals that instead.
217      */
218     private static class SetProxyDefaultImplConverter extends CollectionConverter {
219 
220         SetProxyDefaultImplConverter(Mapper mapper) {
221             super(mapper);
222         }
223 
224         @Override @SuppressWarnings("unchecked")
225         public boolean canConvert(Class type) {
226             return SetProxyDefaultImpl.class.equals(type);
227         }
228 
229         /**
230          * moves elements from a {@link SetProxyDefaultImpl} into a {@link HashSet} and marshals that instead.
231          */
232         @Override @SuppressWarnings("unchecked")
233         public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
234 
235             // move data to a HashSet
236             HashSet altered = new HashSet((Set)source);
237             // and marshal that
238             super.marshal(altered, writer, context);
239         }
240 
241         /**
242          * NOTE: using this class to unmarshal a SetProxyDefaultImpl inside Rice is unexpected, since those should
243          * have been converted to HashSets during marshalling. However, in the interest of attempting to
244          * provide a full Converter implementation here, we'll attempt to unmarshal it to an HashSet by throwing
245          * away all the funny fields that a SetProxyDefaultImpl contains (Helpfully, their names all start with "_")'
246          */
247         @Override @SuppressWarnings("unchecked")
248         public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
249 
250             HashSet result = null;
251 
252             while (reader.hasMoreChildren()) {
253                 try {
254                     reader.moveDown();
255 
256                     if (reader.getNodeName().startsWith("_")) {
257                         // do nothing
258                     } else {
259                         if (result == null) { result = new HashSet(); } // lazy init
260                         addCurrentElementToCollection(reader, context, result, result);
261                     }
262                 } finally {
263                     reader.moveUp();
264                 }
265             }
266 
267             return result;
268         }
269     }
270 
271 }