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 */ 022package org.granite.hibernate4; 023 024import java.io.ByteArrayInputStream; 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.ObjectInput; 028import java.io.ObjectInputStream; 029import java.io.ObjectOutput; 030import java.io.ObjectOutputStream; 031import java.io.Serializable; 032import java.lang.reflect.InvocationTargetException; 033import java.lang.reflect.ParameterizedType; 034import java.lang.reflect.Type; 035import java.util.Comparator; 036import java.util.List; 037import java.util.Map; 038import java.util.Set; 039import java.util.SortedMap; 040import java.util.SortedSet; 041import java.util.concurrent.ConcurrentHashMap; 042 043import javax.persistence.Embeddable; 044import javax.persistence.Entity; 045import javax.persistence.MappedSuperclass; 046 047import org.granite.collections.BasicMap; 048import org.granite.config.GraniteConfig; 049import org.granite.context.GraniteContext; 050import org.granite.logging.Logger; 051import org.granite.messaging.amf.io.convert.Converters; 052import org.granite.messaging.amf.io.util.ClassGetter; 053import org.granite.messaging.amf.io.util.MethodProperty; 054import org.granite.messaging.amf.io.util.Property; 055import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer; 056import org.granite.messaging.annotations.Include; 057import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection; 058import org.granite.messaging.persistence.ExternalizablePersistentBag; 059import org.granite.messaging.persistence.ExternalizablePersistentList; 060import org.granite.messaging.persistence.ExternalizablePersistentMap; 061import org.granite.messaging.persistence.ExternalizablePersistentSet; 062import org.granite.util.StringUtil; 063import org.granite.util.TypeUtil; 064import org.granite.util.XMap; 065import org.hibernate.Hibernate; 066import org.hibernate.annotations.Sort; 067import org.hibernate.annotations.SortType; 068import org.hibernate.collection.internal.PersistentBag; 069import org.hibernate.collection.internal.PersistentList; 070import org.hibernate.collection.internal.PersistentMap; 071import org.hibernate.collection.internal.PersistentSet; 072import org.hibernate.collection.internal.PersistentSortedMap; 073import org.hibernate.collection.internal.PersistentSortedSet; 074import org.hibernate.collection.spi.PersistentCollection; 075import org.hibernate.proxy.HibernateProxy; 076import org.hibernate.proxy.LazyInitializer; 077 078/** 079 * @author Franck WOLFF 080 */ 081public 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}