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