org.javers.core.Javers 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.core;
import org.javers.core.changelog.ChangeProcessor;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitMetadata;
import org.javers.core.diff.Change;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.PropertyChange;
import org.javers.core.json.JsonConverter;
import org.javers.core.metamodel.object.CdoSnapshot;
import org.javers.core.metamodel.object.GlobalId;
import org.javers.core.metamodel.property.Property;
import org.javers.core.metamodel.type.JaversType;
import org.javers.repository.jql.GlobalIdDTO;
import org.javers.repository.jql.JqlQuery;
import org.javers.repository.jql.QueryBuilder;
import org.javers.repository.jql.ShadowScope;
import org.javers.shadow.Shadow;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Facade to JaVers instance.
* Should be constructed by {@link JaversBuilder} provided with your domain model configuration.
*
*
* For example, to deeply compare two objects
* or two arbitrary complex graphs of objects, call:
*
* Javers javers = JaversBuilder.javers().build();
* Diff diff = javers.compare(oldVersion, currentVersion);
*
*
* @see http://javers.org/documentation
* @author bartosz walacik
*/
public interface Javers {
/**
* Persists a current state of a given domain object graph
* in JaVers repository.
*
*
* JaVers applies commit() to given object and all objects navigable from it.
* You can capture a state of an arbitrary complex object graph with a single commit() call.
*
* @see http://javers.org/documentation/repository-examples
* @param author current user
* @param currentVersion standalone object or handle to an object graph
*/
Commit commit(String author, Object currentVersion);
/**
* Variant of {@link #commit(String, Object)} with commitProperties.
*
* You can pass arbitrary commit properties and
* use them in JQL to search for snapshots or changes.
*
* @see QueryBuilder#withCommitProperty(String, String)
* @param commitProperties for example ["channel":"web", "locale":"pl-PL"]
*/
Commit commit(String author, Object currentVersion, Map commitProperties);
/**
* Marks given object as deleted.
*
*
* Unlike {@link Javers#commit(String, Object)}, this method is shallow
* and affects only given object.
*
*
* This method doesn't delete anything from JaVers repository.
* It just persists 'terminal snapshot' of a given object.
*
* @param deleted object to be marked as deleted
*/
Commit commitShallowDelete(String author, Object deleted);
/**
* Variant of {@link #commitShallowDelete(String, Object)} with commitProperties.
*
*
* See {@link #commit(String, Object, Map)} for commitProperties description.
*/
Commit commitShallowDelete(String author, Object deleted, Map commitProperties);
/**
* The same like {@link #commitShallowDelete(String,Object)}
* but deleted object is selected using globalId
*/
Commit commitShallowDeleteById(String author, GlobalIdDTO globalId);
/**
* Variant of {@link #commitShallowDeleteById(String, GlobalIdDTO)} with commitProperties.
*
*
* See {@link #commit(String, Object, Map)} for commitProperties description.
*/
Commit commitShallowDeleteById(String author, GlobalIdDTO globalId, Map commitProperties);
/**
* Deep compare
*
* JaVers core function,
* deeply compares two arbitrary complex object graphs.
*
*
* To calculate a diff, just provide two versions of the
* same Domain Object (Entity or ValueObject)
* or handles to two versions of the same object graph.
*
* The handle could be a root of an Aggregate, tree root
* or any node in an Domain Object graph from where all other nodes are navigable.
*
*
* Flat collection compare
* You can also pass object collections here (List, Sets or Maps),
* but in this case, JaVers calculates flat collection diff only.
* Because it's impossible to determine type of raw collection items, JaVers maps them as Values
* and compares using {@link Object#equals(Object)}.
* So if you need to deep compare, wrap collections in some Value Objects.
*
* Misc
* compare()
function is used for ad-hoc objects comparing.
* In order to use data auditing feature, call {@link #commit(String, Object)}.
*
*
* Diffs can be converted to JSON with {@link JsonConverter#toJson(Object)} or pretty-printed with {@link Diff#prettyPrint()}
*
*
* @see
* http://javers.org/documentation/diff-examples
*/
Diff compare(Object oldVersion, Object currentVersion);
/**
* Deeply compares two top-level collections.
*
*
* Introduced due to the lack of possibility to statically
* determine type of collection items when two top-level collections are passed as references to
* {@link #compare(Object, Object)}.
*
*
* Usage example:
*
* List<Person> oldList = ...
* List<Person> newList = ...
* Diff diff = javers.compareCollections(oldList, newList, Person.class);
*
*
* @see
* Compare top-level collections example
*/
Diff compareCollections(Collection oldVersion, Collection currentVersion, Class itemClass);
/**
* Initial diff is a kind of snapshot of given domain object graph.
* Use it alongside with {@link #compare(Object, Object)}
*/
Diff initial(Object newDomainObject);
/**
* Queries JaversRepository for object Shadows.
* Shadow is a historical version of a domain object restored from a snapshot.
*
*
* For example, to get latest Shadows of "bob" Person, call:
*
* List shadows = javers.findShadows( QueryBuilder.byInstanceId("bob", Person.class)
* .limit(5).build() );
*
*
* Since Shadows are instances of your domain classes,
* you can use them directly in your application:
*
*
* assert shadows.get(0).get() instanceof Person.class;
*
*
* Query scopes
*
* By default, queries are run in the Shallow scope,
* which is the fastest one.
* To eagerly load all referenced objects use one of the wider scopes,
* Commit-deep or Deep+ :
*
*
* {@link QueryBuilder#withScopeCommitDeep()}
* {@link QueryBuilder#withScopeDeepPlus(int)}
*
*
* We recommend the Deep+ scope as a good start.
*
* Query scopes example
*
* To understand Shadow query scopes, you need to understand how JaVers commit works.
* Remember that JaVers reuses existing snapshots and creates a fresh one
* only if a given object is changed.
* The way how objects are committed affects shadow query results.
*
* For example, we have four Entities in the object graph, joined by references:
*
*
* // E1 -> E2 -> E3 -> E4
* def e4 = new Entity(id:4)
* def e3 = new Entity(id:3, ref:e4)
* def e2 = new Entity(id:2, ref:e3)
* def e1 = new Entity(id:1, ref:e2)
*
*
* In the first scenario, our four entities are committed in three commits:
*
* In Shallow scope, referenced entities are not loaded.
* But they all can be loaded using Deep+3 scope.
*
*
*given:
* javers.commit("author", e4) // commit 1.0 with e4 snapshot
* javers.commit("author", e3) // commit 2.0 with e3 snapshot
* javers.commit("author", e1) // commit 3.0 with snapshots of e1 and e2
*
*when: 'shallow scope query'
* def shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
* .build())
* def shadowE1 = shadows.get(0).get()
*
*then: 'only e1 is loaded'
* shadowE1 instanceof Entity
* shadowE1.id == 1
* shadowE1.ref == null
*
*when: 'commit-deep scope query'
* shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
* .withScopeCommitDeep().build())
* shadowE1 = shadows.get(0).get()
*
*then: 'only e1 and e2 are loaded, both was committed in commit 3.0'
* shadowE1.id == 1
* shadowE1.ref.id == 2
* shadowE1.ref.ref == null
*
*when: 'deep+1 scope query'
* shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
* .withScopeDeepPlus(1).build())
* shadowE1 = shadows.get(0).get()
*
*then: 'only e1 + e2 are loaded'
* shadowE1.id == 1
* shadowE1.ref.id == 2
* shadowE1.ref.ref == null
*
*when: 'deep+3 scope query'
* shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
* .withScopeDeepPlus(3).build())
* shadowE1 = shadows.get(0).get()
*
*then: 'all object are loaded'
* shadowE1.id == 1
* shadowE1.ref.id == 2
* shadowE1.ref.ref.id == 3
* shadowE1.ref.ref.ref.id == 4
*
*
* In the second scenario, our four entities are committed in the single commit:
*
* Shallow scope works in the same way.
* Commit-deep scope is enough to load the full graph.
*
*
*given:
* javers.commit("author", e1) //commit 1.0 with snapshots of e1, e2, e3 and e4
*
*when: 'commit-deep scope query'
* shadows = javers.findShadows(QueryBuilder.byInstanceId(1, Entity)
* .withScopeCommitDeep().build())
* shadowE1 = shadows.get(0).get()
*
*then: 'all object are loaded'
* shadowE1.id == 1
* shadowE1.ref.id == 2
* shadowE1.ref.ref.id == 3
* shadowE1.ref.ref.ref.id == 4
*
*
* Performance & Profiling
* Each Shadow query executes one or more Snapshot queries (depending on the scope)
* and complexity of an object graph.
* If you are having
* performance issues, check with debug mode how your Shadow query is executed
* and then, try to reduce the scope.
*
*
*<logger name="org.javers.JQL" level="DEBUG"/>
*
*
* Example debug for a Shadow query execution:
*
*
*DEBUG org.javers.JQL - SHALLOW query: 1 snapshots loaded (entities: 1, valueObjects: 0)
*DEBUG org.javers.JQL - DEEP_PLUS query for '...SnapshotEntity/2' at commitId 3.0, 1 snapshot(s) loaded, gaps filled so far: 1
*DEBUG org.javers.JQL - warning: object '...SnapshotEntity/3' is outside of the DEEP_PLUS+1 scope, references to this object will be nulled. Increase maxGapsToFill and fill all gaps in your object graph.
*DEBUG org.javers.JQL - queryForShadows executed:
*JqlQuery {
* IdFilter{ globalId: ...SnapshotEntity/1 }
* QueryParams{ aggregate: true, limit: 100 }
* ShadowScopeDefinition{ shadowScope: DEEP_PLUS, maxGapsToFill: 1 }
* Stats{
* executed in millis: 12
* DB queries: 2
* all snapshots: 2
* SHALLOW snapshots: 1
* DEEP_PLUS snapshots: 1
* gaps filled: 1
* gaps left!: 1
* }
*}
*
*
* Execution stats are also available in {@link JqlQuery#stats()}.
*
* @return A list ordered in reverse chronological order. Empty if nothing found.
* @param type of a domain object
* @see ShadowScope
* @see http://javers.org/documentation/jql-examples
* @since 3.2
*/
List> findShadows(JqlQuery query);
/**
* Queries JaversRepository for changes history (diff sequence) of a given class, object or property.
* There are various types of changes. See {@link Change} class hierarchy.
*
*
* Querying for Entity changes by instance Id
*
* For example, to get change history of last 5 versions of "bob" Person, call:
*
* javers.findChanges( QueryBuilder.byInstanceId("bob", Person.class).limit(5).build() );
*
*
* Last "salary" changes of "bob" Person:
*
* javers.findChanges( QueryBuilder.byInstanceId("bob", Person.class).withChangedProperty("salary").build() );
*
*
* Querying for ValueObject changes
*
* Last changes on Address ValueObject owned by "bob" Person:
*
* javers.findChanges( QueryBuilder.byValueObjectId("bob", Person.class, "address").build() );
*
*
* Last changes on Address ValueObject owned by any Person:
*
* javers.findChanges( QueryBuilder.byValueObject(Person.class, "address").build() );
*
*
* Last changes on nested ValueObject
* (when Address is a ValueObject nested in PersonDetails ValueObject):
*
* javers.findChanges( QueryBuilder.byValueObject(Person.class, "personDetails/address").build() );
*
*
* Querying for any object changes by its class
*
* Last changes on any object of MyClass.class:
*
* javers.findChanges( QueryBuilder.byClass(MyClass.class).build() );
*
*
* Last "myProperty" changes on any object of MyClass.class:
*
* javers.findChanges( QueryBuilder.byClass(Person.class).withChangedProperty("myProperty").build() );
*
*
* @return A list ordered in reverse chronological order. Empty if nothing found.
* @see http://javers.org/documentation/jql-examples
*/
List findChanges(JqlQuery query);
/**
* Queries JaversRepository for object Snapshots.
* Snapshot is a historical state of a domain object captured as the property->value Map.
*
*
* For example, to get latest Snapshots of "bob" Person, call:
*
* javers.findSnapshots( QueryBuilder.byInstanceId("bob", Person.class).limit(5).build() );
*
*
* For more query examples, see {@link #findChanges(JqlQuery)} method.
*
* Use the same JqlQuery to get changes, snapshots and shadows views.
*
* @return A list ordered in reverse chronological order. Empty if nothing found.
* @see http://javers.org/documentation/jql-examples
*/
List findSnapshots(JqlQuery query);
/**
* Latest snapshot of a given Entity instance.
*
*
* For example, to get the current state of Bob
, call:
*
* javers.getLatestSnapshot("bob", Person.class);
*
*
* Returns Optional#EMPTY if an instance is not versioned.
*/
Optional getLatestSnapshot(Object localId, Class entity);
/**
* Historical snapshot of a given Entity instance.
*
*
* For example, to get the historical state of Bob
at 2017-01-01, call:
*
* javers.getHistoricalSnapshot("bob", Person.class, LocalDateTime.of(2017,01,01));
*
*
* Returns Optional#EMPTY if an instance is not versioned.
*
* @since 3.4
*/
Optional getHistoricalSnapshot(Object localId, Class entity, LocalDateTime effectiveDate);
/**
* If you are serializing JaVers objects like
* {@link Commit}, {@link Change}, {@link Diff} or {@link CdoSnapshot} to JSON, use this JsonConverter.
*
*
* For example:
*
* javers.getJsonConverter().toJson(changes);
*
*/
JsonConverter getJsonConverter();
/**
* Generic purpose method for processing a changes list.
* After iterating over given list, returns data computed by
* {@link org.javers.core.changelog.ChangeProcessor#result()}.
*
* It's more convenient than iterating over changes on your own.
* ChangeProcessor frees you from if + inctanceof boilerplate.
*
*
* Additional features:
* - when several changes in a row refers to the same Commit, {@link ChangeProcessor#onCommit(CommitMetadata)}
* is called only for first occurrence
* - similarly, when several changes in a row affects the same object, {@link ChangeProcessor#onAffectedObject(GlobalId)}
* is called only for first occurrence
*
*
* For example, to get pretty change log, call:
*
* List<Change> changes = javers.calculateDiffs(...);
* String changeLog = javers.processChangeList(changes, new SimpleTextChangeLog());
* System.out.println( changeLog );
*
*
* @see org.javers.core.changelog.SimpleTextChangeLog
*/
T processChangeList(List changes, ChangeProcessor changeProcessor);
/**
* Use JaversTypes, if you want to:
* - describe your class in the context of JaVers domain model mapping,
* - use JaVers Reflection API to conveniently access your object properties
* (instead of awkward java.lang.reflect API).
*
*
*
* Class describe example.
* You can pretty-print JaversType of your class and
* check if mapping is correct.
*
* class Person {
* @Id int id;
* @Transient String notImportantField;
* String name;
* }
*
*
* Calling
*
* System.out.println( javers.getTypeMapping(Person.class).prettyPrint() );
*
*
* prints:
*
* EntityType{
* baseType: org.javers.core.examples.Person
* managedProperties:
* Field int id; //declared in: Person
* Field String name; //declared in: Person
* idProperty: login
* }
*
*
* Property access example.
* You can list object property values using {@link Property} abstraction.
*
* Javers javers = JaversBuilder.javers().build();
* ManagedType jType = javers.getTypeMapping(Person.class);
* Person person = new Person("bob", "Uncle Bob");
*
* System.out.println("Bob's properties:");
* for (Property p : jType.getPropertyNames()){
* Object value = p.get(person);
* System.out.println( "property:" + p.getName() + ", value:" + value );
* }
*
*
* prints:
*
* Bob's properties:
* property:login, value:bob
* property:name, value:Uncle Bob
*
*/
T getTypeMapping(Type userType);
/**
* Returns {@link Property} which underlies given {@link PropertyChange}
*
* @since 1.4.1
*/
Property getProperty(PropertyChange propertyChange);
}