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;
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.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.collection.spi.PersistentCollection;
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 if (o instanceof HibernateProxy) {
289 HibernateProxy proxy = (HibernateProxy)o;
290 detachedState = getProxyDetachedState(proxy);
291
292 // Only write initialized flag & detachedState & entity id if proxy is uninitialized.
293 if (proxy.getHibernateLazyInitializer().isUninitialized()) {
294 Serializable id = proxy.getHibernateLazyInitializer().getIdentifier();
295 log.debug("Writing uninitialized HibernateProxy %s with id %s", detachedState, id);
296
297 // Write initialized flag.
298 out.writeObject(Boolean.FALSE);
299 // Write detachedState.
300 out.writeObject(detachedState);
301 // Write entity id.
302 out.writeObject(id);
303 return;
304 }
305
306 // Proxy is initialized, get the underlying persistent object.
307 log.debug("Writing initialized HibernateProxy...");
308 o = proxy.getHibernateLazyInitializer().getImplementation();
309 }
310
311 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
312 log.debug("Delegating non regular entity writing to DefaultExternalizer...");
313 super.writeExternal(o, out);
314 }
315 else {
316 if (isRegularEntity(o.getClass())) {
317 // Write initialized flag.
318 out.writeObject(Boolean.TRUE);
319 // Write detachedState.
320 out.writeObject(null);
321 }
322
323 // Externalize entity fields.
324 List<Property> fields = findOrderedFields(oClass, false);
325 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
326 for (Property field : fields) {
327 Object value = field.getProperty(o);
328
329 // Persistent collections.
330 if (value instanceof PersistentCollection)
331 value = newExternalizableCollection((PersistentCollection)value);
332 // Transient maps.
333 else if (value instanceof Map<?, ?>)
334 value = BasicMap.newInstance((Map<?, ?>)value);
335
336 if (isValueIgnored(value))
337 out.writeObject(null);
338 else
339 out.writeObject(value);
340 }
341 }
342 }
343
344 protected AbstractExternalizablePersistentCollection newExternalizableCollection(PersistentCollection value) {
345 final boolean initialized = Hibernate.isInitialized(value);
346 final boolean dirty = value.isDirty();
347
348 AbstractExternalizablePersistentCollection coll = null;
349
350 if (value instanceof PersistentSet)
351 coll = new ExternalizablePersistentSet(initialized ? (Set<?>)value : null, initialized, dirty);
352 else if (value instanceof PersistentList)
353 coll = new ExternalizablePersistentList(initialized ? (List<?>)value : null, initialized, dirty);
354 else if (value instanceof PersistentBag)
355 coll = new ExternalizablePersistentBag(initialized ? (List<?>)value : null, initialized, dirty);
356 else if (value instanceof PersistentMap)
357 coll = new ExternalizablePersistentMap(initialized ? (Map<?, ?>)value : null, initialized, dirty);
358 else
359 throw new UnsupportedOperationException("Unsupported Hibernate collection type: " + value);
360
361 if (serializeMetadata != SerializeMetadata.NO && (serializeMetadata == SerializeMetadata.YES || !initialized) && value.getRole() != null) {
362 char[] hexKey = StringUtil.bytesToHexChars(serializeSerializable(value.getKey()));
363 char[] hexSnapshot = StringUtil.bytesToHexChars(serializeSerializable(value.getStoredSnapshot()));
364 String metadata = new StringBuilder(hexKey.length + 1 + hexSnapshot.length + 1 + value.getRole().length())
365 .append(hexKey).append(':')
366 .append(hexSnapshot).append(':')
367 .append(value.getRole())
368 .toString();
369 coll.setMetadata(metadata);
370 }
371
372 return coll;
373 }
374
375 @Override
376 public int accept(Class<?> clazz) {
377 return (
378 clazz.isAnnotationPresent(Entity.class) ||
379 clazz.isAnnotationPresent(MappedSuperclass.class) ||
380 clazz.isAnnotationPresent(Embeddable.class)
381 ) ? 1 : -1;
382 }
383
384 protected String getProxyDetachedState(HibernateProxy proxy) {
385 LazyInitializer initializer = proxy.getHibernateLazyInitializer();
386
387 StringBuilder sb = new StringBuilder();
388
389 sb.append(initializer.getClass().getName())
390 .append(':');
391 if (initializer.getPersistentClass() != null)
392 sb.append(initializer.getPersistentClass().getName());
393 sb.append(':');
394 if (initializer.getEntityName() != null)
395 sb.append(initializer.getEntityName());
396
397 return sb.toString();
398 }
399
400 protected boolean isRegularEntity(Class<?> clazz) {
401 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
402 }
403
404 protected boolean isEmbeddable(Class<?> clazz) {
405 return clazz.isAnnotationPresent(Embeddable.class);
406 }
407
408 protected byte[] serializeSerializable(Serializable o) {
409 if (o == null)
410 return BYTES_0;
411 try {
412 ByteArrayOutputStream baos = new ByteArrayOutputStream();
413 ObjectOutputStream oos = new ObjectOutputStream(baos);
414 oos.writeObject(o);
415 return baos.toByteArray();
416 } catch (Exception e) {
417 throw new RuntimeException("Could not serialize: " + o);
418 }
419 }
420
421 protected Serializable deserializeSerializable(byte[] data) {
422 if (data.length == 0)
423 return null;
424 try {
425 ByteArrayInputStream baos = new ByteArrayInputStream(data);
426 ObjectInputStream oos = new ObjectInputStream(baos);
427 return (Serializable)oos.readObject();
428 } catch (Exception e) {
429 throw new RuntimeException("Could not deserialize: " + data);
430 }
431 }
432 }