001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2013 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.hibernate.jmf;
022    
023    import java.io.IOException;
024    import java.io.Serializable;
025    import java.lang.reflect.Field;
026    import java.lang.reflect.InvocationTargetException;
027    import java.lang.reflect.Method;
028    import java.util.List;
029    import java.util.concurrent.ConcurrentHashMap;
030    import java.util.concurrent.ConcurrentMap;
031    
032    import javax.persistence.Entity;
033    import javax.persistence.MappedSuperclass;
034    
035    import org.granite.logging.Logger;
036    import org.granite.messaging.jmf.ExtendedObjectInput;
037    import org.granite.messaging.jmf.ExtendedObjectOutput;
038    import org.granite.messaging.jmf.codec.ExtendedObjectCodec;
039    import org.hibernate.proxy.AbstractSerializableProxy;
040    import org.hibernate.proxy.HibernateProxy;
041    import org.hibernate.proxy.LazyInitializer;
042    
043    /**
044     * @author Franck WOLFF
045     */
046    public class EntityCodec implements ExtendedObjectCodec {
047    
048            private static final Logger log = Logger.getLogger(EntityCodec.class);
049            
050            private final ConcurrentMap<Class<?>, SerializableProxyAdapter> serializableProxyAdapters = new ConcurrentHashMap<Class<?>, SerializableProxyAdapter>();
051                    
052            static class SerializableProxyAdapter {
053                    
054                    private final AbstractSerializableProxy serializableProxy;
055                    private final Field idField;
056                    private final Method readResolveMethod;
057                    
058                    public SerializableProxyAdapter(Object serializableProxy) throws NoSuchFieldException, NoSuchMethodException, SecurityException {
059                            this.serializableProxy = (AbstractSerializableProxy)serializableProxy;
060                            
061                            this.idField = AbstractSerializableProxy.class.getDeclaredField("id");
062                            this.idField.setAccessible(true);
063                            
064                            this.readResolveMethod = serializableProxy.getClass().getDeclaredMethod("readResolve");
065                            this.readResolveMethod.setAccessible(true);
066                    }
067                    
068                    public synchronized HibernateProxy getProxy(Serializable id) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
069                            idField.set(serializableProxy, id);
070                            return (HibernateProxy)readResolveMethod.invoke(serializableProxy);
071                    }
072            }
073            
074    
075            public boolean canEncode(ExtendedObjectOutput out, Object v) {
076                    Class<?> cls = getClass(out, v);
077                    return (cls.isAnnotationPresent(Entity.class) || cls.isAnnotationPresent(MappedSuperclass.class));
078            }
079    
080            public String getEncodedClassName(ExtendedObjectOutput out, Object v) {
081            return getClass(out, v).getName();
082            }
083    
084            public void encode(ExtendedObjectOutput out, Object v) throws IOException, IllegalAccessException {
085            String detachedState = null;
086            
087            if (v instanceof HibernateProxy) {
088                HibernateProxy proxy = (HibernateProxy)v;
089    
090                // Only write initialized flag, detachedState & id if v is an uninitialized proxy.
091                if (proxy.getHibernateLazyInitializer().isUninitialized()) {
092                    
093                    Class<?> persistentClass = proxy.getHibernateLazyInitializer().getPersistentClass();
094                    if (!serializableProxyAdapters.containsKey(persistentClass)) {
095                            try {
096                                    SerializableProxyAdapter proxyAdapter = new SerializableProxyAdapter(proxy.writeReplace());
097                                    serializableProxyAdapters.putIfAbsent(persistentClass, proxyAdapter);
098                            }
099                            catch (Exception e) {
100                                    throw new IOException("Could not create SerializableProxyAdapter for: " + proxy);
101                            }
102                    }
103                    
104                    Serializable id = proxy.getHibernateLazyInitializer().getIdentifier();
105                    log.debug("Writing uninitialized HibernateProxy %s with id %s", detachedState, id);
106                    
107                    out.writeBoolean(false);
108                    out.writeUTF(null);
109                    out.writeObject(id);
110                    return;
111                }
112    
113                // Proxy is initialized, get the underlying persistent object.
114                    log.debug("Writing initialized HibernateProxy...");
115                v = proxy.getHibernateLazyInitializer().getImplementation();
116            }
117    
118            // Write initialized flag & detachedState.
119            out.writeBoolean(true);
120            out.writeUTF(null);
121                    
122            // Write all fields in lexical order. 
123                    List<Field> fields = out.getReflection().findSerializableFields(v.getClass());
124                    for (Field field : fields)
125                            out.getAndWriteField(v, field);
126            }
127    
128            public boolean canDecode(ExtendedObjectInput in, String className) throws ClassNotFoundException {
129                    Class<?> cls = in.getReflection().loadClass(className);
130                    return (cls.isAnnotationPresent(Entity.class) || cls.isAnnotationPresent(MappedSuperclass.class));
131            }
132    
133            public String getDecodedClassName(ExtendedObjectInput in, String className) {
134                    return in.getAlias(className);
135            }
136    
137            public Object newInstance(ExtendedObjectInput in, String className)
138                    throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
139                    InvocationTargetException, SecurityException, NoSuchMethodException, IOException {
140                    
141                    Class<?> cls = in.getReflection().loadClass(className);
142                    
143            // Read initialized flag & detachedState.
144                    boolean initialized = in.readBoolean();
145            in.readUTF();
146                    
147                    if (initialized)
148                            return in.getReflection().newInstance(cls);
149                    
150            // Create an HibernateProxy.
151                    SerializableProxyAdapter proxyAdapter = serializableProxyAdapters.get(cls);
152                    if (proxyAdapter == null)
153                            throw new IOException("Could not find SerializableProxyAdapter for: " + cls);
154                    Serializable id = (Serializable)in.readObject();
155                    return proxyAdapter.getProxy(id);
156            }
157    
158            public void decode(ExtendedObjectInput in, Object v) throws IOException, ClassNotFoundException, IllegalAccessException {
159                    if (!(v instanceof HibernateProxy)) {
160                            List<Field> fields = in.getReflection().findSerializableFields(v.getClass());
161                            for (Field field : fields)
162                                    in.readAndSetField(v, field);
163                    }
164            }
165            
166            protected Class<?> getClass(ExtendedObjectOutput out, Object v) {
167            Class<?> cls = v.getClass();
168                    
169            if (v instanceof HibernateProxy) {
170                    LazyInitializer initializer = ((HibernateProxy)v).getHibernateLazyInitializer();
171                    
172                    String className = (
173                            initializer.isUninitialized() ?
174                            initializer.getEntityName() :
175                            initializer.getImplementation().getClass().getName()
176                    );
177    
178                    if (className != null && className.length() > 0) {
179                    try {
180                                            cls = out.getReflection().loadClass(className);
181                                    } catch (ClassNotFoundException e) {
182                                    cls = initializer.getPersistentClass();
183                                    }
184                }
185                    }
186                    
187            return cls;
188            }
189    }