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