All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.javers.core.Javers Maven / Gradle / Ivy

There is a newer version: 7.6.1
Show newest version
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.EntityType;
import org.javers.core.metamodel.type.JaversType;
import org.javers.core.metamodel.type.ValueObjectType;
import org.javers.repository.jql.*;
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;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Stream;


/**
 * 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); /** * Async version of {@link #commit(String, Object)} *

* * Important! Async Javers commits work seamless with MongoDB. * If you are using SQL repository — take extra care about transaction management. * * @param executor ExecutorService to be used to process commit() asynchronously. */ CompletableFuture commitAsync(String author, Object currentVersion, Executor executor); /** * Async version of {@link #commit(String, Object, Map)} *

* * @param executor ExecutorService to be used to process commit() asynchronously */ CompletableFuture commitAsync(String author, Object currentVersion, Map commitProperties, Executor executor); /** * 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 (Entity or Value Object) */ 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. *
* The domain object can be a root of an Aggregate, tree root * or any node in a domain object graph from where all other nodes are navigable. *

* * Both oldVersion and currentVersion * should be mapped to {@link EntityType} or {@link ValueObjectType}, * see * Domain model mapping. * *

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()} *

* * @param oldVersion Old version of a domain object, an instance of {@link EntityType} or * {@link ValueObjectType} * , nullable * @param currentVersion Current version of a domain object, nullable * * @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 a given object graph. * * @deprecated Use {@link Javers#compare(Object, Object)} passing null as the first parameter. */ @Deprecated 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 of Shadows, ordered in reverse chronological order. Empty if nothing found. * Remember that {@link QueryBuilder#limit(int)} * limits the number of Snapshots loaded in the base query * and not the number of returned Shadows * (one Shadow can be reconstructed from many Snapshots). * The only correct way for paging Shadows is {@link #findShadowsAndStream(JqlQuery)} * with {@link Stream#skip(long)} and {@link Stream#limit(long)}. * @param type of a domain object * @see ShadowScope * @see http://javers.org/documentation/jql-examples * @since 3.2 */ List> findShadows(JqlQuery query); /** * Streamed version of {@link #findShadows(JqlQuery)}. *

* * Using {@link Stream#skip(long)} and {@link Stream#limit(long)} * is the only correct way for paging Shadows. * * @return A stream of Shadows, ordered in reverse chronological order. Terminated stream if nothing found. * @param type of a domain object * @see #findShadows(JqlQuery) * @since 3.10 */ Stream> findShadowsAndStream(JqlQuery query); /** * Queries a JaversRepository for change history (diff sequence) of a given class, object or property.
* Returns the list of Changes.
* There are various types of changes. See {@link Change} class hierarchy.
* {@link Changes} can be easily traversed using {@link Changes#groupByCommit()} and {@link Changes#groupByObject()}. *

* * 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 of Changes ordered in reverse chronological order. * Empty if nothing found. * @see http://javers.org/documentation/jql-examples */ Changes 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); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy