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.eclipselink;
023    
024    import java.io.IOException;
025    import java.io.ObjectInput;
026    import java.io.ObjectOutput;
027    import java.lang.reflect.Field;
028    import java.lang.reflect.InvocationTargetException;
029    import java.lang.reflect.ParameterizedType;
030    import java.lang.reflect.Type;
031    import java.util.ArrayList;
032    import java.util.HashMap;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Set;
036    
037    import javax.persistence.Embeddable;
038    import javax.persistence.Entity;
039    import javax.persistence.IdClass;
040    import javax.persistence.MappedSuperclass;
041    
042    import org.eclipse.persistence.indirection.IndirectContainer;
043    import org.eclipse.persistence.indirection.IndirectList;
044    import org.eclipse.persistence.indirection.IndirectMap;
045    import org.eclipse.persistence.indirection.IndirectSet;
046    import org.eclipse.persistence.indirection.ValueHolderInterface;
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 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    }