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.openjpa;
023    
024    import java.io.ByteArrayInputStream;
025    import java.io.ByteArrayOutputStream;
026    import java.io.IOException;
027    import java.io.ObjectInput;
028    import java.io.ObjectInputStream;
029    import java.io.ObjectOutput;
030    import java.io.ObjectOutputStream;
031    import java.lang.reflect.Field;
032    import java.lang.reflect.InvocationTargetException;
033    import java.lang.reflect.ParameterizedType;
034    import java.lang.reflect.Type;
035    import java.util.ArrayList;
036    import java.util.Arrays;
037    import java.util.BitSet;
038    import java.util.Collection;
039    import java.util.HashMap;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.Set;
043    
044    import javax.persistence.Embeddable;
045    import javax.persistence.Entity;
046    import javax.persistence.IdClass;
047    import javax.persistence.MappedSuperclass;
048    
049    import org.apache.openjpa.enhance.PersistenceCapable;
050    import org.apache.openjpa.kernel.OpenJPAStateManager;
051    import org.apache.openjpa.util.ProxyCollection;
052    import org.apache.openjpa.util.ProxyMap;
053    import org.granite.collections.BasicMap;
054    import org.granite.config.GraniteConfig;
055    import org.granite.context.GraniteContext;
056    import org.granite.logging.Logger;
057    import org.granite.messaging.amf.io.convert.Converters;
058    import org.granite.messaging.amf.io.util.ClassGetter;
059    import org.granite.messaging.amf.io.util.MethodProperty;
060    import org.granite.messaging.amf.io.util.Property;
061    import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
062    import org.granite.messaging.annotations.Include;
063    import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
064    import org.granite.messaging.persistence.ExternalizablePersistentList;
065    import org.granite.messaging.persistence.ExternalizablePersistentMap;
066    import org.granite.messaging.persistence.ExternalizablePersistentSet;
067    import org.granite.util.StringUtil;
068    import org.granite.util.TypeUtil;
069    
070    /**
071     * @author Franck WOLFF
072     */
073    public class OpenJpaExternalizer extends DefaultExternalizer {
074    
075            private static final Logger log = Logger.getLogger(OpenJpaExternalizer.class);
076    
077        @Override
078        public Object newInstance(String type, ObjectInput in)
079            throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
080    
081            // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
082            // and we fall back to DefaultExternalizer behavior.
083            Class<?> clazz = TypeUtil.forName(type);
084            if (!isRegularEntity(clazz))
085                return super.newInstance(type, in);
086            
087            // Read initialized flag.
088            boolean initialized = ((Boolean)in.readObject()).booleanValue();
089    
090            // Read detached state...
091            String detachedState = (String)in.readObject();
092            
093            // Pseudo-proxy (uninitialized entity).
094            if (!initialized) {
095                    Object id = in.readObject();
096                    if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass())))
097                            throw new RuntimeException("Id for OpenJPA pseudo-proxy should be null or IdClass (" + type + ")");
098                    return null;
099            }
100            
101            // New entity.
102            if (detachedState == null)
103                    return super.newInstance(type, in);
104    
105            // Existing entity.
106                    Object entity = clazz.newInstance();
107                    if (detachedState.length() > 0) {
108                    byte[] data = StringUtil.hexStringToBytes(detachedState);
109                            ((PersistenceCapable)entity).pcSetDetachedState(deserializeDetachedState(data));
110                    }
111                    return entity;
112        }
113    
114        @Override
115        public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
116    
117            if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) {
118                    log.debug("Delegating non regular entity reading to DefaultExternalizer...");
119                super.readExternal(o, in);
120            }
121            // Regular @Entity or @MappedSuperclass
122            else {
123                GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
124    
125                Converters converters = config.getConverters();
126                ClassGetter classGetter = config.getClassGetter();
127                Class<?> oClass = classGetter.getClass(o);
128                ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass);
129    
130                List<Property> fields = findOrderedFields(oClass);
131                log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
132                for (Property field : fields) {
133                    Object value = in.readObject();
134                    
135                    if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) {
136                            
137                            // (Un)Initialized collections/maps.
138                            if (value instanceof AbstractExternalizablePersistentCollection) {
139                                    // Uninitialized.
140                                    if (!((AbstractExternalizablePersistentCollection)value).isInitialized())
141                                            value = null;
142                                    // Initialized.
143                                    else {
144                                            if (value instanceof ExternalizablePersistentSet)
145                                                    value = ((ExternalizablePersistentSet)value).getContentAsSet(field.getType());
146                                            else if (value instanceof ExternalizablePersistentMap)
147                                                    value = ((ExternalizablePersistentMap)value).getContentAsMap(field.getType());
148                                            else
149                                                    value = ((ExternalizablePersistentList)value).getContentAsList(field.getType());
150                                    }
151                            }
152                            // Others...
153                        else {
154                            Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes);
155                                    value = converters.convert(value, targetType);
156                        }
157                        
158                            field.setProperty(o, value, false);
159                    }
160                }
161            }
162        }
163    
164        @Override
165        public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
166    
167            ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
168            Class<?> oClass = classGetter.getClass(o);
169    
170            if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
171                    log.debug("Delegating non regular entity writing to DefaultExternalizer...");
172                super.writeExternal(o, out);
173            }
174            else {
175                    PersistenceCapable pco = (PersistenceCapable)o;
176                    
177                    if (isRegularEntity(o.getClass())) {
178                            // Pseudo-proxy created for uninitialized entities (see below).
179                            if (Boolean.FALSE.equals(pco.pcGetDetachedState())) {
180                            // Write uninitialized flag.
181                            out.writeObject(Boolean.FALSE);
182                            // Write detached state.
183                                    out.writeObject(null);
184                                    // Write id.
185                                    out.writeObject(null);
186                                    return;
187                            }
188            
189                            // Write initialized flag.
190                            out.writeObject(Boolean.TRUE);
191            
192                            // Write detached state as a String, in the form of an hex representation
193                            // of the serialized detached state.
194                            byte[] detachedState = serializeDetachedState(pco);
195                            char[] hexDetachedState = StringUtil.bytesToHexChars(detachedState);
196                        out.writeObject(new String(hexDetachedState));
197                    }
198    
199                // Externalize entity fields.
200                List<Property> fields = findOrderedFields(oClass);
201                    Map<String, Boolean> loadedState = getLoadedState(pco, oClass);
202                log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
203                for (Property field : fields) {
204                    Object value = field.getProperty(o);
205                    
206                    // Uninitialized associations.
207                    if (value == null && loadedState.containsKey(field.getName()) && !loadedState.get(field.getName())) {
208                            Class<?> fieldClass = TypeUtil.classOfType(field.getType());
209                                    
210                            // Create a "pseudo-proxy" for uninitialized entities: detached state is set to
211                            // Boolean.FALSE (uninitialized flag).
212                            if (PersistenceCapable.class.isAssignableFrom(fieldClass)) {
213                                    try {
214                                            value = fieldClass.newInstance();
215                                    } catch (Exception e) {
216                                            throw new RuntimeException("Could not create OpenJPA pseudo-proxy for: " + field, e);
217                                    }
218                                    ((PersistenceCapable)value).pcSetDetachedState(Boolean.FALSE);
219                            }
220                            // Create pseudo-proxy for collections (set or list).
221                            else if (Collection.class.isAssignableFrom(fieldClass)) {
222                                    if (Set.class.isAssignableFrom(fieldClass))
223                                            value = new ExternalizablePersistentSet((Object[])null, false, false);
224                                    else
225                                            value = new ExternalizablePersistentList((Object[])null, false, false);
226                            }
227                            // Create pseudo-proxy for maps.
228                            else if (Map.class.isAssignableFrom(fieldClass)) {
229                                    value = new ExternalizablePersistentMap((Object[])null, false, false);
230                            }
231                    }
232                    
233                    // Initialized collections.
234                    else if (value instanceof ProxyCollection) {
235                            if (value instanceof Set<?>)
236                                    value = new ExternalizablePersistentSet(((ProxyCollection)value).toArray(), true, false);
237                            else
238                                    value = new ExternalizablePersistentList(((ProxyCollection)value).toArray(), true, false);
239                    }
240                    
241                    // Initialized maps.
242                    else if (value instanceof ProxyMap) {
243                            value = new ExternalizablePersistentMap((Object[])null, true, false);
244                            ((ExternalizablePersistentMap)value).setContentFromMap((ProxyMap)value);
245                    }
246                    
247                    // Transient maps.
248                    else if (value instanceof Map<?, ?>)
249                            value = BasicMap.newInstance((Map<?, ?>)value);
250                    
251                    out.writeObject(value);
252                }
253            }
254        }
255    
256        @Override
257        public int accept(Class<?> clazz) {
258            return (
259                clazz.isAnnotationPresent(Entity.class) ||
260                clazz.isAnnotationPresent(MappedSuperclass.class) ||
261                clazz.isAnnotationPresent(Embeddable.class)
262            ) ? 1 : -1;
263        }
264    
265        protected boolean isRegularEntity(Class<?> clazz) {
266            return PersistenceCapable.class.isAssignableFrom(clazz) && (
267                    clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class)
268            );
269        }
270    
271        protected boolean isEmbeddable(Class<?> clazz) {
272            return PersistenceCapable.class.isAssignableFrom(clazz) && clazz.isAnnotationPresent(Embeddable.class);
273        }
274        
275        // Very hacky!
276        static Map<String, Boolean> getLoadedState(PersistenceCapable pc, Class<?> clazz) {
277            try {
278                    BitSet loaded = null;
279                    if (pc.pcGetStateManager() instanceof OpenJPAStateManager) {
280                            OpenJPAStateManager sm = (OpenJPAStateManager)pc.pcGetStateManager();
281                            loaded = sm.getLoaded();
282                    }
283                    // State manager may be null for some entities...
284                    if (loaded == null) {
285                            Object ds = pc.pcGetDetachedState();
286                            if (ds != null && ds.getClass().isArray()) {
287                                    Object[] dsa = (Object[])ds;
288                                    if (dsa.length > 1 && dsa[1] instanceof BitSet)
289                                            loaded = (BitSet)dsa[1];
290                            }
291                    }
292                    
293                    List<String> fieldNames = new ArrayList<String>();
294                    for (Class<?> c = clazz; c != null && PersistenceCapable.class.isAssignableFrom(c); c = c.getSuperclass()) { 
295                            Field pcFieldNames = c.getDeclaredField("pcFieldNames");
296                            pcFieldNames.setAccessible(true);
297                            fieldNames.addAll(0, Arrays.asList((String[])pcFieldNames.get(null)));
298                    }
299                    
300                    Map<String, Boolean> loadedState = new HashMap<String, Boolean>();
301                    for (int i = 0; i < fieldNames.size(); i++)
302                            loadedState.put(fieldNames.get(i), (loaded != null && loaded.size() > i ? loaded.get(i) : true));
303                    return loadedState;
304            }
305            catch (Exception e) {
306                    throw new RuntimeException("Could not get loaded state for: " + pc);
307            }
308        }
309        
310        protected byte[] serializeDetachedState(PersistenceCapable pc) {
311            try {
312                    ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
313                    ObjectOutputStream oos = new ObjectOutputStream(baos);
314                    oos.writeObject(pc.pcGetDetachedState());
315                    return baos.toByteArray();
316            } catch (Exception e) {
317                    throw new RuntimeException("Could not serialize detached state for: " + pc);
318            }
319        }
320        
321        protected Object deserializeDetachedState(byte[] data) {
322            try {
323                    ByteArrayInputStream baos = new ByteArrayInputStream(data);
324                    ObjectInputStream oos = new ObjectInputStream(baos);
325                    return oos.readObject();
326            } catch (Exception e) {
327                    throw new RuntimeException("Could not deserialize detached state for: " + data);
328            }
329        }
330    }