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  
23  import javax.persistence.Transient;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.kuali.rice.krad.service.LegacyDataAdapter;
28  import org.kuali.rice.krad.service.XmlObjectSerializerService;
29  import org.kuali.rice.krad.service.util.DateTimeConverter;
30  import org.springframework.beans.factory.annotation.Required;
31  
32  import com.thoughtworks.xstream.XStream;
33  import com.thoughtworks.xstream.converters.MarshallingContext;
34  import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
35  import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
36  import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
37  import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
38  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
39  import com.thoughtworks.xstream.mapper.Mapper;
40  
41  
42  /**
43   * Service implementation for the XmlObjectSerializer structure. This is the default implementation that gets
44   * delivered with Kuali. It utilizes the XStream open source libraries and framework.
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  public class XmlObjectSerializerServiceImpl implements XmlObjectSerializerService {
49  	private static final Log LOG = LogFactory.getLog(XmlObjectSerializerServiceImpl.class);
50  
51  	protected LegacyDataAdapter lda;
52  
53  	protected XStream xstream;
54  
55  	public XmlObjectSerializerServiceImpl() {
56  		xstream = new XStream(new ProxyAwareJavaReflectionProvider());
57  
58          // See http://xstream.codehaus.org/faq.html#Serialization_CGLIB
59          // To use a newer version of XStream we may need to do something like this:
60  //        xstream = new XStream() {
61  //
62  //            @Override
63  //            public ReflectionProvider getReflectionProvider() {
64  //                return new ProxyAwareJavaReflectionProvider();
65  //            }
66  //
67  //            protected MapperWrapper wrapMapper(MapperWrapper next) {
68  //                return new CGLIBMapper(next);
69  //            }
70  //        };
71  //        xstream.registerConverter(new CGLIBEnhancedConverter(xstream.getMapper(), xstream.getReflectionProvider()));
72  
73  		xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
74          try {
75          	Class<?> objListProxyClass = Class.forName("org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl");
76              xstream.addDefaultImplementation(ArrayList.class, objListProxyClass);
77          } catch ( Exception ex ) {
78          	// Do nothing - this will blow if the OJB class does not exist, which it won't in some installs
79          }
80          xstream.registerConverter(new DateTimeConverter());
81  	}
82  
83      @Required
84      public void setLegacyDataAdapter(LegacyDataAdapter lda) {
85          this.lda = lda;
86      }
87  
88      @Override
89  	public String toXml(Object object) {
90      	if ( LOG.isDebugEnabled() ) {
91      		LOG.debug( "toXml(" + object + ") : \n" + xstream.toXML(object) );
92      	}
93          return xstream.toXML(object);
94      }
95  
96      @Override
97  	public Object fromXml(String xml) {
98      	if ( LOG.isDebugEnabled() ) {
99      		LOG.debug( "fromXml() : \n" + xml );
100     	}
101     	if ( xml != null ) {
102     		xml = xml.replaceAll( "--EnhancerByCGLIB--[0-9a-f]{0,8}", "" );
103     	}
104         return xstream.fromXML(xml);
105     }
106 
107     /**
108      * This custom converter only handles proxies for BusinessObjects.  List-type proxies are handled by configuring XStream to treat
109      * ListProxyDefaultImpl as ArrayLists (see constructor for this service).
110      */
111     public class ProxyConverter extends ReflectionConverter {
112         public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
113             super(mapper, reflectionProvider);
114         }
115 
116         @Override
117         // since the ReflectionConverter supertype defines canConvert without using a parameterized Class type, we must declare
118         // the overridden version the same way
119         @SuppressWarnings("unchecked")
120         public boolean canConvert(Class clazz) {
121             return clazz.getName().contains("CGLIB");
122         }
123 
124         @Override
125         public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
126             super.marshal(lda.resolveProxy(obj), writer, context);
127         }
128 
129         // 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.
130     }
131 
132     public class ProxyAwareJavaReflectionProvider extends PureJavaReflectionProvider {
133 
134     	public ProxyAwareJavaReflectionProvider() {
135     		super();
136     	}
137         /**
138          * @see com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider#visitSerializableFields(java.lang.Object, com.thoughtworks.xstream.converters.reflection.ReflectionProvider.Visitor)
139          */
140         @Override
141         public void visitSerializableFields(Object object, Visitor visitor) {
142             for (Iterator iterator = fieldDictionary.serializableFieldsFor(object.getClass()); iterator.hasNext();) {
143                 Field field = (Field) iterator.next();
144                 if (!fieldModifiersSupported(field)) {
145                     continue;
146                 }
147                 validateFieldAccess(field);
148                 if (ignoreField(field)) {
149                     continue;
150                 }
151                 Object value = null;
152                 try {
153                     value = field.get(object);
154                     if (value != null && lda.isProxied(value)) {
155                         value = lda.resolveProxy(value);
156                     }
157                 } catch (IllegalArgumentException e) {
158                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
159                 } catch (IllegalAccessException e) {
160                     throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
161                 }
162                 visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
163             }
164         }
165         protected boolean ignoreField(Field field) {
166         	// Ignore @Transient annotated fields when saving to XML
167         	Annotation transientAnnotation = field.getAnnotation(Transient.class);
168         	if ( transientAnnotation != null ) {
169         		return true;
170         	}
171             return false;
172         }
173 
174     }
175 
176 //	public PersistenceService getPersistenceService() {
177 //		if ( persistenceService == null ) {
178 //			persistenceService = KRADServiceLocator.getPersistenceService();
179 //		}
180 //		return persistenceService;
181 //	}
182 
183 }