001    /*
002     * Copyright 2005-2007 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    
017    package org.kuali.rice.kns.service.impl;
018    
019    import com.thoughtworks.xstream.XStream;
020    import com.thoughtworks.xstream.converters.MarshallingContext;
021    import com.thoughtworks.xstream.converters.reflection.ObjectAccessException;
022    import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
023    import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
024    import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
025    import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
026    import com.thoughtworks.xstream.mapper.Mapper;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.ojb.broker.core.proxy.ListProxyDefaultImpl;
030    import org.apache.ojb.broker.core.proxy.ProxyHelper;
031    import org.kuali.rice.kns.service.KNSServiceLocator;
032    import org.kuali.rice.kns.service.PersistenceService;
033    import org.kuali.rice.kns.service.XmlObjectSerializerService;
034    
035    import javax.xml.transform.*;
036    import javax.xml.transform.dom.DOMSource;
037    import javax.xml.transform.stream.StreamResult;
038    import java.io.StringWriter;
039    import java.lang.reflect.Field;
040    import java.util.ArrayList;
041    import java.util.Iterator;
042    
043    
044    /**
045     * This class is the service implementation for the XmlObjectSerializer structure. This is the default implementation that gets
046     * delivered with Kuali. It utilizes the XStream open source libraries and framework.
047     * 
048     * 
049     */
050    public class XmlObjectSerializerServiceImpl implements XmlObjectSerializerService {
051            private static final Log LOG = LogFactory.getLog(XmlObjectSerializerServiceImpl.class);
052            
053            private PersistenceService persistenceService;
054            
055            private XStream xstream;
056            
057            public XmlObjectSerializerServiceImpl() {
058                    xstream = new XStream(new ProxyAwareJavaReflectionProvider());
059                    xstream.registerConverter(new ProxyConverter(xstream.getMapper(), xstream.getReflectionProvider() ));
060                    xstream.addDefaultImplementation(ArrayList.class, ListProxyDefaultImpl.class);
061            }
062            
063        /**
064         * @see org.kuali.rice.kns.service.XmlObjectSerializer#toXml(java.lang.Object)
065         */
066        public String toXml(Object object) {
067            if ( LOG.isDebugEnabled() ) {
068                    LOG.debug( "toXml(" + object + ") : \n" + xstream.toXML(object) );
069            }
070            return xstream.toXML(object);
071        }
072    
073        /**
074         * @see org.kuali.rice.kns.service.XmlObjectSerializer#fromXml(java.lang.String)
075         */
076        public Object fromXml(String xml) {
077            if ( LOG.isDebugEnabled() ) {
078                    LOG.debug( "fromXml() : \n" + xml );
079            }
080            if ( xml != null ) {
081                    xml = xml.replaceAll( "--EnhancerByCGLIB--[0-9a-f]{0,8}", "" );
082            }
083            return xstream.fromXML(xml);
084        }
085    
086        public String writeNode(org.w3c.dom.Node node, boolean indent) throws TransformerException {
087            Source source = new DOMSource(node);
088            StringWriter writer = new StringWriter();
089            Result result = new StreamResult(writer);
090            Transformer transformer = TransformerFactory.newInstance().newTransformer();
091            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
092            if (indent) {
093                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
094            }
095            transformer.transform(source, result);
096            return writer.toString();
097        }
098    
099    
100        /**
101         * This custom converter only handles proxies for BusinessObjects.  List-type proxies are handled by configuring XStream to treat
102         * ListProxyDefaultImpl as ArrayLists (see constructor for this service). 
103         */
104        public class ProxyConverter extends ReflectionConverter {
105            public ProxyConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
106                super(mapper, reflectionProvider);
107            }
108            public boolean canConvert(Class clazz) {
109                return clazz.getName().contains("CGLIB");
110            }
111    
112            public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {
113                super.marshal(getPersistenceService().resolveProxy(obj), writer, context);
114            }
115            
116            // 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. 
117        }
118        
119        public class ProxyAwareJavaReflectionProvider extends PureJavaReflectionProvider {
120    
121            public ProxyAwareJavaReflectionProvider() {
122                    super();
123            }
124            /**
125             * @see com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider#visitSerializableFields(java.lang.Object, com.thoughtworks.xstream.converters.reflection.ReflectionProvider.Visitor)
126             */
127            @Override
128            public void visitSerializableFields(Object object, Visitor visitor) {
129                for (Iterator iterator = fieldDictionary.serializableFieldsFor(object.getClass()); iterator.hasNext();) {
130                    Field field = (Field) iterator.next();
131                    if (!fieldModifiersSupported(field)) {
132                        continue;
133                    }
134                    validateFieldAccess(field);
135                    Object value = null;
136                    try {
137                        value = field.get(object);
138                        if (value != null && ProxyHelper.isProxy(value)) {
139                            value = getPersistenceService().resolveProxy(value);
140                        }
141                    } catch (IllegalArgumentException e) {
142                        throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
143                    } catch (IllegalAccessException e) {
144                        throw new ObjectAccessException("Could not get field " + field.getClass() + "." + field.getName(), e);
145                    }
146                    visitor.visit(field.getName(), field.getType(), field.getDeclaringClass(), value);
147                }
148            }
149            
150        }
151    
152            public PersistenceService getPersistenceService() {
153                    if ( persistenceService == null ) {
154                            persistenceService = KNSServiceLocator.getPersistenceService();
155                    }
156                    return persistenceService;
157            }
158        
159    }