TSK-1642: now resolving complex objects during change detection properly

This commit is contained in:
Mustapha Zorgati 2021-06-09 23:01:44 +02:00
parent 9b98b56ce4
commit 5e45c1a1de
2 changed files with 56 additions and 57 deletions

View File

@ -1,12 +1,13 @@
package pro.taskana.common.internal.util; package pro.taskana.common.internal.util;
import static pro.taskana.common.internal.util.CheckedFunction.wrap;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.json.JSONObject; import org.json.JSONObject;
@ -23,82 +24,76 @@ public class ObjectAttributeChangeDetector {
* @param newObject the new object for the comparison * @param newObject the new object for the comparison
* @param <T> The generic type parameter * @param <T> The generic type parameter
* @return the details of all changed fields as JSON string * @return the details of all changed fields as JSON string
* @throws SystemException when any parameter is null * @throws SystemException when any parameter is null or the class of oldObject and newObject do
* not match
*/ */
public static <T> String determineChangesInAttributes(T oldObject, T newObject) { public static <T> String determineChangesInAttributes(T oldObject, T newObject) {
List<Field> fields = new ArrayList<>(); if (oldObject == null || newObject == null) {
if (Objects.isNull(oldObject) || Objects.isNull(newObject)) {
throw new SystemException( throw new SystemException(
"Null was provided as a parameter. Please provide two objects of the same type"); "Null was provided as a parameter. Please provide two objects of the same type");
} }
Class<?> currentClass = oldObject.getClass(); Class<?> objectClass = oldObject.getClass();
if (List.class.isAssignableFrom(objectClass)) {
if (List.class.isAssignableFrom(currentClass)) {
return compareLists(oldObject, newObject); return compareLists(oldObject, newObject);
} else {
retrieveFields(fields, currentClass);
} }
Predicate<Triplet<Field, Object, Object>> areFieldsNotEqual = // this has to be checked after we deal with List data types, because
fieldAndValuePairTriplet -> // we want to allow different implementations of the List interface to work as well.
!Objects.equals( if (!oldObject.getClass().equals(newObject.getClass())) {
fieldAndValuePairTriplet.getMiddle(), fieldAndValuePairTriplet.getRight()); throw new SystemException(
Predicate<Triplet<Field, Object, Object>> isFieldNotCustomAttributes = String.format(
fieldAndValuePairTriplet -> "The classes differ between the oldObject(%s) and newObject(%s). "
!fieldAndValuePairTriplet.getLeft().getName().equals("customAttributes"); + "In order to detect changes properly they should not differ.",
oldObject.getClass().getName(), newObject.getClass().getName()));
}
List<JSONObject> changedAttributes = List<JSONObject> changedAttributes =
fields.stream() retrieveAllFields(objectClass).stream()
.peek(field -> field.setAccessible(true)) .peek(field -> field.setAccessible(true))
.map( .filter(field -> !"customAttributes".equals(field.getName()))
CheckedFunction.wrap( .map(wrap(field -> Triplet.of(field, field.get(oldObject), field.get(newObject))))
field -> Triplet.of(field, field.get(oldObject), field.get(newObject)))) .filter(t -> !Objects.equals(t.getMiddle(), t.getRight()))
.filter(areFieldsNotEqual.and(isFieldNotCustomAttributes)) .map(t -> generateChangedAttribute(t.getLeft(), t.getMiddle(), t.getRight()))
.map(
fieldAndValuePairTriplet -> {
JSONObject changedAttribute = new JSONObject();
changedAttribute.put("fieldName", fieldAndValuePairTriplet.getLeft().getName());
changedAttribute.put(
"oldValue",
Optional.ofNullable(fieldAndValuePairTriplet.getMiddle()).orElse(""));
changedAttribute.put(
"newValue",
Optional.ofNullable(fieldAndValuePairTriplet.getRight()).orElse(""));
return changedAttribute;
})
.collect(Collectors.toList()); .collect(Collectors.toList());
JSONObject changes = new JSONObject(); JSONObject changes = new JSONObject();
changes.put("changes", changedAttributes); changes.put("changes", changedAttributes);
return changes.toString(); return changes.toString();
} }
private static void retrieveFields(List<Field> fields, Class<?> currentClass) { private static JSONObject generateChangedAttribute(
Field field, Object oldValue, Object newValue) {
JSONObject changedAttribute = new JSONObject();
changedAttribute.put("fieldName", field.getName());
changedAttribute.put(
"oldValue", Optional.ofNullable(oldValue).map(JSONObject::wrap).orElse(""));
changedAttribute.put(
"newValue", Optional.ofNullable(newValue).map(JSONObject::wrap).orElse(""));
return changedAttribute;
}
private static List<Field> retrieveAllFields(Class<?> currentClass) {
List<Field> fields = new ArrayList<>();
while (currentClass.getSuperclass() != null) { while (currentClass.getSuperclass() != null) {
fields.addAll(Arrays.asList(currentClass.getDeclaredFields())); fields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass(); currentClass = currentClass.getSuperclass();
} }
return fields;
} }
private static <T> String compareLists(T oldObject, T newObject) { private static <T> String compareLists(T oldObject, T newObject) {
if (!oldObject.equals(newObject)) { if (oldObject.equals(newObject)) {
JSONObject changedAttribute = new JSONObject(); return "";
}
JSONObject changedAttribute = new JSONObject();
changedAttribute.put("oldValue", oldObject); changedAttribute.put("oldValue", oldObject);
changedAttribute.put("newValue", newObject); changedAttribute.put("newValue", newObject);
JSONObject changes = new JSONObject(); JSONObject changes = new JSONObject();
changes.put("changes", changedAttribute); changes.put("changes", changedAttribute);
return changes.toString(); return changes.toString();
} }
return "";
}
} }

