Skip to content

New feature: REPLACE_PERSISTENT_COLLECTIONS to fix #93 #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ public enum Feature {
* @since 2.4
*/
REQUIRE_EXPLICIT_LAZY_LOADING_MARKER(false),

/**
* Replaces org.hibernate.collection.spi.PersistentCollection List, Set, Map subclasses to java.util.ArrayList, HashSet,
* HashMap, during Serialization.
* <p>
* Default is false.
*
* @since 2.8.2
*/
REPLACE_PERSISTENT_COLLECTIONS(false)
;

final boolean _defaultState;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.fasterxml.jackson.datatype.hibernate3;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.*;

Expand All @@ -21,6 +26,7 @@
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.impl.SessionFactoryImpl;
import org.hibernate.mapping.Bag;
import org.hibernate.transaction.JDBCTransactionFactory;
import org.hibernate.transaction.TransactionFactory;

Expand Down Expand Up @@ -256,6 +262,11 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi
if (_serializer == null) { // sanity check...
throw JsonMappingException.from(jgen, "PersistentCollection does not have serializer set");
}

if (Feature.REPLACE_PERSISTENT_COLLECTIONS.enabledIn(_features)) {
value = convertToJavaCollection(value); // Strip PersistentCollection
}

_serializer.serialize(value, jgen, provider);
}

Expand All @@ -279,6 +290,11 @@ public void serializeWithType(Object value, JsonGenerator jgen, SerializerProvid
if (_serializer == null) { // sanity check...
throw JsonMappingException.from(jgen, "PersistentCollection does not have serializer set");
}

if (Feature.REPLACE_PERSISTENT_COLLECTIONS.enabledIn(_features)) {
value = convertToJavaCollection(value); // Strip PersistentCollection
}

_serializer.serializeWithType(value, jgen, provider, typeSer);
}

Expand Down Expand Up @@ -384,4 +400,38 @@ protected boolean usesLazyLoading(BeanProperty property)
}
return false;
}

private Object convertToJavaCollection(Object value) {
if (!(value instanceof PersistentCollection)) {
return value;
}

if (value instanceof Set) {
return convertToSet((Set<?>) value);
}

if (value instanceof List
|| value instanceof Bag
) {
return convertToList((List<?>) value);
}

if (value instanceof Map) {
return convertToMap((Map<?, ?>) value);
}

throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}

private Object convertToList(List<?> value) {
return new ArrayList<>(value);
}

private Object convertToMap(Map<?, ?> value) {
return new HashMap<>(value);
}

