Move PdxInstance identifier/identity resolution logic from JsonCacheDataImporterExporter to PdxInstanceWrapper.

This commit is contained in:
John Blum
2020-06-10 12:39:21 -07:00
parent 0a3c2bb7ab
commit 91a10a8ca1
4 changed files with 475 additions and 465 deletions

View File

@@ -51,6 +51,7 @@ public class PdxInstanceWrapper implements PdxInstance, Sendable {
public static final String AT_TYPE_FIELD_NAME = "@type";
public static final String CLASS_NAME_PROPERTY = "className";
public static final String ID_FIELD_NAME = "id";
protected static final String NO_FIELD_NAME = "";
protected static final String ARRAY_BEGIN = "[";
protected static final String ARRAY_END = "]";
@@ -202,14 +203,6 @@ public class PdxInstanceWrapper implements PdxInstance, Sendable {
return getDelegate().isEnum();
}
/**
* @inheritDoc
*/
@Override
public boolean isIdentityField(String fieldName) {
return getDelegate().isIdentityField(fieldName);
}
/**
* @inheritDoc
*/
@@ -226,10 +219,123 @@ public class PdxInstanceWrapper implements PdxInstance, Sendable {
return getDelegate().getFieldNames();
}
/**
* Determines the {@link Object identifier} for, or {@link PdxInstance#isIdentityField(String) identity} of,
* this {@link PdxInstance}.
*
* @return the {@link Object identifier} for this {@link PdxInstance}; never {@literal null}.
* @throws IllegalStateException if the {@link PdxInstance} does not have an id.
* @see #isIdentityField(String)
* @see #getField(String)
* @see #getFieldNames()
* @see #getId()
*/
public Object getIdentifier() {
Optional<String> identityFieldName = nullSafeList(getFieldNames()).stream()
.filter(this::hasText)
.filter(this::isIdentityField)
.findFirst();
return identityFieldName
.map(this::getField)
.orElseGet(this::getId);
}
/**
* Searches for a PDX {@link String field name} called {@literal id} on this {@link PdxInstance}
* and returns its {@link Object value} as the {@link Object identifier} for,
* or {@link PdxInstance#isIdentityField(String) identity} of, this {@link PdxInstance}.
*
* @return the {@link Object value} of the {@literal id} {@link String field} on this {@link PdxInstance}.
* @throws IllegalStateException if this {@link PdxInstance} does not have an id.
* @see #getAtIdentifier()
* @see #getField(String)
* @see #hasField(String)
*/
protected Object getId() {
return hasField(ID_FIELD_NAME)
? getField(ID_FIELD_NAME)
: getAtIdentifier();
}
/**
* Searches for a PDX {@link String field} declared by the {@literal @identifier} metadata {@link String field}
* on this {@link PdxInstance} and returns the {@link Object value} of this {@link String field}
* as the {@link Object identifier} for, or {@link PdxInstance#isIdentityField(String) identity} of,
* this {@link PdxInstance}.
*
* @return the {@link Object value} of the {@link String field} declared in the {@literal @identifier} metadata
* {@link String field} on this {@link PdxInstance}.
* @throws IllegalStateException if the {@link PdxInstance} does not have an id.
* @see org.apache.geode.pdx.PdxInstance
*/
protected Object getAtIdentifier() {
return Optional.of(AT_IDENTIFIER_FIELD_NAME)
.filter(this::hasField)
.map(this::getField)
.map(String::valueOf)
.filter(this::hasField)
.map(this::getField)
.orElseThrow(() -> new IllegalStateException(String.format("PdxInstance for type [%1$s] has no %2$s",
getClassName(), resolveMessageForIdentifierError(this))));
}
private String resolveMessageForIdentifierError(PdxInstance pdxInstance) {
String message = "declared identifier";
if (pdxInstance.hasField(ID_FIELD_NAME)) {
message = "id";
}
else if (pdxInstance.hasField(AT_IDENTIFIER_FIELD_NAME)) {
Object atIdentifierFieldValue = pdxInstance.getField(AT_IDENTIFIER_FIELD_NAME);
String resolvedIdentifierFieldName = Objects.nonNull(atIdentifierFieldValue)
? atIdentifierFieldValue.toString().trim()
: NO_FIELD_NAME;
boolean identifierFieldNameWasDeclaredAndIsValid = pdxInstance.hasField(resolvedIdentifierFieldName);
Object identifier = identifierFieldNameWasDeclaredAndIsValid
? pdxInstance.getField(resolvedIdentifierFieldName)
: null;
String ifMessage = "value [%s] for field [%s] declared in [%s]";
String elseMessage = "field [%s] declared in [%s]";
message = identifierFieldNameWasDeclaredAndIsValid
? String.format(ifMessage, identifier, resolvedIdentifierFieldName, AT_IDENTIFIER_FIELD_NAME)
: String.format(elseMessage, resolvedIdentifierFieldName, AT_IDENTIFIER_FIELD_NAME);
}
return message;
}
/**
* @inheritDoc
*/
@Override
public boolean isIdentityField(String fieldName) {
return getDelegate().isIdentityField(fieldName);
}
/**
* Materializes an {@link Object} from the PDX bytes described by this {@link PdxInstance}.
*
* If these PDX bytes describe an {@link Object} parsed from JSON, then the JSON is reconstructed from
* this {@link PdxInstance} and mapped to an instance of the {@link Class type} identified by
* the {@literal @type} metadata PDX {@link String field} using Jackson's {@link ObjectMapper}.
*
* @return an {@link Object} constructed from the PDX bytes described by this {@link PdxInstance}.
* @see com.fasterxml.jackson.databind.ObjectMapper
* @see java.lang.Object
* @see #getObjectMapper()
*/
@Override
public Object getObject() {
return getObjectMapper()
@@ -247,7 +353,7 @@ public class PdxInstanceWrapper implements PdxInstance, Sendable {
return objectMapper.readValue(json, type);
}
catch (Throwable ignore) {
// TODO Add log statement
// TODO Log Throwable?
return null;
}
})
@@ -370,6 +476,10 @@ public class PdxInstanceWrapper implements PdxInstance, Sendable {
return String.format(FIELD_TYPE_VALUE, fieldName, nullSafeType(fieldValue), fieldValue);
}
private boolean hasText(String value) {
return value != null && !value.trim().isEmpty();
}
private boolean isArray(Object value) {
return Objects.nonNull(value) && value.getClass().isArray();
}