View File

@ -148,20 +148,24 @@ class CreateHistoryEventOnTaskTransferAccTest extends AbstractAccTest {
TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId); TaskHistoryEvent event = historyService.getTaskHistoryEvent(eventId);
assertThat(event.getDetails()).isNotNull(); assertThat(event.getDetails()).isNotNull();
JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes"); JSONArray changes = new JSONObject(event.getDetails()).getJSONArray("changes");
assertThat(changes.length()).isPositive(); assertThat(changes.length()).isPositive();
for (int i = 0; i < changes.length(); i++) { boolean foundField = false;
for (int i = 0; i < changes.length() && !foundField; i++) {
JSONObject change = changes.getJSONObject(i); JSONObject change = changes.getJSONObject(i);
if (change.get("fieldName").equals("workbasketSummary")) { if (change.get("fieldName").equals("workbasketSummary")) {
String oldWorkbasketStr = (String) change.get("oldValue"); foundField = true;
String newWorkbasketStr = (String) change.get("newValue"); String oldWorkbasketStr = change.get("oldValue").toString();
String newWorkbasketStr = change.get("newValue").toString();
WorkbasketService workbasketService = taskanaEngine.getWorkbasketService(); WorkbasketService workbasketService = taskanaEngine.getWorkbasketService();
Workbasket oldWorkbasket = workbasketService.getWorkbasket(expectedOldValue); Workbasket oldWorkbasket = workbasketService.getWorkbasket(expectedOldValue);
assertThat(oldWorkbasket.asSummary()).hasToString(oldWorkbasketStr); assertThat(oldWorkbasketStr)
.isEqualTo(JSONObject.wrap(oldWorkbasket.asSummary()).toString());
Workbasket newWorkbasket = workbasketService.getWorkbasket(expectedNewValue); Workbasket newWorkbasket = workbasketService.getWorkbasket(expectedNewValue);
assertThat(newWorkbasket.asSummary()).hasToString(newWorkbasketStr); assertThat(newWorkbasketStr)
.isEqualTo(JSONObject.wrap(newWorkbasket.asSummary()).toString());
} }
} }
assertThat(foundField).describedAs("changes do not contain field 'workbasketSummary'").isTrue();
assertThat(event.getId()).startsWith("THI:"); assertThat(event.getId()).startsWith("THI:");
assertThat(event.getOldValue()).isEqualTo(expectedOldValue); assertThat(event.getOldValue()).isEqualTo(expectedOldValue);