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

Registering entities before referred value type fails (sometimes) #798

Closed
vr-one opened this Issue Mar 4, 2019 · 4 comments

Comments

Projects
None yet
2 participants
@vr-one
Copy link
Contributor

vr-one commented Mar 4, 2019

We want to compare two instances of a complex datastructure to identify their functional differences. During the usage of JaVers to compare those instances I found a behavior I can not fully explain to myself. It took quiet a while reducing this setup to the essential part. So let's start:

In the reproducer we have two classes Person + Person2 refering a third class Passport, which is registered as value. This way we can define a functional ID for the entities situated within the third class.

Passport sebastiansPassport = new Passport("ID-398");
Person sebastian = new Person(sebastiansPassport);

We are using Hibernate hence every class has an property "id". But we do not want to use those as ID for the entities, because those are technical IDs and not functional IDs. So we are ignoring those.

class Person {
	@DiffIgnore
	@Id
	private Integer id; 
	public Passport passport;
	
	public Person(Passport passport) {
		this.passport = passport;
	}
}

class Passport {
	@DiffIgnore
	@Id
	private Integer id; 
	private String passportId;
	
	public Passport(String passportId) {
		this.passportId = passportId;
	}
}

class Person2 {
	@DiffIgnore
	@Id
	private Integer id; 
	public Passport passport;
	
	public Person2(Passport passport) {
		this.passport = passport;
	}
}

Here is the registration of the classes for the JaversBuilder:

JaversBuilder javersBuilder = JaversBuilder.javers();
javersBuilder.withListCompareAlgorithm(ListCompareAlgorithm.LEVENSHTEIN_DISTANCE);
javersBuilder.registerEntity(new EntityDefinition(Person2.class, "passport", Arrays.asList("id")));
javersBuilder.registerEntity(new EntityDefinition(Person.class, "passport", Arrays.asList("id")));
javersBuilder.registerValueWithCustomToString(Passport.class, (passport) -> (passport.passportId));
Javers javers = javersBuilder.build();

Having two entities refering to the same value class is one essential part to reproduce the behavior. The second part, which was quiet harder to identify, is the order in which the registered classes are stored in the JaversBuilder.clientsClassDefinitions after the registration. This variable is a HashMap. The order in which the classes are stored in this map depends on the hash code of the classes. One parameter influencing the hash code is a runtime adress. So sometimes my reproducer works, sometimes it fails, depending on the hash codes of the classes.. Also switching between runtime configurations leads to different results.

To enforce a reliable behavior of the reproducer I changed the datatype of the JaversBuilder.clientsClassDefinitions to a LinkedHashMap, which stores the classes in the order they were registered.

So, what happens if all those preconditions are met? In the following lines an ENTITY_INSTANCE_WITH_NULL_ID exception is thrown:

EntityType entityType = (EntityType) javers.getTypeMapping(Person.class);
InstanceId sebastiansId = `entityType.createIdFromInstance(sebastian);

Reason is the entity Person tries to generate the requested ID using the ignored id property in the Passport class: If the Person and Person2 classes are registered before the Passport class the former infer the Passport class as entity (EntityType). When afterwards the Passport class is registered as value the type mapper state is updated, but the IdProperty in the Person classes are not. If one of the Person classes is registered before the Passport class the code above succeeds, because the code path seems to be differ within the function EntitiyType.getIdOf().

In my opinion this is an unexpected behavior and I see two ways to fix this behavior: changing the datatype of the HashMap which will make the registration of types order-dependent. Or assure infered data types are updated correctly. Can you explain this behavior?

javers-reproducer.zip

@bartoszwalacik

This comment has been minimized.

Copy link
Member

bartoszwalacik commented Mar 10, 2019

Hi @vr-one , interesting investigation, I will try to figure out what's going on in your case. Could you please push your example to your fork of this repo? See how we write test cases - https://github.com/javers/javers/tree/master/javers-core/src/test/groovy/org/javers/core/cases

vr-one added a commit to vr-one/javers that referenced this issue Mar 13, 2019

'javers#798'
adds tests as reproducer
@vr-one

This comment has been minimized.

Copy link
Contributor Author

vr-one commented Mar 13, 2019

Hi @bartoszwalacik. You find my test cases in my fork on the branch issue-798.

@bartoszwalacik

This comment has been minimized.

Copy link
Member

bartoszwalacik commented Mar 19, 2019

You have touched the serious problem in javers-core -- mutability of TypeMaper.

When Javers starts, somehow, Pasport is mapped to EntityType first (annotations inferring),
and then explicit type registration to ValueType overrides it. When this overrides happen is undefined, it could be before EntityType for Person is created or after.

I need some tome to fix it.

@bartoszwalacik

This comment has been minimized.

Copy link
Member

bartoszwalacik commented Mar 26, 2019

fix is released in 5.3.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.