org.javers.shadow.ShadowGraphBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javers-core Show documentation
Show all versions of javers-core Show documentation
JaVers - object auditing and diff framework for Java
package org.javers.shadow;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import org.javers.common.validation.Validate;
import org.javers.core.commit.CommitMetadata;
import org.javers.core.json.JsonConverter;
import org.javers.core.metamodel.object.*;
import org.javers.core.metamodel.type.*;
import java.time.format.DateTimeParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import static org.javers.core.metamodel.object.CdoSnapshotStateBuilder.cdoSnapshotState;
/**
* Stateful builder
*
* @author bartosz.walacik
*/
class ShadowGraphBuilder {
private final JsonConverter jsonConverter;
private final BiFunction referenceResolver;
private boolean built = false;
private Map builtNodes = new HashMap<>();
private final TypeMapper typeMapper;
private final CommitMetadata rootContext;
ShadowGraphBuilder(JsonConverter jsonConverter, BiFunction referenceResolver, TypeMapper typeMapper, CommitMetadata rootContext) {
this.jsonConverter = jsonConverter;
this.referenceResolver = referenceResolver;
this.typeMapper = typeMapper;
this.rootContext = rootContext;
}
Object buildDeepShadow(CdoSnapshot cdoSnapshot) {
Validate.argumentIsNotNull(cdoSnapshot);
switchToBuilt();
ShadowBuilder root = assembleShadowStub(cdoSnapshot);
doWiring();
return root.getShadow();
}
private void doWiring() {
builtNodes.values().forEach(ShadowBuilder::wire);
}
private void switchToBuilt() {
if (built) {
throw new IllegalStateException("already built");
}
built = true;
}
private ShadowBuilder assembleShallowReferenceShadow(InstanceId instanceId, EntityType shallowReferenceType) {
CdoSnapshotState state = cdoSnapshotState().withPropertyValue(shallowReferenceType.getIdProperty(), instanceId.getCdoId()).build();
Object shadowStub = jsonConverter.fromJson(toJson(state), shallowReferenceType.getBaseJavaClass());
ShadowBuilder shadowBuilder = new ShadowBuilder(null, shadowStub);
builtNodes.put(instanceId, shadowBuilder);
return shadowBuilder;
}
private ShadowBuilder assembleShadowStub(CdoSnapshot cdoSnapshot) {
ShadowBuilder shadowBuilder = new ShadowBuilder(cdoSnapshot, null);
builtNodes.put(cdoSnapshot.getGlobalId(), shadowBuilder);
JsonObject jsonElement = toJson(cdoSnapshot.stateWithAllPrimitives());
mapCustomPropertyNamesToJavaOrigin(cdoSnapshot.getManagedType(), jsonElement);
followReferences(shadowBuilder, jsonElement);
shadowBuilder.withStub(
deserializeObjectFromJsonElement(cdoSnapshot.getManagedType(), jsonElement));
return shadowBuilder;
}
private Object deserializeObjectFromJsonElement(ManagedType managedType, JsonObject jsonElement) {
try {
return jsonConverter.fromJson(jsonElement, managedType.getBaseJavaClass());
} catch(JsonSyntaxException | DateTimeParseException e) {
return sanitizedDeserialization(jsonElement, managedType);
}
}
private Object sanitizedDeserialization(JsonObject jsonElement, ManagedType managedType) {
managedType.getProperties().forEach(p -> {
try {
jsonConverter.fromJson(jsonElement.get(p.getName()), p.getRawType());
}
catch (Exception e) {
jsonElement.remove(p.getName());
}
});
return jsonConverter.fromJson(jsonElement, managedType.getBaseJavaClass());
}
private void mapCustomPropertyNamesToJavaOrigin(ManagedType managedType, JsonObject jsonElement) {
managedType.forEachProperty(javersProperty -> {
if (javersProperty.hasCustomName()) {
JsonElement value = jsonElement.get(javersProperty.getName());
jsonElement.remove(javersProperty.getName());
jsonElement.add(javersProperty.getOriginalName(), value);
}
});
}
private void followReferences(ShadowBuilder currentNode, JsonObject jsonElement) {
CdoSnapshot cdoSnapshot = currentNode.getCdoSnapshot();
cdoSnapshot.getManagedType().forEachProperty( property -> {
if (cdoSnapshot.isNull(property)) {
return;
}
if (property.getType() instanceof ManagedType) {
GlobalId refId = (GlobalId) cdoSnapshot.getPropertyValue(property);
ShadowBuilder target = createOrReuseNodeFromRef(refId, property);
if (target != null) {
currentNode.addReferenceWiring(property, target);
}
jsonElement.remove(property.getName());
}
if (typeMapper.isContainerOfManagedTypes(property.getType()) ||
typeMapper.isKeyValueTypeWithManagedTypes(property.getType()))
{
EnumerableType propertyType = property.getType();
Object containerWithRefs = cdoSnapshot.getPropertyValue(property);
if (!propertyType.isEmpty(containerWithRefs)) {
currentNode.addEnumerableWiring(property, propertyType
.map(containerWithRefs, (value) -> passValueOrCreateNodeFromRef(value, property), true));
jsonElement.remove(property.getName());
}
}
});
}
private Object passValueOrCreateNodeFromRef(Object value, JaversProperty property) {
if (value instanceof GlobalId) {
return createOrReuseNodeFromRef((GlobalId)value, property);
}
return value;
}
private ShadowBuilder createOrReuseNodeFromRef(GlobalId globalId, JaversProperty property) {
if (builtNodes.containsKey(globalId)) {
return builtNodes.get(globalId);
}
if (property.isShallowReference()) {
EntityType shallowReferenceType = property.getType() instanceof EntityType
? property.getType()
: (EntityType)typeMapper.getJaversManagedType(globalId);
if (shallowReferenceType.getIdProperty().getType() instanceof ValueObjectType) {
//TODO don't know how to reconstruct Id in ShallowReference, which happened to be a ValueObject,
return null;
} else {
return assembleShallowReferenceShadow((InstanceId) globalId, shallowReferenceType);
}
}
CdoSnapshot cdoSnapshot = referenceResolver.apply(rootContext, globalId);
if (cdoSnapshot != null) {
return assembleShadowStub(cdoSnapshot);
}
return null;
}
private JsonObject toJson(CdoSnapshotState state) {
return (JsonObject)jsonConverter.toJsonElement(state);
}
}