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