private Object convertToSet(Set<?> value) {
return new HashSet<>(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.fasterxml.jackson.datatype.hibernate3;

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate3.data.Customer;
import com.fasterxml.jackson.datatype.hibernate3.data.Payment;
import org.hibernate.Hibernate;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.Map;
import java.util.Set;

public class ReplacePersistentCollectionTest {

private EntityManagerFactory emf;

private EntityManager em;

@Before
public void setUp() throws Exception {
emf = Persistence.createEntityManagerFactory("persistenceUnit");
em = emf.createEntityManager();
}

@After
public void tearDown() throws Exception {
em.close();
emf.close();
}

// [Issue#93], backwards compatible case
@Test
public void testNoReplacePersistentCollection() throws Exception {
final ObjectMapper mapper = new ObjectMapper()
.registerModule(new Hibernate3Module()
.configure(Hibernate3Module.Feature.FORCE_LAZY_LOADING, true)
).enableDefaultTyping();

Customer customer = em.find(Customer.class, 103);
Assert.assertFalse(Hibernate.isInitialized(customer.getPayments()));
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
Assert.assertTrue(json.contains("org.hibernate.collection"));
// should force loading...
Set<Payment> payments = customer.getPayments();
/*
System.out.println("--- JSON ---");
System.out.println(json);
System.out.println("--- /JSON ---");
*/

Assert.assertTrue(Hibernate.isInitialized(payments));
// TODO: verify
Assert.assertNotNull(json);

boolean exceptionThrown = false;
try {
Map<?, ?> stuff = mapper.readValue(json, Map.class);
} catch (JsonMappingException e) {
exceptionThrown = true;
}
Assert.assertTrue(exceptionThrown);
}

// [Issue#93], backwards compatible case
@Test
public void testReplacePersistentCollection() throws Exception {
final ObjectMapper mapper = new ObjectMapper()
.registerModule(new Hibernate3Module()
.configure(Hibernate3Module.Feature.FORCE_LAZY_LOADING, true)
.configure(Hibernate3Module.Feature.REPLACE_PERSISTENT_COLLECTIONS, true)
).enableDefaultTyping();

Customer customer = em.find(Customer.class, 103);
Assert.assertFalse(Hibernate.isInitialized(customer.getPayments()));
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(customer);
Assert.assertFalse(json.contains("org.hibernate.collection"));
// should force loading...
Set<Payment> payments = customer.getPayments();
/*
System.out.println("--- JSON ---");
System.out.println(json);
System.out.println("--- /JSON ---");
*/

Assert.assertTrue(Hibernate.isInitialized(payments));
// TODO: verify
Assert.assertNotNull(json);

/*
* Currently this cannot be verified due to Issue#94 default typing fails on 2.7.0 - 2.8.2-SNAPSHOT,
* commented out until that is fixed.
*/

boolean issue94failed = false;
try {
Map<?, ?> stuff = mapper.readValue(json, Map.class);
} catch (JsonMappingException e) {
issue94failed = true;
}

Assert.assertTrue("If this fails, means #94 is fixed. Replace to the below commented lines", issue94failed);

// Map<?, ?> stuff = mapper.readValue(json, Map.class);
//
// Assert.assertTrue(stuff.containsKey("payments"));
// Assert.assertTrue(stuff.containsKey("orders"));
// Assert.assertNull(stuff.get("orderes"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ public enum Feature {
* @since 2.4
*/
REQUIRE_EXPLICIT_LAZY_LOADING_MARKER(false),

/**
* Replaces org.hibernate.collection.spi.PersistentCollection List, Set, Map subclasses to java.util.ArrayList, HashSet,
* HashMap, during Serialization.
* <p>
* Default is false.
*
* @since 2.8.2
*/
REPLACE_PERSISTENT_COLLECTIONS(false)
;

final boolean _defaultState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.mapping.Bag;

import javax.persistence.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Wrapper serializer used to handle aspects of lazy loading that can be used
Expand Down Expand Up @@ -250,6 +256,11 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi
if (_serializer == null) { // sanity check...
throw JsonMappingException.from(jgen, "PersistentCollection does not have serializer set");
}

if (Feature.REPLACE_PERSISTENT_COLLECTIONS.enabledIn(_features)) {
value = convertToJavaCollection(value); // Strip PersistentCollection
}

_serializer.serialize(value, jgen, provider);
}

Expand All @@ -268,6 +279,11 @@ public void serializeWithType(Object value, JsonGenerator jgen, SerializerProvid
if (_serializer == null) { // sanity check...
throw JsonMappingException.from(jgen, "PersistentCollection does not have serializer set");
}

if (Feature.REPLACE_PERSISTENT_COLLECTIONS.enabledIn(_features)) {
value = convertToJavaCollection(value); // Strip PersistentCollection
}

_serializer.serializeWithType(value, jgen, provider, typeSer);
}

Expand Down Expand Up @@ -368,4 +384,38 @@ protected boolean usesLazyLoading(BeanProperty property) {
}
return false;
}

private Object convertToJavaCollection(Object value) {
if (!(value instanceof PersistentCollection)) {
return value;
}

if (value instanceof Set) {
return convertToSet((Set<?>) value);
}

if (value instanceof List
|| value instanceof Bag
) {
return convertToList((List<?>) value);
}

if (value instanceof Map) {
return convertToMap((Map<?, ?>) value);
}

throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}

private Object convertToList(List<?> value) {
return new ArrayList<>(value);
}

private Object convertToMap(Map<?, ?> value) {
return new HashMap<>(value);
}

private Object convertToSet(Set<?> value) {
return new HashSet<>(value);
}
}
Loading