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