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