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 */
022package org.granite.toplink;
023
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027import java.lang.reflect.InvocationTargetException;
028import java.lang.reflect.ParameterizedType;
029import java.lang.reflect.Type;
030import java.util.ArrayList;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import javax.persistence.Embeddable;
037import javax.persistence.Entity;
038import javax.persistence.IdClass;
039import javax.persistence.MappedSuperclass;
040
041import oracle.toplink.essentials.indirection.IndirectContainer;
042import oracle.toplink.essentials.indirection.IndirectList;
043import oracle.toplink.essentials.indirection.IndirectMap;
044import oracle.toplink.essentials.indirection.IndirectSet;
045import oracle.toplink.essentials.indirection.ValueHolderInterface;
046
047import org.granite.collections.BasicMap;
048import org.granite.config.GraniteConfig;
049import org.granite.context.GraniteContext;
050import org.granite.logging.Logger;
051import org.granite.messaging.amf.io.convert.Converters;
052import org.granite.messaging.amf.io.util.ClassGetter;
053import org.granite.messaging.amf.io.util.MethodProperty;
054import org.granite.messaging.amf.io.util.Property;
055import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
056import org.granite.messaging.annotations.Include;
057import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
058import org.granite.messaging.persistence.ExternalizablePersistentList;
059import org.granite.messaging.persistence.ExternalizablePersistentMap;
060import org.granite.messaging.persistence.ExternalizablePersistentSet;
061import org.granite.util.TypeUtil;
062
063/**
064 * @author William DRAI
065 */
066public 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}