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 }