Skip to content
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

Shallow multi edge #803

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 18 additions & 13 deletions javers-core/src/main/java/org/javers/core/graph/EdgeBuilder.java
Expand Up @@ -8,9 +8,6 @@
import org.javers.core.metamodel.object.PropertyOwnerContext;
import org.javers.core.metamodel.type.*;

import java.util.ArrayList;
import java.util.List;

/**
* @author bartosz walacik
*/
Expand Down Expand Up @@ -48,17 +45,25 @@ MultiEdge createMultiEdge(JaversProperty containerProperty, EnumerableType enume

Object container = node.getPropertyValue(containerProperty);

EnumerableFunction edgeBuilder;
EnumerableFunction edgeBuilder = (input, context) -> {
final ManagedType inputManagedType = typeMapper.getJaversManagedType(input.getClass());
final LiveCdo cdo = cdoFactory.create(input, context);
if (!containerProperty.isShallowReference() && !inputManagedType.hasShallowReferenceAnn()) {
return buildNodeStubOrReuse(cdo);
} else {
return cdo.getGlobalId();
}
};

if (enumerableType instanceof KeyValueType){
KeyValueType mapType = (KeyValueType)enumerableType;
edgeBuilder = new MultiEdgeMapBuilder(
typeMapper.getJaversType(mapType.getKeyType()) instanceof ManagedType,
typeMapper.getJaversType(mapType.getValueType()) instanceof ManagedType
typeMapper.getJaversType(mapType.getValueType()) instanceof ManagedType,
edgeBuilder
);

} else if (enumerableType instanceof ContainerType) {
edgeBuilder = (input, context) -> buildNodeStubOrReuse(cdoFactory.create(input, context));
} else {
} else if (!(enumerableType instanceof ContainerType)) {
throw new JaversException(JaversExceptionCode.NOT_IMPLEMENTED);
}

Expand All @@ -69,24 +74,24 @@ MultiEdge createMultiEdge(JaversProperty containerProperty, EnumerableType enume
private class MultiEdgeMapBuilder implements EnumerableFunction {
private final boolean managedKeys;
private final boolean managedValues;
private final EnumerableFunction edgeBuilder;

public MultiEdgeMapBuilder(boolean managedKeys, boolean managedValues) {
public MultiEdgeMapBuilder(boolean managedKeys, boolean managedValues, EnumerableFunction edgeBuilder) {
this.managedKeys = managedKeys;
this.managedValues = managedValues;
this.edgeBuilder = edgeBuilder;
}

@Override
public Object apply(Object keyOrValue, EnumerationAwareOwnerContext context) {
MapEnumerationOwnerContext mapContext = (MapEnumerationOwnerContext)context;

if (managedKeys && mapContext.isKey()) {
LiveNode objectNode = buildNodeStubOrReuse(cdoFactory.create(keyOrValue, context));
return objectNode;
return edgeBuilder.apply(keyOrValue, context);
}

if (managedValues && !mapContext.isKey()) {
LiveNode objectNode = buildNodeStubOrReuse(cdoFactory.create(keyOrValue, context));
return objectNode;
return edgeBuilder.apply(keyOrValue, context);
}

return keyOrValue;
Expand Down
Expand Up @@ -22,15 +22,18 @@ class ManagedClass {
private final List<JaversProperty> managedProperties;
private final List<JaversProperty> looksLikeId;
private final ManagedPropertiesFilter managedPropertiesFilter;
private final boolean hasShallowReferenceAnn;

ManagedClass(Class baseJavaClass, List<JaversProperty> managedProperties, List<JaversProperty> looksLikeId, ManagedPropertiesFilter managedPropertiesFilter) {
ManagedClass(Class baseJavaClass, List<JaversProperty> managedProperties, List<JaversProperty> looksLikeId,
ManagedPropertiesFilter managedPropertiesFilter, boolean hasShallowReferenceAnn) {
argumentsAreNotNull(baseJavaClass, managedProperties, looksLikeId, managedPropertiesFilter);

this.baseJavaClass = baseJavaClass;
this.managedProperties = new ArrayList<>();
this.propertiesByName = new HashMap<>();
this.looksLikeId = looksLikeId;
this.managedPropertiesFilter = managedPropertiesFilter;
this.hasShallowReferenceAnn = hasShallowReferenceAnn;

for (JaversProperty property : managedProperties) {
this.managedProperties.add(property);
Expand All @@ -39,11 +42,11 @@ class ManagedClass {
}

static ManagedClass unknown() {
return new ManagedClass(Object.class, Collections.emptyList(), Collections.emptyList(), ManagedPropertiesFilter.empty());
return new ManagedClass(Object.class, Collections.emptyList(), Collections.emptyList(), ManagedPropertiesFilter.empty(), false);
}

ManagedClass createShallowReference(){
return new ManagedClass(baseJavaClass, Collections.emptyList(), getLooksLikeId(), ManagedPropertiesFilter.empty());
return new ManagedClass(baseJavaClass, Collections.emptyList(), getLooksLikeId(), ManagedPropertiesFilter.empty(), true);
}

ManagedPropertiesFilter getManagedPropertiesFilter() {
Expand Down Expand Up @@ -96,4 +99,8 @@ void forEachProperty(Consumer<JaversProperty> consumer) {
Class<?> getBaseJavaClass() {
return baseJavaClass;
}

public boolean hasShallowReferenceAnn() {
return hasShallowReferenceAnn;
}
}
Expand Up @@ -27,22 +27,23 @@ ManagedClass create(ClientsClassDefinition def, ClassScan scan) {
ManagedPropertiesFilter managedPropertiesFilter =
new ManagedPropertiesFilter(def.getBaseJavaClass(), allProperties, def.getPropertiesFilter());

return create(def.getBaseJavaClass(), allProperties, managedPropertiesFilter);
return create(def.getBaseJavaClass(), allProperties, managedPropertiesFilter, scan.hasShallowReferenceAnn());
}

ManagedClass createFromPrototype(Class<?> baseJavaClass, ClassScan scan, ManagedPropertiesFilter prototypePropertiesFilter) {
List<JaversProperty> allProperties = convert(scan.getProperties());
return create(baseJavaClass, allProperties, prototypePropertiesFilter);
return create(baseJavaClass, allProperties, prototypePropertiesFilter, scan.hasShallowReferenceAnn());
}

private ManagedClass create(Class<?> baseJavaClass, List<JaversProperty> allProperties, ManagedPropertiesFilter propertiesFilter){
private ManagedClass create(Class<?> baseJavaClass, List<JaversProperty> allProperties,
ManagedPropertiesFilter propertiesFilter, boolean hasShallowReferenceAnn){

List<JaversProperty> filtered = propertiesFilter.filterProperties(allProperties);

filtered = filterIgnoredType(filtered, baseJavaClass);

return new ManagedClass(baseJavaClass, filtered,
positiveFilter(allProperties, p -> p.looksLikeId()), propertiesFilter);
positiveFilter(allProperties, p -> p.looksLikeId()), propertiesFilter, hasShallowReferenceAnn);
}

private List<JaversProperty> convert(List<Property> properties) {
Expand Down
Expand Up @@ -67,6 +67,10 @@ public Set<String> getPropertyNames(){
return managedClass.getPropertyNames();
}

public boolean hasShallowReferenceAnn() {
return managedClass.hasShallowReferenceAnn();
}

ManagedClass getManagedClass() {
return managedClass;
}
Expand Down
Expand Up @@ -35,7 +35,7 @@ public class ValueObjectType extends ManagedType{
}

public ValueObjectType(Class baseJavaClass, List<JaversProperty> allProperties){
this(new ManagedClass(baseJavaClass, allProperties, Collections.emptyList(), ManagedPropertiesFilter.empty()));
this(new ManagedClass(baseJavaClass, allProperties, Collections.emptyList(), ManagedPropertiesFilter.empty(), false));
}

ValueObjectType(ManagedClass valueObject, Optional<String> typeName, boolean isDefault) {
Expand Down
Expand Up @@ -58,6 +58,11 @@ private void switchToBuilt() {
built = true;
}

private ShadowBuilder assembleShallowReferenceShadow(InstanceId instanceId) {
final ManagedType type = typeMapper.getJaversManagedType(instanceId);
return assembleShallowReferenceShadow(instanceId, (EntityType) type);
}

private ShadowBuilder assembleShallowReferenceShadow(InstanceId instanceId, EntityType shallowReferenceType) {
CdoSnapshotState state = cdoSnapshotState().withPropertyValue(shallowReferenceType.getIdProperty(), instanceId.getCdoId()).build();
JsonObject jsonElement = (JsonObject)jsonConverter.toJsonElement(state);
Expand Down Expand Up @@ -158,7 +163,11 @@ private ShadowBuilder createOrReuseNodeFromRef(GlobalId globalId, JaversProperty
}

if (property.isShallowReference()) {
return assembleShallowReferenceShadow((InstanceId)globalId, (EntityType)property.getType());
if (property.getType() instanceof EntityType) {
return assembleShallowReferenceShadow((InstanceId) globalId, (EntityType) property.getType());
} else {
return assembleShallowReferenceShadow((InstanceId) globalId);
}
}

CdoSnapshot cdoSnapshot = referenceResolver.apply(rootContext, globalId);
Expand Down
Expand Up @@ -21,23 +21,25 @@ import static GlobalIdTestBuilder.valueObjectId
*/
class JaversCommitE2ETest extends Specification {

def "should not commit snapshot of ShallowReference Entity"() {
def "should not commit snapshot of ShallowReference Entities"() {
given:
def javers = javers().build()
def entity = new SnapshotEntity(id:1, shallowPhone:new ShallowPhone(1, "123", new CategoryC(1, "some")))
def reference = new ShallowPhone(1, "123", new CategoryC(1, "some"))
def entity = new SnapshotEntity(id:1,
shallowPhone: reference,
shallowPhones: [reference] as Set,
shallowPhonesMap: ["key": reference]
)

when:
def commit = javers.commit("", entity)

then:
commit.snapshots.each {
println it.toString()
println ".. props:"+ it.state.propertyNames
}
commit.snapshots.each { println it }
commit.snapshots.size() == 1
}

def "should not commit snapshot of ShallowReference property"() {
def "should not commit snapshot of a reference when a property has @ShallowReference annotation"() {
given:
def javers = javers().build()
def entity = new PhoneWithShallowCategory(id:1, shallowCategory:new CategoryC(1, "old shallow"))
Expand All @@ -46,13 +48,32 @@ class JaversCommitE2ETest extends Specification {
def commit = javers.commit("", entity)

then:
commit.snapshots.each {
println it.toString()
println ".. props:"+ it.state.propertyNames
}
println commit.snapshots[0]

commit.snapshots.size() == 1
}

@Unroll
def "should not commit snapshots in #collection when a property has @ShallowReference annotation" () {
given:
def javers = javers().build()

when:
def commit = javers.commit("", entity)

then:
println commit.snapshots[0]

commit.snapshots.size() == 1

where:
collection << ["Set", "Map"]
entity << [
new PhoneWithShallowCategory(id:1, shallowCategories:[new CategoryC(1, "old shallow")]),
new PhoneWithShallowCategory(id:1, shallowCategoryMap:["foo":new CategoryC(1, "old shallow")])
]
}

def "should mark changed properties"() {
given:
def javers = javers().build()
Expand Down
Expand Up @@ -5,8 +5,10 @@ import org.javers.common.exception.JaversExceptionCode
import org.javers.core.examples.typeNames.NewEntity
import org.javers.core.examples.typeNames.NewEntityWithTypeAlias
import org.javers.core.examples.typeNames.OldEntity
import org.javers.core.model.CategoryC
import org.javers.core.model.DummyAddress
import org.javers.core.model.DummyNetworkAddress
import org.javers.core.model.PhoneWithShallowCategory
import org.javers.core.model.SnapshotEntity
import org.javers.repository.jql.QueryBuilder
import spock.lang.Unroll
Expand Down Expand Up @@ -601,4 +603,49 @@ class JaversRepositoryShadowE2ETest extends JaversRepositoryE2ETest {
it.valueObjectRef instanceof DummyAddress
}
}

def "should load a shadow snapshot referencing a @ShallowReference"(){
given:
def a = new PhoneWithShallowCategory(id:1, shallowCategory:new CategoryC(1, "some"))
javers.commit("a", a)

when:
def shadows = javers.findShadows(QueryBuilder.byInstanceId(1, PhoneWithShallowCategory)
.withScopeDeepPlus().build()).collect{it.get()}

then:
shadows.size() == 1
shadows.first().shallowCategory.id == 1
shadows.first().shallowCategory.name == null
}

def "should load a shadow snapshot referencing a @ShallowReference list"(){
given:
def a = new PhoneWithShallowCategory(id:1, shallowCategories:[new CategoryC(1, "some")])
javers.commit("a", a)

when:
def shadows = javers.findShadows(QueryBuilder.byInstanceId(1, PhoneWithShallowCategory)
.withScopeDeepPlus().build()).collect{it.get()}

then:
shadows.size() == 1
shadows.first().shallowCategories.first().id == 1
shadows.first().shallowCategories.first().name == null
}

def "should load a shadow snapshot referencing a @ShallowReference map"(){
given:
def a = new PhoneWithShallowCategory(id:1, shallowCategoryMap:["foo":new CategoryC(1, "some")])
javers.commit("a", a)

when:
def shadows = javers.findShadows(QueryBuilder.byInstanceId(1, PhoneWithShallowCategory)
.withScopeDeepPlus().build()).collect{it.get()}

then:
shadows.size() == 1
shadows.first().shallowCategoryMap["foo"].id == 1L
shadows.first().shallowCategoryMap["foo"].name == null
}
}
Expand Up @@ -10,9 +10,16 @@ class PhoneWithShallowCategory {
@Id
Long id
String number = "123"
CategoryC deepCategory

@ShallowReference
CategoryC shallowCategory
CategoryC deepCategory

@ShallowReference
Set<CategoryC> shallowCategories

@ShallowReference
Map<String, CategoryC> shallowCategoryMap

@Id
Long getId() {
Expand Down
Expand Up @@ -69,6 +69,10 @@ class SnapshotEntity {

ShallowPhone shallowPhone

Set<ShallowPhone> shallowPhones

Map<String, ShallowPhone> shallowPhonesMap

Map<DummyAddress,String> mapVoToPrimitive //not supported

Map nonParametrizedMap //not supported
Expand Down