Refactor JSONFormatterPdxToJsonConverter to handle missing type metadata in both PdxInstances and in JSON, which is required to reconstitute the Object.
Resolves gh-67.
This commit is contained in:
@@ -17,22 +17,37 @@ package org.springframework.geode.data.json.converter.support;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.apache.geode.pdx.JSONFormatter;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
import org.apache.geode.pdx.WritablePdxInstance;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.geode.data.json.converter.ObjectToJsonConverter;
|
||||
import org.springframework.geode.pdx.PdxInstanceBuilder;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link ObjectToJsonConverter} implementation using the Apache Geode {@link JSONFormatter} to convert
|
||||
* from a {@link PdxInstance} to a {@literal JSON} {@link String}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see org.apache.geode.pdx.JSONFormatter
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.apache.geode.pdx.WritablePdxInstance
|
||||
* @see org.springframework.geode.data.json.converter.ObjectToJsonConverter
|
||||
* @see org.springframework.geode.data.json.converter.support.JacksonObjectToJsonConverter
|
||||
* @see org.springframework.geode.pdx.PdxInstanceBuilder
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JSONFormatterPdxToJsonConverter extends JacksonObjectToJsonConverter {
|
||||
@@ -70,6 +85,165 @@ public class JSONFormatterPdxToJsonConverter extends JacksonObjectToJsonConverte
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
protected @NonNull String convertPdxToJson(@NonNull PdxInstance pdxInstance) {
|
||||
//return JSONFormatter.toJSON(pdxInstance);
|
||||
//return JSONFormatter.toJSON(decorate(pdxInstance));
|
||||
return decorate(pdxInstance, jsonFormatterToJson(pdxInstance));
|
||||
}
|
||||
|
||||
@NonNull String jsonFormatterToJson(@NonNull PdxInstance pdxInstance) {
|
||||
return JSONFormatter.toJSON(pdxInstance);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING!!!
|
||||
*
|
||||
* First, this method might be less than optimal and could lead to PDX type explosion!
|
||||
*
|
||||
* Second, this {@code pdxInstance.createWriter().setField(AT_TYPE_METADATA_PROPERTY_NAME, className);} ...
|
||||
*
|
||||
* Throws:
|
||||
* org.apache.geode.pdx.PdxFieldDoesNotExistException: A field named @type does not exist on ...
|
||||
* PdxType[dsid=0,typenum=7232261,name=example.app.crm.model.Customer,fields=[id:long:identity:0:idx0(relativeOffset)=0:idx1(vlfOffsetIndex)=0, name:String:1:idx0(relativeOffset)=8:idx1(vlfOffsetIndex)=-1,]]
|
||||
* at org.apache.geode.pdx.internal.WritablePdxInstanceImpl.setField(WritablePdxInstanceImpl.java:119)
|
||||
* ...
|
||||
*
|
||||
* This code needs to create a {@literal new} {@link PdxInstance} from an existing {@link PdxInstance}
|
||||
* or add the new (PDX) field to the PDX type metadata using {@code PdxType.addField(:PdxField)} before
|
||||
* setting the new field on the {@link PdxInstance} using the {@link WritablePdxInstance}. Unfortunately,
|
||||
* {@code PdxType} is part of the internal API and updating and ditributing a {@code PdxType} is complicated,
|
||||
* requiring a Distributed Lock, among other responsibilities.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected @NonNull PdxInstance decorate(@NonNull PdxInstance pdxInstance) {
|
||||
|
||||
if (isMissingObjectTypeMetadata(pdxInstance)) {
|
||||
|
||||
String pdxInstanceClassName = pdxInstance.getClassName();
|
||||
|
||||
Assert.isTrue(hasValidClassName(pdxInstance), () ->
|
||||
String.format("Class name [%s] is required and cannot be equal to [%s]",
|
||||
pdxInstanceClassName, JSONFormatter.JSON_CLASSNAME));
|
||||
|
||||
pdxInstance = newPdxInstanceBuilder()
|
||||
.copy(pdxInstance)
|
||||
.writeString(AT_TYPE_METADATA_PROPERTY_NAME, pdxInstanceClassName)
|
||||
.create();
|
||||
}
|
||||
|
||||
return pdxInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link PdxInstanceBuilder}.
|
||||
*
|
||||
* @return a new instance of {@link PdxInstanceBuilder}; never {@literal null}.
|
||||
* @see org.springframework.geode.pdx.PdxInstanceBuilder
|
||||
*/
|
||||
@NonNull PdxInstanceBuilder newPdxInstanceBuilder() {
|
||||
return PdxInstanceBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates the given {@link String JSON} to include the {@literal @type} metadata property in order to
|
||||
* indicate the type of the {@literal JSON} object, which is required for deserialization back to PDX.
|
||||
*
|
||||
* If an {@link JsonProcessingException} is thrown during this operation and if the {@link PdxInstance}
|
||||
* has a {@link #hasValidClassName(PdxInstance) valid class name}, then an attempt is made to serialize
|
||||
* the {@link PdxInstance#getObject() object instance} of the {@link PdxInstance} to {@link String JSON}
|
||||
* using Jackson's {@link ObjectMapper}.
|
||||
*
|
||||
* @param pdxInstance required {@link PdxInstance} from which the {@link String JSON} was serialized;
|
||||
* must not be {@literal null}.
|
||||
* @param json {@link String JSON} generated from the serialization of the {@link PdxInstance};
|
||||
* must not be {@literal null}.
|
||||
* @return the decorated {@link String JSON} including the {@literal @type} metadata property.
|
||||
* @throws DataRetrievalFailureException if {@link String JSON} cannot be decorated with type metadata
|
||||
* and the {@link PdxInstance} is not based on a valid {@link Class} type.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see #newObjectMapper(Object)
|
||||
* @see super#convert(Object)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected @NonNull String decorate(@NonNull PdxInstance pdxInstance, @NonNull String json) {
|
||||
|
||||
if (isDecorationRequired(pdxInstance, json)) {
|
||||
try {
|
||||
|
||||
ObjectMapper objectMapper = newObjectMapper(json);
|
||||
|
||||
JsonNode jsonNode = objectMapper.readTree(json);
|
||||
|
||||
if (isMissingObjectTypeMetadata(jsonNode)) {
|
||||
((ObjectNode) jsonNode).put(AT_TYPE_METADATA_PROPERTY_NAME, pdxInstance.getClassName());
|
||||
json = objectMapper.writeValueAsString(jsonNode);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
catch (JsonProcessingException cause) {
|
||||
|
||||
if (hasValidClassName(pdxInstance)) {
|
||||
return convertObjectToJson(pdxInstance.getObject());
|
||||
}
|
||||
|
||||
String message = String.format("Failed to parse JSON [%s]", json);
|
||||
|
||||
throw new DataRetrievalFailureException(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link PdxInstance}
|
||||
* has a valid {@link Class#getName() Class Name}.
|
||||
*
|
||||
* @param pdxInstance {@link PdxInstance} to evaluate;
|
||||
* @return a boolean value indicating whether the {@link PdxInstance}
|
||||
* has a valid {@link Class#getName() Class Name}.
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
*/
|
||||
boolean hasValidClassName(@Nullable PdxInstance pdxInstance) {
|
||||
|
||||
return Optional.ofNullable(pdxInstance)
|
||||
.map(PdxInstance::getClassName)
|
||||
.filter(StringUtils::hasText)
|
||||
.filter(className -> !JSONFormatter.JSON_CLASSNAME.equals(className))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private boolean isDecorationRequired(@Nullable PdxInstance pdxInstance, @Nullable String json) {
|
||||
return isMissingObjectTypeMetadata(pdxInstance) && isValidJson(json);
|
||||
}
|
||||
|
||||
private boolean isMissingObjectTypeMetadata(@Nullable JsonNode node) {
|
||||
return isObjectNode(node) && !node.has(AT_TYPE_METADATA_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
private boolean isMissingObjectTypeMetadata(@Nullable PdxInstance pdxInstance) {
|
||||
return pdxInstance != null && !pdxInstance.hasField(AT_TYPE_METADATA_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine if the given {@link JsonNode} represents a valid {@link String JSON} object.
|
||||
*
|
||||
* @param node {@link JsonNode} to evaluate.
|
||||
* @return a boolean valued indicating whether the given {@link JsonNode} is a valid {@link ObjectNode}.
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
*/
|
||||
boolean isObjectNode(@Nullable JsonNode node) {
|
||||
return node != null && (node.isObject() || node instanceof ObjectNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Null-safe method to determine whether the given {@link String JSON} is valid.
|
||||
*
|
||||
* @param json {@link String} containing JSON to evaluate.
|
||||
* @return a boolean value indicating whether the given {@link String JSON} is valid.
|
||||
*/
|
||||
boolean isValidJson(@Nullable String json) {
|
||||
return StringUtils.hasText(json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,35 @@ package org.springframework.geode.data.json.converter.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerationException;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.geode.pdx.JSONFormatter;
|
||||
import org.apache.geode.pdx.PdxInstance;
|
||||
import org.apache.geode.pdx.PdxInstanceFactory;
|
||||
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.geode.pdx.PdxInstanceBuilder;
|
||||
|
||||
import example.app.crm.model.Customer;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link JSONFormatterPdxToJsonConverter}.
|
||||
@@ -36,8 +53,14 @@ import org.apache.geode.pdx.PdxInstance;
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see com.fasterxml.jackson.databind.JsonNode
|
||||
* @see com.fasterxml.jackson.databind.ObjectMapper
|
||||
* @see com.fasterxml.jackson.databind.node.ObjectNode
|
||||
* @see org.apache.geode.pdx.JSONFormatter
|
||||
* @see org.apache.geode.pdx.PdxInstance
|
||||
* @see org.apache.geode.pdx.PdxInstanceFactory
|
||||
* @see org.springframework.geode.data.json.converter.support.JSONFormatterPdxToJsonConverter
|
||||
* @see org.springframework.geode.pdx.PdxInstanceBuilder
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class JSONFormatterPdxToJsonConverterUnitTests {
|
||||
@@ -60,7 +83,7 @@ public class JSONFormatterPdxToJsonConverterUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertsPdxInstanceToJson() {
|
||||
public void convertsPdxToJson() {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
@@ -70,10 +93,459 @@ public class JSONFormatterPdxToJsonConverterUnitTests {
|
||||
|
||||
doReturn(json).when(converter).convertPdxToJson(eq(mockPdxInstance));
|
||||
|
||||
assertThat(converter.convertPdxToJson(mockPdxInstance)).isEqualTo(json);
|
||||
assertThat(converter.convert(mockPdxInstance)).isEqualTo(json);
|
||||
|
||||
verify(converter, times(1)).convertPdxToJson(eq(mockPdxInstance));
|
||||
verify(converter, never()).convertObjectToJson(any());
|
||||
verifyNoInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertPdxToJsonCallsDecorateAndJsonFormatterToJson() {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
doReturn(json).when(converter).jsonFormatterToJson(eq(mockPdxInstance));
|
||||
doReturn(json).when(converter).decorate(eq(mockPdxInstance), eq(json));
|
||||
|
||||
assertThat(converter.convertPdxToJson(mockPdxInstance)).isEqualTo(json);
|
||||
|
||||
verify(converter, times(1)).jsonFormatterToJson(eq(mockPdxInstance));
|
||||
verify(converter, times(1)).decorate(eq(mockPdxInstance), eq(json));
|
||||
verifyNoInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoratePdxInstance() {
|
||||
|
||||
PdxInstance mockPdxInstanceSource = mock(PdxInstance.class);
|
||||
PdxInstance mockPdxInstanceTarget = mock(PdxInstance.class);
|
||||
|
||||
PdxInstanceBuilder mockPdxInstanceBuilder = mock(PdxInstanceBuilder.class);
|
||||
|
||||
PdxInstanceFactory mockPdxInstanceFactory = mock(PdxInstanceFactory.class);
|
||||
|
||||
doReturn(false).when(mockPdxInstanceSource).hasField(anyString());
|
||||
doReturn("example.app.test.model.Type").when(mockPdxInstanceSource).getClassName();
|
||||
doReturn(mockPdxInstanceFactory).when(mockPdxInstanceBuilder).copy(eq(mockPdxInstanceSource));
|
||||
doReturn(mockPdxInstanceFactory).when(mockPdxInstanceFactory)
|
||||
.writeString(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME),
|
||||
eq("example.app.test.model.Type"));
|
||||
doReturn(mockPdxInstanceTarget).when(mockPdxInstanceFactory).create();
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
doReturn(mockPdxInstanceBuilder).when(converter).newPdxInstanceBuilder();
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstanceSource)).isEqualTo(mockPdxInstanceTarget);
|
||||
|
||||
verify(mockPdxInstanceSource, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(mockPdxInstanceSource, times(2)).getClassName();
|
||||
verify(converter,times(1)).newPdxInstanceBuilder();
|
||||
verify(mockPdxInstanceBuilder, times(1)).copy(eq(mockPdxInstanceSource));
|
||||
verify(mockPdxInstanceFactory, times(1))
|
||||
.writeString(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME),
|
||||
eq("example.app.test.model.Type"));
|
||||
verify(mockPdxInstanceFactory, times(1)).create();
|
||||
verifyNoInteractions(mockPdxInstanceTarget);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoratePdxInstanceIsUnnecessary() {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(true).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = new JSONFormatterPdxToJsonConverter();
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance)).isSameAs(mockPdxInstance);
|
||||
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void decoratePdxInstanceWithInvalidClassName() {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(false).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
doReturn(JSONFormatter.JSON_CLASSNAME).when(mockPdxInstance).getClassName();
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
try {
|
||||
converter.decorate(mockPdxInstance);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Class name [%s] is required and cannot be equal to [%s]",
|
||||
JSONFormatter.JSON_CLASSNAME);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(mockPdxInstance, times(2)).getClassName();
|
||||
verify(converter, never()).newPdxInstanceBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void decoratePdxInstanceWithNoClassName() {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(false).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
doReturn(null).when(mockPdxInstance).getClassName();
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
try {
|
||||
converter.decorate(mockPdxInstance);
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
|
||||
assertThat(expected).hasMessage("Class name [null] is required and cannot be equal to [%s]",
|
||||
JSONFormatter.JSON_CLASSNAME);
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(mockPdxInstance, times(2)).getClassName();
|
||||
verify(converter, never()).newPdxInstanceBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateWithNullPdxInstanceIsNullSafe() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().decorate(null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void decorateJson() throws JsonProcessingException {
|
||||
|
||||
String sourceJson = "{ \"name\": \"Jon Doe\" }";
|
||||
String targetJson = String.format("{ \"%s\": \"%s\"\"name\": \"Jon Doe\" }",
|
||||
JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME, Customer.class.getName());
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
|
||||
|
||||
JsonNode mockJsonNode = mock(ObjectNode.class);
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(JSONFormatterPdxToJsonConverter.class);
|
||||
|
||||
doReturn(Customer.class.getName()).when(mockPdxInstance).getClassName();
|
||||
doReturn(false).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
doReturn(mockObjectMapper).when(converter).newObjectMapper(eq(sourceJson));
|
||||
doReturn(mockJsonNode).when(mockObjectMapper).readTree(eq(sourceJson));
|
||||
doReturn(false).when(mockJsonNode)
|
||||
.has(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
doReturn(targetJson).when(mockObjectMapper).writeValueAsString(eq(mockJsonNode));
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance, sourceJson)).isEqualTo(targetJson);
|
||||
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(converter, times(1)).newObjectMapper(eq(sourceJson));
|
||||
verify(mockObjectMapper, times(1)).readTree(eq(sourceJson));
|
||||
verify(mockJsonNode, times(1))
|
||||
.has(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verify((ObjectNode) mockJsonNode, times(1))
|
||||
.put(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME), eq(Customer.class.getName()));
|
||||
verify(mockObjectMapper, times(1)).writeValueAsString(eq(mockJsonNode));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateJsonForReal() {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
String expectedJson = String.format("{\"name\":\"Jon Doe\",\"%s\":\"%s\"}",
|
||||
JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME, Customer.class.getName());
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(Customer.class.getName()).when(mockPdxInstance).getClassName();
|
||||
doReturn(false).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = new JSONFormatterPdxToJsonConverter();
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance, json)).isEqualTo(expectedJson);
|
||||
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateJsonIsUnnecessary() {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(true).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance, json)).isEqualTo(json);
|
||||
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(converter, never()).newObjectMapper(any());
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decorateJsonNodeIsUnnecessary() throws JsonProcessingException {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
|
||||
|
||||
JsonNode mockJsonNode = mock(ObjectNode.class);
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
doReturn(false).when(mockPdxInstance)
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
doReturn(mockObjectMapper).when(converter).newObjectMapper(eq(json));
|
||||
doReturn(mockJsonNode).when(mockObjectMapper).readTree(eq(json));
|
||||
doReturn(true).when(mockJsonNode)
|
||||
.has(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance, json)).isEqualTo(json);
|
||||
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(converter, times(1)).newObjectMapper(eq(json));
|
||||
verify(mockObjectMapper, times(1)).readTree(eq(json));
|
||||
verify(mockJsonNode, times(1))
|
||||
.has(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verifyNoMoreInteractions(mockPdxInstance, mockObjectMapper, mockJsonNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decoratesJsonThrowsJsonProcessingExceptionWhenPdxInstanceClassNameIsSpecifiedAndValid()
|
||||
throws JsonProcessingException {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
|
||||
|
||||
Customer jonDoe = Customer.newCustomer(1L, "Jon Doe");
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
doReturn(jonDoe.getClass().getName()).when(mockPdxInstance).getClassName();
|
||||
doReturn(jonDoe).when(mockPdxInstance).getObject();
|
||||
doReturn(false).when(mockPdxInstance).hasField(anyString());
|
||||
doReturn(mockObjectMapper).when(converter).newObjectMapper(eq(json));
|
||||
doThrow(new JsonGenerationException("TEST", (JsonGenerator) null)).when(mockObjectMapper).readTree(eq(json));
|
||||
doReturn(json).when(converter).convertObjectToJson(eq(jonDoe));
|
||||
|
||||
assertThat(converter.decorate(mockPdxInstance, json)).isEqualTo(json);
|
||||
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(converter, times(1)).newObjectMapper(eq(json));
|
||||
verify(mockObjectMapper, times(1)).readTree(eq(json));
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verify(mockPdxInstance, times(1)).getObject();
|
||||
verify(converter, times(1)).convertObjectToJson(eq(jonDoe));
|
||||
verifyNoMoreInteractions(mockPdxInstance, mockObjectMapper);
|
||||
}
|
||||
|
||||
@Test(expected = DataRetrievalFailureException.class)
|
||||
public void decorateJsonThrowsDataRetrievalFailureException() throws JsonProcessingException {
|
||||
|
||||
String json = "{ \"name\": \"Jon Doe\" }";
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
|
||||
|
||||
JSONFormatterPdxToJsonConverter converter = spy(new JSONFormatterPdxToJsonConverter());
|
||||
|
||||
doReturn(false).when(mockPdxInstance).hasField(anyString());
|
||||
doReturn(mockObjectMapper).when(converter).newObjectMapper(eq(json));
|
||||
doThrow(new JsonGenerationException("TEST", (JsonGenerator) null)).when(mockObjectMapper).readTree(eq(json));
|
||||
doReturn(JSONFormatter.JSON_CLASSNAME).when(mockPdxInstance).getClassName();
|
||||
|
||||
try {
|
||||
converter.decorate(mockPdxInstance, json);
|
||||
}
|
||||
catch (DataRetrievalFailureException expected) {
|
||||
|
||||
assertThat(expected).hasMessageStartingWith("Failed to parse JSON [%s]", json);
|
||||
assertThat(expected).hasCauseInstanceOf(JsonGenerationException.class);
|
||||
assertThat(expected.getCause()).hasMessage("TEST");
|
||||
assertThat(expected.getCause()).hasNoCause();
|
||||
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockPdxInstance, times(1))
|
||||
.hasField(eq(JSONFormatterPdxToJsonConverter.AT_TYPE_METADATA_PROPERTY_NAME));
|
||||
verify(converter, times(1)).newObjectMapper(eq(json));
|
||||
verify(mockObjectMapper, times(1)).readTree(eq(json));
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verifyNoMoreInteractions(mockPdxInstance, mockObjectMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingValidClassName() {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(Customer.class.getName()).when(mockPdxInstance).getClassName();
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().hasValidClassName(mockPdxInstance)).isTrue();
|
||||
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingNonExistingClassType() {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn("non.existing.class.Type").when(mockPdxInstance).getClassName();
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().hasValidClassName(mockPdxInstance)).isTrue();
|
||||
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
private void testHasValidClassNameWithPdxInstanceHavingInvalidClassName(String className) {
|
||||
|
||||
PdxInstance mockPdxInstance = mock(PdxInstance.class);
|
||||
|
||||
doReturn(className).when(mockPdxInstance).getClassName();
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().hasValidClassName(mockPdxInstance)).isFalse();
|
||||
|
||||
verify(mockPdxInstance, times(1)).getClassName();
|
||||
verifyNoMoreInteractions(mockPdxInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingJsonFormatterJsonClassName() {
|
||||
testHasValidClassNameWithPdxInstanceHavingInvalidClassName(JSONFormatter.JSON_CLASSNAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingBlankClassName() {
|
||||
testHasValidClassNameWithPdxInstanceHavingInvalidClassName(" ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingEmptyClassName() {
|
||||
testHasValidClassNameWithPdxInstanceHavingInvalidClassName("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasValidClassNameWithPdxInstanceHavingNullClassName() {
|
||||
testHasValidClassNameWithPdxInstanceHavingInvalidClassName(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isNotObjectNodeWhenJsonNodeIsObjectReturnsFalse() {
|
||||
|
||||
JsonNode mockJsonNode = mock(JsonNode.class);
|
||||
|
||||
doReturn(false).when(mockJsonNode).isObject();
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isObjectNode(mockJsonNode)).isFalse();
|
||||
|
||||
verify(mockJsonNode, times(1)).isObject();
|
||||
verifyNoMoreInteractions(mockJsonNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isObjectNodeWhenJsonNodeIsObjectReturnsTrue() {
|
||||
|
||||
JsonNode mockJsonNode = mock(JsonNode.class);
|
||||
|
||||
doReturn(true).when(mockJsonNode).isObject();
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isObjectNode(mockJsonNode)).isTrue();
|
||||
|
||||
verify(mockJsonNode, times(1)).isObject();
|
||||
verifyNoMoreInteractions(mockJsonNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isObjectNodeWithObjectNode() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isObjectNode(mock(ObjectNode.class))).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isObjectNodeIsNullSafe() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isObjectNode(null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidJsonWithJson() {
|
||||
|
||||
assertThat(new JSONFormatterPdxToJsonConverter()
|
||||
.isValidJson("[{ \"name\": \"Jon Doe\" }, { \"name\": \"Jane Doe\" }]")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("{ \"name\": \"Jon Doe\" }")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("{}")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("[]")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("test")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("<xml>")).isTrue();
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("<html></html>")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidJsonWithNonJsonString() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("non-Json String")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidJsonWithBlankString() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson(" ")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidJsonWithEmptyString() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson("")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidJsonWithNull() {
|
||||
assertThat(new JSONFormatterPdxToJsonConverter().isValidJson(null)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user