001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2011 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.hibernate4;
022
023 import java.io.ByteArrayInputStream;
024 import java.io.ByteArrayOutputStream;
025 import java.io.IOException;
026 import java.io.ObjectInput;
027 import java.io.ObjectInputStream;
028 import java.io.ObjectOutput;
029 import java.io.ObjectOutputStream;
030 import java.io.Serializable;
031 import java.lang.reflect.InvocationTargetException;
032 import java.lang.reflect.ParameterizedType;
033 import java.lang.reflect.Type;
034 import java.util.Comparator;
035 import java.util.List;
036 import java.util.Map;
037 import java.util.Set;
038 import java.util.SortedMap;
039 import java.util.SortedSet;
040 import java.util.concurrent.ConcurrentHashMap;
041
042 import javax.persistence.Embeddable;
043 import javax.persistence.Entity;
044 import javax.persistence.MappedSuperclass;
045
046 import org.granite.collections.BasicMap;
047 import org.granite.config.GraniteConfig;
048 import org.granite.context.GraniteContext;
049 import org.granite.logging.Logger;
050 import org.granite.messaging.amf.io.convert.Converters;
051 import org.granite.messaging.amf.io.util.ClassGetter;
052 import org.granite.messaging.amf.io.util.MethodProperty;
053 import org.granite.messaging.amf.io.util.Property;
054 import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
055 import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedProperty;
056 import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
057 import org.granite.messaging.persistence.ExternalizablePersistentBag;
058 import org.granite.messaging.persistence.ExternalizablePersistentList;
059 import org.granite.messaging.persistence.ExternalizablePersistentMap;
060 import org.granite.messaging.persistence.ExternalizablePersistentSet;
061 import org.granite.util.TypeUtil;
062 import org.granite.util.StringUtil;
063 import org.granite.util.XMap;
064 import org.hibernate.Hibernate;
065 import org.hibernate.annotations.Sort;
066 import org.hibernate.annotations.SortType;
067 import org.hibernate.collection.spi.PersistentCollection;
068 import org.hibernate.collection.internal.PersistentBag;
069 import org.hibernate.collection.internal.PersistentList;
070 import org.hibernate.collection.internal.PersistentMap;
071 import org.hibernate.collection.internal.PersistentSet;
072 import org.hibernate.collection.internal.PersistentSortedMap;
073 import org.hibernate.collection.internal.PersistentSortedSet;
074 import org.hibernate.proxy.HibernateProxy;
075 import org.hibernate.proxy.LazyInitializer;
076
077 /**
078 * @author Franck WOLFF
079 */
080 public class HibernateExternalizer extends DefaultExternalizer {
081
082 private static final Logger log = Logger.getLogger(HibernateExternalizer.class);
083
084 private final ConcurrentHashMap<String, ProxyFactory> proxyFactories = new ConcurrentHashMap<String, ProxyFactory>();
085
086 static enum SerializeMetadata {
087 YES,
088 NO,
089 LAZY
090 }
091
092 private SerializeMetadata serializeMetadata = SerializeMetadata.NO;
093
094
095 /**
096 * Configure this externalizer with the values supplied in granite-config.xml.
097 *
098 * <p>The only supported configuration option is 'hibernate-collection-metadata' with
099 * values in ['no' (default), 'yes' and 'lazy']. By default, collection metadata (key,
100 * role and snapshot) aren't serialized. If the value of the 'hibernate-collection-metadata'
101 * node is 'yes', metadata will be always serialized, while the 'lazy' value tells the
102 * externalizer to serialiaze metadata for uninitialized collections only.
103 *
104 * <p>Configuration example (granite-config.xml):
105 * <pre>
106 * <granite-config scan="true">
107 * <externalizers>
108 * <configuration>
109 * <hibernate-collection-metadata>lazy</hibernate-collection-metadata>
110 * </configuration>
111 * </externalizers>
112 * </granite-config>
113 * </pre>
114 *
115 * @param properties an XMap instance that contains the configuration node.
116 */
117 @Override
118 public void configure(XMap properties) {
119 super.configure(properties);
120
121 if (properties != null) {
122 String collectionmetadata = properties.get("hibernate-collection-metadata");
123 if (collectionmetadata != null) {
124 if ("no".equalsIgnoreCase(collectionmetadata))
125 serializeMetadata = SerializeMetadata.NO;
126 else if ("yes".equalsIgnoreCase(collectionmetadata))
127 serializeMetadata = SerializeMetadata.YES;
128 else if ("lazy".equalsIgnoreCase(collectionmetadata))
129 serializeMetadata = SerializeMetadata.LAZY;
130 else
131 throw new RuntimeException("Illegal value for the 'hibernate-collection-metadata' option: " + collectionmetadata);
132 }
133 }
134 }
135
136 @Override
137 public Object newInstance(String type, ObjectInput in)
138 throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
139
140 // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
141 // and we fall back to DefaultExternalizer behavior.
142 Class<?> clazz = TypeUtil.forName(type);
143 if (!isRegularEntity(clazz))
144 return super.newInstance(type, in);
145
146 // Read initialized flag.
147 boolean initialized = ((Boolean)in.readObject()).booleanValue();
148
149 // Read detachedState.
150 String detachedState = (String)in.readObject();
151
152 // New or initialized entity.
153 if (initialized)
154 return super.newInstance(type, in);
155
156 // Actual proxy instantiation is deferred in order to keep consistent order in
157 // stored objects list (see AMF3Deserializer).
158 return newProxyInstantiator(proxyFactories, detachedState);
159 }
160
161 protected Object newProxyInstantiator(ConcurrentHashMap<String, ProxyFactory> proxyFactories, String detachedState) {
162 return new HibernateProxyInstantiator(proxyFactories, detachedState);
163 }
164
165 @Override
166 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
167
168 // Skip unserialized fields for proxies (only read id).
169 if (o instanceof HibernateProxyInstantiator) {
170 log.debug("Reading Hibernate Proxy...");
171 ((HibernateProxyInstantiator)o).readId(in);
172 }
173 // @Embeddable or others...
174 else if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) {
175 log.debug("Delegating non regular entity reading to DefaultExternalizer...");
176 super.readExternal(o, in);
177 }
178 // Regular @Entity or @MappedSuperclass
179 else {
180 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
181
182 Converters converters = config.getConverters();
183 ClassGetter classGetter = config.getClassGetter();
184 Class<?> oClass = classGetter.getClass(o);
185 ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass);
186
187 List<Property> fields = findOrderedFields(oClass, false);
188 log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
189 for (Property field : fields) {
190 Object value = in.readObject();
191 if (!(field instanceof MethodProperty && field.isAnnotationPresent(ExternalizedProperty.class, true))) {
192
193 if (value instanceof AbstractExternalizablePersistentCollection)
194 value = newHibernateCollection((AbstractExternalizablePersistentCollection)value, field);
195 else if (!(value instanceof HibernateProxy)) {
196 Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes);
197 value = converters.convert(value, targetType);
198 }
199
200 field.setProperty(o, value, false);
201 }
202 }
203 }
204 }
205
206 protected PersistentCollection newHibernateCollection(AbstractExternalizablePersistentCollection value, Property field) {
207 final Type target = field.getType();
208 final boolean initialized = value.isInitialized();
209 final String metadata = value.getMetadata();
210 final boolean dirty = value.isDirty();
211 final boolean sorted = (
212 SortedSet.class.isAssignableFrom(TypeUtil.classOfType(target)) ||
213 SortedMap.class.isAssignableFrom(TypeUtil.classOfType(target))
214 );
215
216 Comparator<?> comparator = null;
217 if (sorted && field.isAnnotationPresent(Sort.class)) {
218 Sort sort = field.getAnnotation(Sort.class);
219 if (sort.type() == SortType.COMPARATOR) {
220 try {
221 comparator = TypeUtil.newInstance(sort.comparator(), Comparator.class);
222 } catch (Exception e) {
223 throw new RuntimeException("Could not create instance of Comparator: " + sort.comparator());
224 }
225 }
226 }
227
228 PersistentCollection coll = null;
229 if (value instanceof ExternalizablePersistentSet) {
230 if (initialized) {
231 Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target, comparator);
232 coll = (sorted ? new PersistentSortedSet(null, (SortedSet<?>)set) : new PersistentSet(null, set));
233 }
234 else
235 coll = (sorted ? new PersistentSortedSet() : new PersistentSet());
236 }
237 else if (value instanceof ExternalizablePersistentBag) {
238 if (initialized) {
239 List<?> bag = ((ExternalizablePersistentBag)value).getContentAsList(target);
240 coll = new PersistentBag(null, bag);
241 }
242 else
243 coll = new PersistentBag();
244 }
245 else if (value instanceof ExternalizablePersistentList) {
246 if (initialized) {
247 List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
248 coll = new PersistentList(null, list);
249 }
250 else
251 coll = new PersistentList();
252 }
253 else if (value instanceof ExternalizablePersistentMap) {
254 if (initialized) {
255 Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target, comparator);
256 coll = (sorted ? new PersistentSortedMap(null, (SortedMap<?, ?>)map) : new PersistentMap(null, map));
257 }
258 else
259 coll = (sorted ? new PersistentSortedMap() : new PersistentMap());
260 }
261 else
262 throw new RuntimeException("Illegal externalizable persitent class: " + value);
263
264 if (metadata != null && serializeMetadata != SerializeMetadata.NO && (serializeMetadata == SerializeMetadata.YES || !initialized)) {
265 String[] toks = metadata.split(":", 3);
266 if (toks.length != 3)
267 throw new RuntimeException("Invalid collection metadata: " + metadata);
268 Serializable key = deserializeSerializable(StringUtil.hexStringToBytes(toks[0]));
269 Serializable snapshot = deserializeSerializable(StringUtil.hexStringToBytes(toks[1]));
270 String role = toks[2];
271 coll.setSnapshot(key, role, snapshot);
272 }
273
274 if (initialized && dirty)
275 coll.dirty();
276
277 return coll;
278 }
279
280 @Override
281 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
282
283 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
284 Class<?> oClass = classGetter.getClass(o);
285
286 String detachedState = null;
287 if (o instanceof HibernateProxy) {
288 HibernateProxy proxy = (HibernateProxy)o;
289 detachedState = getProxyDetachedState(proxy);
290
291 // Only write initialized flag & detachedState & entity id if proxy is uninitialized.
292 if (proxy.getHibernateLazyInitializer().isUninitialized()) {
293 Serializable id = proxy.getHibernateLazyInitializer().getIdentifier();
294 log.debug("Writing uninitialized HibernateProxy %s with id %s", detachedState, id);
295
296 // Write initialized flag.
297 out.writeObject(Boolean.FALSE);
298 // Write detachedState.
299 out.writeObject(detachedState);
300 // Write entity id.
301 out.writeObject(id);
302 return;
303 }
304
305 // Proxy is initialized, get the underlying persistent object.
306 log.debug("Writing initialized HibernateProxy...");
307 o = proxy.getHibernateLazyInitializer().getImplementation();
308 }
309
310 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
311 log.debug("Delegating non regular entity writing to DefaultExternalizer...");
312 super.writeExternal(o, out);
313 }
314 else {
315 if (isRegularEntity(o.getClass())) {
316 // Write initialized flag.
317 out.writeObject(Boolean.TRUE);
318 // Write detachedState.
319 out.writeObject(null);
320 }
321
322 // Externalize entity fields.
323 List<Property> fields = findOrderedFields(oClass, false);
324 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
325 for (Property field : fields) {
326 Object value = field.getProperty(o);
327
328 // Persistent collections.
329 if (value instanceof PersistentCollection)
330 value = newExternalizableCollection((PersistentCollection)value);
331 // Transient maps.
332 else if (value instanceof Map<?, ?>)
333 value = BasicMap.newInstance((Map<?, ?>)value);
334
335 if (isValueIgnored(value))
336 out.writeObject(null);
337 else
338 out.writeObject(value);
339 }
340 }
341 }
342
343 protected AbstractExternalizablePersistentCollection newExternalizableCollection(PersistentCollection value) {
344 final boolean initialized = Hibernate.isInitialized(value);
345 final boolean dirty = value.isDirty();
346
347 AbstractExternalizablePersistentCollection coll = null;
348
349 if (value instanceof PersistentSet)
350 coll = new ExternalizablePersistentSet(initialized ? (Set<?>)value : null, initialized, dirty);
351 else if (value instanceof PersistentList)
352 coll = new ExternalizablePersistentList(initialized ? (List<?>)value : null, initialized, dirty);
353 else if (value instanceof PersistentBag)
354 coll = new ExternalizablePersistentBag(initialized ? (List<?>)value : null, initialized, dirty);
355 else if (value instanceof PersistentMap)
356 coll = new ExternalizablePersistentMap(initialized ? (Map<?, ?>)value : null, initialized, dirty);
357 else
358 throw new UnsupportedOperationException("Unsupported Hibernate collection type: " + value);
359
360 if (serializeMetadata != SerializeMetadata.NO && (serializeMetadata == SerializeMetadata.YES || !initialized) && value.getRole() != null) {
361 char[] hexKey = StringUtil.bytesToHexChars(serializeSerializable(value.getKey()));
362 char[] hexSnapshot = StringUtil.bytesToHexChars(serializeSerializable(value.getStoredSnapshot()));
363 String metadata = new StringBuilder(hexKey.length + 1 + hexSnapshot.length + 1 + value.getRole().length())
364 .append(hexKey).append(':')
365 .append(hexSnapshot).append(':')
366 .append(value.getRole())
367 .toString();
368 coll.setMetadata(metadata);
369 }
370
371 return coll;
372 }
373
374 @Override
375 public int accept(Class<?> clazz) {
376 return (
377 clazz.isAnnotationPresent(Entity.class) ||
378 clazz.isAnnotationPresent(MappedSuperclass.class) ||
379 clazz.isAnnotationPresent(Embeddable.class)
380 ) ? 1 : -1;
381 }
382
383 protected String getProxyDetachedState(HibernateProxy proxy) {
384 LazyInitializer initializer = proxy.getHibernateLazyInitializer();
385
386 StringBuilder sb = new StringBuilder();
387
388 sb.append(initializer.getClass().getName())
389 .append(':');
390 if (initializer.getPersistentClass() != null)
391 sb.append(initializer.getPersistentClass().getName());
392 sb.append(':');
393 if (initializer.getEntityName() != null)
394 sb.append(initializer.getEntityName());
395
396 return sb.toString();
397 }
398
399 protected boolean isRegularEntity(Class<?> clazz) {
400 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
401 }
402
403 protected boolean isEmbeddable(Class<?> clazz) {
404 return clazz.isAnnotationPresent(Embeddable.class);
405 }
406
407 protected byte[] serializeSerializable(Serializable o) {
408 if (o == null)
409 return BYTES_0;
410 try {
411 ByteArrayOutputStream baos = new ByteArrayOutputStream();
412 ObjectOutputStream oos = new ObjectOutputStream(baos);
413 oos.writeObject(o);
414 return baos.toByteArray();
415 } catch (Exception e) {
416 throw new RuntimeException("Could not serialize: " + o);
417 }
418 }
419
420 protected Serializable deserializeSerializable(byte[] data) {
421 if (data.length == 0)
422 return null;
423 try {
424 ByteArrayInputStream baos = new ByteArrayInputStream(data);
425 ObjectInputStream oos = new ObjectInputStream(baos);
426 return (Serializable)oos.readObject();
427 } catch (Exception e) {
428 throw new RuntimeException("Could not deserialize: " + data);
429 }
430 }
431 }