View File

@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -264,6 +266,315 @@ public class PdxInstanceWrapperUnitTests {
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getIdentifierFromPdxInstanceHavingAnIdentity() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
doReturn(Arrays.asList("age", "id", "name")).when(mockPdxInstance).getFieldNames();
doReturn(true).when(mockPdxInstance).isIdentityField(eq("id"));
doReturn(42).when(mockPdxInstance).getField(eq("id"));
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
assertThat(wrapper.getIdentifier()).isEqualTo(42);
verify(wrapper, never()).getId();
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, times(1)).isIdentityField(eq("age"));
verify(mockPdxInstance, times(1)).isIdentityField(eq("id"));
verify(mockPdxInstance, never()).isIdentityField(eq("name"));
verify(mockPdxInstance, times(1)).getField(eq("id"));
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getIdentifierFromPdxInstanceHavingNoFields() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(null).when(mockPdxInstance).getFieldNames();
doReturn(69).when(wrapper).getId();
assertThat(wrapper.getIdentifier()).isEqualTo(69);
verify(wrapper, times(1)).getId();
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, never()).isIdentityField(anyString());
verify(mockPdxInstance, never()).getField(anyString());
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getIdentifierFromPdxInstanceHavingNoIdentityFields() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Arrays.asList("", "age", null, "name", " ")).when(mockPdxInstance).getFieldNames();
doReturn(false).when(mockPdxInstance).isIdentityField(any());
doReturn(99).when(wrapper).getId();
assertThat(wrapper.getIdentifier()).isEqualTo(99);
verify(wrapper, times(1)).getId();
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, times(1)).isIdentityField(eq("age"));
verify(mockPdxInstance, times(1)).isIdentityField(eq("name"));
verify(mockPdxInstance, never()).isIdentityField(isNull());
verify(mockPdxInstance, never()).isIdentityField(eq(""));
verify(mockPdxInstance, never()).isIdentityField(eq(" "));
verify(mockPdxInstance, never()).getField(anyString());
verifyNoMoreInteractions(mockPdxInstance);
}
@Test(expected = IllegalStateException.class)
public void getIdentifierFromPdxInstanceWithNoIdentifier() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Collections.singletonList("name")).when(mockPdxInstance).getFieldNames();
doReturn(false).when(mockPdxInstance).isIdentityField(anyString());
doThrow(new IllegalStateException("NO ID")).when(wrapper).getId();
try {
wrapper.getIdentifier();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("NO ID");
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getFieldNames();
verify(mockPdxInstance, times(1)).isIdentityField(eq("name"));
verify(mockPdxInstance, never()).getField(anyString());
verify(wrapper, times(1)).getId();
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test
public void getIdFromPdxInstanceHavingIdField() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
doReturn(42).when(mockPdxInstance).getField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
assertThat(wrapper.getId()).isEqualTo(42);
verify(mockPdxInstance, times(1)).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, times(1)).getField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(wrapper, never()).getAtIdentifier();
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getIdFromPdxInstanceHavingIdFieldWithNoValueReturnsNull() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
doReturn(null).when(mockPdxInstance).getField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
assertThat(wrapper.getId()).isNull();
verify(mockPdxInstance, times(1)).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, times(1)).getField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(wrapper, never()).getAtIdentifier();
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getIdFromPdxInstanceWithNoIdFieldCallsGetAtIdentifier() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(false).when(mockPdxInstance).hasField(any());
doReturn(99).when(wrapper).getAtIdentifier();
assertThat(wrapper.getId()).isEqualTo(99);
verify(mockPdxInstance, times(1)).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(wrapper, times(1)).getAtIdentifier();
verifyNoMoreInteractions(mockPdxInstance);
}
@Test
public void getAtIdentifierFromPdxInstance() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
doReturn(true).when(mockPdxInstance).hasField(eq("isbn"));
doReturn("isbn").when(mockPdxInstance).getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
doReturn("123456789").when(mockPdxInstance).getField(eq("isbn"));
assertThat(wrapper.getAtIdentifier()).isEqualTo("123456789");
verify(mockPdxInstance, times(1))
.hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1)).hasField(eq("isbn"));
verify(mockPdxInstance, times(1)).getField(eq("isbn"));
verifyNoMoreInteractions(mockPdxInstance);
}
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithNoDeclaredIdentity() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Account.class.getName()).when(mockPdxInstance).getClassName();
doReturn(false).when(mockPdxInstance).hasField(any());
try {
wrapper.getAtIdentifier();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("PdxInstance for type [%s] has no declared identifier",
Account.class.getName());
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2))
.hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(anyString());
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithNoId() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Account.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
try {
wrapper.getAtIdentifier();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("PdxInstance for type [%s] has no id",
Account.class.getName());
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(1))
.hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1))
.hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, never()).getField(any());
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithValidAtIdentifierAndIdentifierFieldButNoId() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Person.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
doReturn(false).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
doReturn(true).when(mockPdxInstance).hasField(eq("ssn"));
doReturn("ssn").when(mockPdxInstance).getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
doReturn(null).when(mockPdxInstance).getField(eq("ssn"));
try {
wrapper.getAtIdentifier();
}
catch (IllegalStateException expected) {
String expectedMessage = "PdxInstance for type [%s] has no value [null] for field [ssn] declared in [%s]";
assertThat(expected).hasMessage(expectedMessage, Person.class.getName(),
PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2)).hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1)).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, times(2)).hasField(eq("ssn"));
verify(mockPdxInstance, times(2)).getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(2)).getField(eq("ssn"));
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test(expected = IllegalStateException.class)
public void getAtIdentifierFromPdxInstanceWithAtIdentifierReferringToInvalidIdentifierField() {
PdxInstance mockPdxInstance = mock(PdxInstance.class);
PdxInstanceWrapper wrapper = spy(new PdxInstanceWrapper(mockPdxInstance));
doReturn(Person.class.getName()).when(mockPdxInstance).getClassName();
doReturn(true).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
doReturn(false).when(mockPdxInstance).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
doReturn(false).when(mockPdxInstance).hasField(eq("ssn"));
doReturn("ssn").when(mockPdxInstance).getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
try {
wrapper.getAtIdentifier();
}
catch (IllegalStateException expected) {
assertThat(expected).hasMessage("PdxInstance for type [%s] has no field [ssn] declared in [%s]",
Person.class.getName(), PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME);
assertThat(expected).hasNoCause();
throw expected;
}
finally {
verify(mockPdxInstance, times(1)).getClassName();
verify(mockPdxInstance, times(2)).hasField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, times(1)).hasField(eq(PdxInstanceWrapper.ID_FIELD_NAME));
verify(mockPdxInstance, times(2)).hasField(eq("ssn"));
verify(mockPdxInstance, times(2)).getField(eq(PdxInstanceWrapper.AT_IDENTIFIER_FIELD_NAME));
verify(mockPdxInstance, never()).getField(eq("ssn"));
verifyNoMoreInteractions(mockPdxInstance);
}
}
@Test
public void getObjectReturnsObject() throws JsonProcessingException {
@@ -481,6 +792,8 @@ public class PdxInstanceWrapperUnitTests {
String getName();
}
interface Person { }
interface SendablePdxInstance extends PdxInstance, Sendable { }
}