001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.toplink;
023    
024    import java.io.IOException;
025    import java.io.ObjectInput;
026    import java.io.ObjectOutput;
027    import java.lang.reflect.InvocationTargetException;
028    import java.lang.reflect.ParameterizedType;
029    import java.lang.reflect.Type;
030    import java.util.ArrayList;
031    import java.util.HashMap;
032    import java.util.List;
033    import java.util.Map;
034    import java.util.Set;
035    
036    import javax.persistence.Embeddable;
037    import javax.persistence.Entity;
038    import javax.persistence.IdClass;
039    import javax.persistence.MappedSuperclass;
040    
041    import oracle.toplink.essentials.indirection.IndirectContainer;
042    import oracle.toplink.essentials.indirection.IndirectList;
043    import oracle.toplink.essentials.indirection.IndirectMap;
044    import oracle.toplink.essentials.indirection.IndirectSet;
045    import oracle.toplink.essentials.indirection.ValueHolderInterface;
046    
047    import org.granite.collections.BasicMap;
048    import org.granite.config.GraniteConfig;
049    import org.granite.context.GraniteContext;
050    import org.granite.logging.Logger;
051    import org.granite.messaging.amf.io.convert.Converters;
052    import org.granite.messaging.amf.io.util.ClassGetter;
053    import org.granite.messaging.amf.io.util.MethodProperty;
054    import org.granite.messaging.amf.io.util.Property;
055    import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
056    import org.granite.messaging.annotations.Include;
057    import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
058    import org.granite.messaging.persistence.ExternalizablePersistentList;
059    import org.granite.messaging.persistence.ExternalizablePersistentMap;
060    import org.granite.messaging.persistence.ExternalizablePersistentSet;
061    import org.granite.util.TypeUtil;
062    
063    /**
064     * @author William DRAI
065     */
066    public class TopLinkExternalizer extends DefaultExternalizer {
067    
068            private static final Logger log = Logger.getLogger(TopLinkExternalizer.class);
069    
070        @Override
071        public Object newInstance(String type, ObjectInput in)
072            throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
073    
074            // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
075            // and we fall back to DefaultExternalizer behavior.
076            Class<?> clazz = TypeUtil.forName(type);
077            if (!isRegularEntity(clazz))
078                return super.newInstance(type, in);
079    
080            // Read initialized flag.
081            boolean initialized = ((Boolean)in.readObject()).booleanValue();
082            
083            // Read detached state.
084            @SuppressWarnings("unused")
085                    String detachedState = (String)in.readObject();
086            
087            // New or initialized entity.
088            if (initialized)
089                    return super.newInstance(type, in);
090            
091            // Pseudo-proxy (uninitialized entity).
092            Object id = in.readObject();
093            if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass())))
094                    throw new RuntimeException("Id for TopLink pseudo-proxy should be null or IdClass (" + type + ")");
095            
096            return new TopLinkValueHolder();
097        }
098    
099        @Override
100        public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
101            // Ignore TopLink proxies
102            if (o instanceof TopLinkValueHolder)
103                    return;
104            
105            // @Embeddable or others...
106            if (!isRegularEntity(o.getClass())) {
107                    log.debug("Delegating non regular entity reading to DefaultExternalizer...");
108                super.readExternal(o, in);
109            }
110            // Regural @Entity or @MappedSuperclass
111            else {
112                GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
113    
114                Converters converters = config.getConverters();
115                ClassGetter classGetter = config.getClassGetter();
116                Class<?> oClass = classGetter.getClass(o);
117                ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass);
118    
119                List<Property> fields = findOrderedFields(oClass);
120                log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
121                Map<String, Property> topLinkFields = new HashMap<String, Property>();
122                for (Property field : fields) {
123                    if (field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType())) {
124                        topLinkFields.put(field.getName(), field);
125                    }
126                    else {
127                        Object value = in.readObject();
128    
129                        if (value instanceof ValueHolderInterface) {
130                            topLinkFields.get("_toplink_" + field.getName() + "_vh").setProperty(o, value, false);
131                        }
132                        else if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) { 
133                            if (value instanceof AbstractExternalizablePersistentCollection)
134                                value = newIndirectCollection((AbstractExternalizablePersistentCollection)value, field.getType());
135                            else {
136                                    Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes);
137                                value = converters.convert(value, targetType);
138                            }
139                            
140                            field.setProperty(o, value, false);
141                        }
142                    }
143                }
144            }
145        }
146        
147        protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) {
148            final boolean initialized = value.isInitialized();
149                    final Object[] content = value.getContent();
150            
151                    IndirectContainer coll = null;
152                    if (value instanceof ExternalizablePersistentSet) {
153                if (initialized) {
154                    if (content != null) {
155                        Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target);
156                        coll = new IndirectSet(set);
157                    }
158                } 
159                else
160                    coll = new IndirectSet();
161            }
162                    else if (value instanceof ExternalizablePersistentList) {
163                    if (initialized) {
164                        if (content != null) {
165                        List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
166                            coll = new IndirectList(list);
167                        }
168                    }
169                    else
170                        coll = new IndirectList();
171                    }
172                    else if (value instanceof ExternalizablePersistentMap) {
173                    if (initialized) {
174                        if (content != null) {
175                        Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target);
176                            coll = new IndirectMap(map);
177                        }
178                    }
179                    else
180                        coll = new IndirectMap();
181                    }
182                    else {
183                            throw new RuntimeException("Illegal externalizable persitent class: " + value);
184                    }
185            
186            return coll;
187        }
188        
189    
190        @Override
191        public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
192    
193            ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
194            Class<?> oClass = classGetter.getClass(o);
195    
196            if (o instanceof TopLinkProxy) {                
197                TopLinkProxy proxy = (TopLinkProxy)o;
198    
199                // Only write initialized flag, null detachedState & null id if proxy is uninitialized.
200                    log.debug("Writing uninitialized TopLink ValueHolder %s", proxy.getProxiedClass().getName());
201                    
202                    // Write initialized flag.
203                    out.writeObject(Boolean.FALSE);
204                    // Write detachedState.
205                out.writeObject(null);
206                    // Write id.
207                out.writeObject(null);
208    
209                return;
210            }
211    
212            if (!isRegularEntity(o.getClass())) { // @Embeddable or others...
213                    log.debug("Delegating non regular entity writing to DefaultExternalizer...");
214                super.writeExternal(o, out);
215            }
216            else {
217                    // Write initialized flag.
218                    out.writeObject(Boolean.TRUE);
219                    // Write detachedState.
220                out.writeObject(null);
221    
222                // Externalize entity fields.
223                List<Property> fields = findOrderedFields(oClass);
224                List<String> lazyFieldNames = new ArrayList<String>();
225                
226                log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
227                for (Property field : fields) {
228                    if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) {
229                        if (lazyFieldNames.contains(field.getName())) {
230                            TopLinkProxy proxy = new TopLinkProxy((Class<?>)field.getType());
231                            out.writeObject(proxy);
232                        }
233                        else {
234                            Object value = field.getProperty(o);
235            
236                            // Persistent collections & maps.
237                            if (value instanceof IndirectContainer)
238                                    value = newExternalizableCollection((IndirectContainer)value);
239                            // Transient maps.
240                            else if (value instanceof Map<?, ?>)
241                                    value = BasicMap.newInstance((Map<?, ?>)value);
242                            
243                            out.writeObject(value);
244                        }
245                    }
246                    else {
247                        ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o);
248                        if (!vh.isInstantiated())
249                            lazyFieldNames.add(field.getName().substring("_toplink_".length(), field.getName().length()-3));
250                    }
251                }
252            }
253        }
254        
255        protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) {
256            AbstractExternalizablePersistentCollection coll = null;
257            boolean initialized = value.isInstantiated();
258            
259            if (value instanceof IndirectSet) {
260                coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false);
261            }
262            else if (value instanceof IndirectList) {
263                coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false);
264            }
265            else if (value instanceof IndirectMap) {
266                Object[] content = null;
267                
268                if (initialized) {
269                    content = new Object[((IndirectMap)value).size()];
270                    int index = 0;
271                    @SuppressWarnings("unchecked")
272                    Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet();
273                    for (Map.Entry<?, ?> entry : entries)
274                        content[index++] = new Object[]{entry.getKey(), entry.getValue()};
275                }
276                
277                coll = new ExternalizablePersistentMap(content, initialized, false);
278            }
279            else {
280                throw new UnsupportedOperationException("Unsupported TopLink collection type: " + value);
281            }
282            
283            return coll;
284        }
285    
286        
287        @Override
288        public int accept(Class<?> clazz) {
289            return (
290                clazz.isAnnotationPresent(Entity.class) ||
291                clazz.isAnnotationPresent(MappedSuperclass.class) ||
292                clazz.isAnnotationPresent(Embeddable.class)
293            ) ? 1 : -1;
294        }
295    
296        protected boolean isRegularEntity(Class<?> clazz) {
297            return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
298        }
299    }