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.eclipselink;
023
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027import java.lang.reflect.Field;
028import java.lang.reflect.InvocationTargetException;
029import java.lang.reflect.ParameterizedType;
030import java.lang.reflect.Type;
031import java.util.ArrayList;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036
037import javax.persistence.Embeddable;
038import javax.persistence.Entity;
039import javax.persistence.IdClass;
040import javax.persistence.MappedSuperclass;
041
042import org.eclipse.persistence.indirection.IndirectContainer;
043import org.eclipse.persistence.indirection.IndirectList;
044import org.eclipse.persistence.indirection.IndirectMap;
045import org.eclipse.persistence.indirection.IndirectSet;
046import org.eclipse.persistence.indirection.ValueHolderInterface;
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 EclipseLinkExternalizer extends DefaultExternalizer {
067
068        private static final Logger log = Logger.getLogger(EclipseLinkExternalizer.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 EclipseLink pseudo-proxy should be null or IdClass (" + type + ")");
095        
096        return new EclipseLinkValueHolder();
097    }
098
099    @Override
100    public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
101        // Ignore EclipseLink proxies
102        if (o instanceof EclipseLinkValueHolder)
103                return;
104
105        // @Embeddable or others...
106        if (!isRegularEntity(o.getClass()) && !isEmbeddable(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("_persistence_" + 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    @Override
148        protected boolean isPropertyIgnored(Field field) {
149                return field.getName().equals("_persistence_fetchGroup");
150        }
151    
152    
153    protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) {
154        final boolean initialized = value.isInitialized();
155                final Object[] content = value.getContent();
156        
157                IndirectContainer coll = null;
158                if (value instanceof ExternalizablePersistentSet) {
159            if (initialized) {
160                if (content != null) {
161                                Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target);
162                    coll = new IndirectSet(set);
163                }
164            } 
165            else
166                coll = new IndirectSet();
167        }
168                else if (value instanceof ExternalizablePersistentList) {
169                if (initialized) {
170                    if (content != null) {
171                        List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
172                        coll = new IndirectList(list);
173                    }
174                }
175                else
176                    coll = new IndirectList();
177                }
178                else if (value instanceof ExternalizablePersistentMap) {
179                if (initialized) {
180                    if (content != null) {
181                        Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target);
182                        coll = new IndirectMap(map);
183                    }
184                }
185                else
186                    coll = new IndirectMap();
187                }
188                else {
189                        throw new RuntimeException("Illegal externalizable persitent class: " + value);
190                }
191        
192        return coll;
193    }
194
195    
196    @Override
197    public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
198
199        ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
200        Class<?> oClass = classGetter.getClass(o);
201
202        if (o instanceof EclipseLinkProxy) {            
203            EclipseLinkProxy proxy = (EclipseLinkProxy)o;
204
205            // Only write initialized flag, null detachedState & null id if proxy is uninitialized.
206                String description = proxy.getProxiedClass().getName();
207                log.debug("Writing uninitialized EclipseLink ValueHolder %s", description);
208                
209                // Write initialized flag.
210                out.writeObject(Boolean.FALSE);
211                // Write detachedState.
212            out.writeObject(null);
213                // Write id.
214            out.writeObject(null);
215
216            return;
217        }
218
219        if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
220                log.debug("Delegating non regular entity writing to DefaultExternalizer...");
221            super.writeExternal(o, out);
222        }
223        else {
224                if (isRegularEntity(o.getClass())) {
225                        // Write initialized flag.
226                        out.writeObject(Boolean.TRUE);
227                        // Write detachedState.
228                    out.writeObject(null);
229                }
230                   
231            // Externalize entity fields.
232            List<Property> fields = findOrderedFields(oClass);
233            List<String> lazyFieldNames = new ArrayList<String>();
234            
235            log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
236            for (Property field : fields) {
237                if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) {
238                    if (lazyFieldNames.contains(field.getName())) {
239                        EclipseLinkProxy proxy = new EclipseLinkProxy((Class<?>)field.getType());
240                        out.writeObject(proxy);
241                    }
242                    else {
243                        Object value = field.getProperty(o);
244        
245                        // Persistent collections & maps.
246                        if (value instanceof IndirectContainer)
247                                value = newExternalizableCollection((IndirectContainer)value);
248                        // Transient maps.
249                        else if (value instanceof Map<?, ?>)
250                                value = BasicMap.newInstance((Map<?, ?>)value);
251                        
252                        out.writeObject(value);
253                    }
254                }
255                else {
256                    ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o);
257                    if (vh != null && !vh.isInstantiated())
258                        lazyFieldNames.add(field.getName().substring("_persistence_".length(), field.getName().length()-3));
259                }
260            }
261        }
262    }
263    
264    protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) {
265        AbstractExternalizablePersistentCollection coll = null;
266        boolean initialized = value.isInstantiated();
267        
268        if (value instanceof IndirectSet) {
269            coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false);
270        }
271        else if (value instanceof IndirectList) {
272            coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false);
273        }
274        else if (value instanceof IndirectMap) {
275            Object[] content = null;
276            
277            if (initialized) {
278                content = new Object[((IndirectMap)value).size()];
279                int index = 0;
280                @SuppressWarnings("unchecked")
281                Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet();
282                for (Map.Entry<?, ?> entry : entries)
283                    content[index++] = new Object[]{entry.getKey(), entry.getValue()};
284            }
285            
286            coll = new ExternalizablePersistentMap(content, initialized, false);
287        }
288        else {
289            throw new UnsupportedOperationException("Unsupported EclipseLink collection type: " + value);
290        }
291        
292        return coll;
293    }
294
295    
296    @Override
297    public int accept(Class<?> clazz) {
298        return (
299            clazz.isAnnotationPresent(Entity.class) ||
300            clazz.isAnnotationPresent(MappedSuperclass.class) ||
301            clazz.isAnnotationPresent(Embeddable.class)
302        ) ? 1 : -1;
303    }
304
305    protected boolean isRegularEntity(Class<?> clazz) {
306        return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
307    }
308
309    protected boolean isEmbeddable(Class<?> clazz) {
310        return clazz.isAnnotationPresent(Embeddable.class);
311    }
312}