Skip to content

Commit

Permalink
Merge pull request #96 from bedag/master
Browse files Browse the repository at this point in the history
improve SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS feature
  • Loading branch information
cowtowncoder authored Aug 23, 2016
2 parents 256eba4 + a331270 commit c6625ba
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@ public enum Feature {
USE_TRANSIENT_ANNOTATION(true),

/**
* If FORCE_LAZY_LOADING is false lazy-loaded object should be serialized as map IdentifierName=>IdentifierValue
* instead of null (true); or serialized as nulls (false)
* If FORCE_LAZY_LOADING is false, this feature serializes uninitialized lazy loading proxies as
* <code>{"identifierName":"identifierValue"}</code> rather than <code>null</code>.
* <p>
* Default value is false.
* Default value is false.
* <p>
* Note that the name of the identifier property can only be determined if
* <ul>
* <li>the {@link Mapping} is provided to the Hibernate4Module, or </li>
* <li>the persistence context that loaded the proxy has not yet been closed, or</li>
* <li>the id property is mapped with property access (for instance because the {@code @Id}
* annotation is applied to a method rather than a field)</li>
* </ul>
* Otherwise, the entity name will be used instead.
*/
SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS(false),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.datatype.hibernate4;

import java.beans.Introspector;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

import com.fasterxml.jackson.core.*;
Expand All @@ -18,6 +21,7 @@
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.proxy.pojo.BasicLazyInitializer;

/**
* Serializer to use for values proxied using {@link org.hibernate.proxy.HibernateProxy}.
Expand Down Expand Up @@ -174,15 +178,18 @@ protected Object findProxied(HibernateProxy proxy)
LazyInitializer init = proxy.getHibernateLazyInitializer();
if (!_forceLazyLoading && init.isUninitialized()) {
if (_serializeIdentifier) {
final String idName;
String idName;
if (_mapping != null) {
idName = _mapping.getIdentifierPropertyName(init.getEntityName());
} else {
final SessionImplementor session = init.getSession();
if (session != null) {
idName = session.getFactory().getIdentifierPropertyName(init.getEntityName());
} else {
idName = init.getEntityName();
idName = ProxyReader.getIdentifierPropertyName(init);
if (idName == null) {
idName = init.getEntityName();
}
}
}
final Object idValue = init.getIdentifier();
Expand All @@ -194,4 +201,45 @@ protected Object findProxied(HibernateProxy proxy)
}
return init.getImplementation();
}

/**
* Inspects a Hibernate proxy to try and determine the name of the identifier property
* (Hibernate proxies know the getter of the identifier property because it receives special
* treatment in the invocation handler). Alas, the field storing the method reference is
* private and has no getter, so we must resort to ugly reflection hacks to read its value ...
*/
protected static class ProxyReader {

// static final so the JVM can inline the lookup
private static final Field getIdentifierMethodField;

static {
try {
getIdentifierMethodField = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
getIdentifierMethodField.setAccessible(true);
} catch (Exception e) {
// should never happen: the field exists in all versions of hibernate 4 and 5
throw new RuntimeException(e);
}
}

/**
* @return the name of the identifier property, or null if the name could not be determined
*/
static String getIdentifierPropertyName(LazyInitializer init) {
try {
Method idGetter = (Method) getIdentifierMethodField.get(init);
if (idGetter == null) {
return null;
}
String name = idGetter.getName();
if (name.startsWith("get")) {
name = Introspector.decapitalize(name.substring(3));
}
return name;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module.Feature;
import com.fasterxml.jackson.datatype.hibernate4.data.Customer;
import com.fasterxml.jackson.datatype.hibernate4.data.Payment;

Expand Down Expand Up @@ -54,4 +56,25 @@ public void testGetCustomerJson() throws Exception
emf.close();
}
}

@Test
public void testSerializeIdentifierFeature() throws JsonProcessingException {
Hibernate4Module module = new Hibernate4Module();
module.enable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
ObjectMapper objectMapper = new ObjectMapper().registerModule(module);

EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
try {
EntityManager em = emf.createEntityManager();
Customer customerRef = em.getReference(Customer.class, 103);
em.close();
assertFalse(Hibernate.isInitialized(customerRef));

String json = objectMapper.writeValueAsString(customerRef);
assertFalse(Hibernate.isInitialized(customerRef));
assertEquals("{\"customerNumber\":103}", json);
} finally {
emf.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,19 @@ public enum Feature {
USE_TRANSIENT_ANNOTATION(true),

/**
* If FORCE_LAZY_LOADING is false lazy-loaded object should be serialized as map IdentifierName=>IdentifierValue
* instead of null (true); or serialized as nulls (false)
* If FORCE_LAZY_LOADING is false, this feature serializes uninitialized lazy loading proxies as
* <code>{"identifierName":"identifierValue"}</code> rather than <code>null</code>.
* <p>
* Default value is false.
* Default value is false.
* <p>
* Note that the name of the identifier property can only be determined if
* <ul>
* <li>the {@link Mapping} is provided to the Hibernate5Module, or </li>
* <li>the persistence context that loaded the proxy has not yet been closed, or</li>
* <li>the id property is mapped with property access (for instance because the {@code @Id}
* annotation is applied to a method rather than a field)</li>
* </ul>
* Otherwise, the entity name will be used instead.
*/
SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS(false),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.datatype.hibernate5;

import java.beans.Introspector;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

import com.fasterxml.jackson.core.JsonGenerator;
Expand All @@ -18,6 +21,7 @@
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.proxy.pojo.BasicLazyInitializer;

/**
* Serializer to use for values proxied using {@link org.hibernate.proxy.HibernateProxy}.
Expand Down Expand Up @@ -174,15 +178,18 @@ protected Object findProxied(HibernateProxy proxy)
LazyInitializer init = proxy.getHibernateLazyInitializer();
if (!_forceLazyLoading && init.isUninitialized()) {
if (_serializeIdentifier) {
final String idName;
String idName;
if (_mapping != null) {
idName = _mapping.getIdentifierPropertyName(init.getEntityName());
} else {
final SessionImplementor session = init.getSession();
if (session != null) {
idName = session.getFactory().getIdentifierPropertyName(init.getEntityName());
} else {
idName = init.getEntityName();
idName = ProxyReader.getIdentifierPropertyName(init);
if (idName == null) {
idName = init.getEntityName();
}
}
}
final Object idValue = init.getIdentifier();
Expand All @@ -194,4 +201,45 @@ protected Object findProxied(HibernateProxy proxy)
}
return init.getImplementation();
}

/**
* Inspects a Hibernate proxy to try and determine the name of the identifier property
* (Hibernate proxies know the getter of the identifier property because it receives special
* treatment in the invocation handler). Alas, the field storing the method reference is
* private and has no getter, so we must resort to ugly reflection hacks to read its value ...
*/
protected static class ProxyReader {

// static final so the JVM can inline the lookup
private static final Field getIdentifierMethodField;

static {
try {
getIdentifierMethodField = BasicLazyInitializer.class.getDeclaredField("getIdentifierMethod");
getIdentifierMethodField.setAccessible(true);
} catch (Exception e) {
// should never happen: the field exists in all versions of hibernate 4 and 5
throw new RuntimeException(e);
}
}

/**
* @return the name of the identifier property, or null if the name could not be determined
*/
static String getIdentifierPropertyName(LazyInitializer init) {
try {
Method idGetter = (Method) getIdentifierMethodField.get(init);
if (idGetter == null) {
return null;
}
String name = idGetter.getName();
if (name.startsWith("get")) {
name = Introspector.decapitalize(name.substring(3));
}
return name;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module.Feature;
import com.fasterxml.jackson.datatype.hibernate5.data.Customer;
import com.fasterxml.jackson.datatype.hibernate5.data.Payment;

Expand Down Expand Up @@ -57,4 +59,25 @@ public void testGetCustomerJson() throws Exception
emf.close();
}
}

@Test
public void testSerializeIdentifierFeature() throws JsonProcessingException {
Hibernate5Module module = new Hibernate5Module();
module.enable(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
ObjectMapper objectMapper = new ObjectMapper().registerModule(module);

EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistenceUnit");
try {
EntityManager em = emf.createEntityManager();
Customer customerRef = em.getReference(Customer.class, 103);
em.close();
assertFalse(Hibernate.isInitialized(customerRef));

String json = objectMapper.writeValueAsString(customerRef);
assertFalse(Hibernate.isInitialized(customerRef));
assertEquals("{\"customerNumber\":103}", json);
} finally {
emf.close();
}
}
}

0 comments on commit c6625ba

Please sign in to comment.