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