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