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

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

There is a newer version: 7.6.1
Show newest version
package org.javers.core;

import com.google.gson.TypeAdapter;
import org.javers.common.collections.Lists;
import org.javers.common.date.DateProvider;
import org.javers.common.date.DefaultDateProvider;
import org.javers.common.reflection.ReflectionUtil;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitFactoryModule;
import org.javers.core.diff.DiffFactoryModule;
import org.javers.core.diff.ListCompareAlgorithm;
import org.javers.core.diff.appenders.DiffAppendersModule;
import org.javers.core.diff.changetype.PropertyChange;
import org.javers.core.diff.custom.CustomPropertyComparator;
import org.javers.core.diff.custom.CustomToNativeAppenderAdapter;
import org.javers.core.diff.custom.CustomValueComparator;
import org.javers.core.graph.GraphFactoryModule;
import org.javers.core.graph.ObjectAccessHook;
import org.javers.core.graph.TailoredJaversMemberFactoryModule;
import org.javers.core.json.JsonAdvancedTypeAdapter;
import org.javers.core.json.JsonConverter;
import org.javers.core.json.JsonConverterBuilder;
import org.javers.core.json.JsonTypeAdapter;
import org.javers.core.json.typeadapter.change.ChangeTypeAdaptersModule;
import org.javers.core.json.typeadapter.commit.CommitTypeAdaptersModule;
import org.javers.core.metamodel.annotation.*;
import org.javers.core.metamodel.clazz.*;
import org.javers.core.metamodel.scanner.ScannerModule;
import org.javers.core.metamodel.type.*;
import org.javers.core.pico.AddOnsModule;
import org.javers.core.snapshot.SnapshotModule;
import org.javers.groovysupport.GroovyAddOns;
import org.javers.guava.GuavaAddOns;
import org.javers.jodasupport.JodaAddOns;
import org.javers.mongosupport.MongoLong64JsonDeserializer;
import org.javers.mongosupport.RequiredMongoSupportPredicate;
import org.javers.repository.api.JaversExtendedRepository;
import org.javers.repository.api.JaversRepository;
import org.javers.repository.inmemory.InMemoryRepositoryModule;
import org.javers.shadow.ShadowModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import static org.javers.common.reflection.ReflectionUtil.*;
import static org.javers.common.validation.Validate.argumentIsNotNull;
import static org.javers.common.validation.Validate.argumentsAreNotNull;

/**
 * Creates a JaVers instance based on your domain model metadata and custom configuration.
 * 

* * For example, to build a JaVers instance configured with reasonable defaults: *
 * Javers javers = JaversBuilder.javers().build();
 * 
* * To build a JaVers instance with Entity type registered: *
 * Javers javers = JaversBuilder.javers()
 *                              .registerEntity(MyEntity.class)
 *                              .build();
 * 
* * @see http://javers.org/documentation/domain-configuration * @author bartosz walacik */ public class JaversBuilder extends AbstractContainerBuilder { private static final Logger logger = LoggerFactory.getLogger(JaversBuilder.class); private final Map clientsClassDefinitions = new HashMap<>(); private final Map> mappedToStringFunction = new ConcurrentHashMap<>(); private final Set classesToScan = new HashSet<>(); private final Set conditionalTypesPlugins; private JaversRepository repository; private DateProvider dateProvider; private long bootStart = System.currentTimeMillis(); public static JaversBuilder javers() { return new JaversBuilder(); } /** * use static factory method {@link JaversBuilder#javers()} */ protected JaversBuilder() { logger.debug("starting up JaVers ..."); //conditional plugins conditionalTypesPlugins = new HashSet<>(); if (isClassPresent("groovy.lang.MetaClass")) { conditionalTypesPlugins.add(new GroovyAddOns()); } if (isClassPresent("org.joda.time.LocalDate")){ conditionalTypesPlugins.add(new JodaAddOns()); } if (isClassPresent("com.google.common.collect.Multimap")) { conditionalTypesPlugins.add(new GuavaAddOns()); } // bootstrap phase 1: container & core bootContainer(); addModule(new CoreJaversModule(getContainer())); } public Javers build() { Javers javers = assembleJaversInstance(); repository.ensureSchema(); long boot = System.currentTimeMillis() - bootStart; logger.info("JaVers instance started in {} ms", boot); return javers; } protected Javers assembleJaversInstance(){ // bootstrap phase 2: main modules addModule(new DiffFactoryModule()); addModule(new CommitFactoryModule(getContainer())); addModule(new SnapshotModule(getContainer())); addModule(new GraphFactoryModule(getContainer())); addModule(new DiffAppendersModule(coreConfiguration(), getContainer())); addModule(new TailoredJaversMemberFactoryModule(coreConfiguration(), getContainer())); addModule(new ScannerModule(coreConfiguration(), getContainer())); addModule(new ShadowModule(getContainer())); // bootstrap phase 3: add-on modules Set additionalTypes = bootAddOns(); // bootstrap phase 4: TypeMapper bootManagedTypeModule(); // bootstrap phase 5: JSON beans & domain aware typeAdapters additionalTypes.addAll( bootJsonConverter() ); bootDateTimeProvider(); // clases to scan & additionalTypes for (Class c : classesToScan){ typeMapper().getJaversType(c); } typeMapper().addTypes(additionalTypes); bootRepository(); return getContainerComponent(JaversCore.class); } /** * @see http://javers.org/documentation/repository-configuration */ public JaversBuilder registerJaversRepository(JaversRepository repository) { argumentsAreNotNull(repository); this.repository = repository; return this; } /** * Registers an {@link EntityType}.
* Use @Id annotation to mark exactly one Id-property. *

* * Optionally, use @Transient or @{@link DiffIgnore} annotations to mark ignored properties. *

* * For example, Entities are: Person, Document * * @see http://javers.org/documentation/domain-configuration/#entity * @see #registerEntity(EntityDefinition) */ public JaversBuilder registerEntity(Class entityClass) { argumentIsNotNull(entityClass); return registerEntity( new EntityDefinition(entityClass)); } /** * Registers a {@link ValueObjectType}.
* Optionally, use @Transient or @{@link DiffIgnore} annotations to mark ignored properties. *

* * For example, ValueObjects are: Address, Point * * @see http://javers.org/documentation/domain-configuration/#value-object * @see #registerValueObject(ValueObjectDefinition) */ public JaversBuilder registerValueObject(Class valueObjectClass) { argumentIsNotNull(valueObjectClass); registerClassDefinition(new ValueObjectDefinition(valueObjectClass)); return this; } /** * Registers an {@link EntityType}.
* Use this method if you are not willing to use {@link Entity} annotation. *

* * Recommended way to create {@link EntityDefinition} is {@link EntityDefinitionBuilder}, * for example: *
     * javersBuilder.registerEntity(
     *     EntityDefinitionBuilder.entityDefinition(Person.class)
     *     .withIdPropertyName("id")
     *     .withTypeName("Person")
     *     .withIgnoredProperties("notImportantProperty","transientProperty")
     *     .build());
     * 
* * For simple cases, you can use {@link EntityDefinition} constructors, * for example: *
     * javersBuilder.registerEntity( new EntityDefinition(Person.class, "login") );
     * 
* * @see http://javers.org/documentation/domain-configuration/#entity * @see EntityDefinitionBuilder#entityDefinition(Class) */ public JaversBuilder registerEntity(EntityDefinition entityDefinition){ argumentIsNotNull(entityDefinition); registerClassDefinition(entityDefinition); return this; } /** * Registers a {@link ValueObjectType}.
* Use this method if you are not willing to use {@link ValueObject} annotations. *

* * Recommended way to create {@link ValueObjectDefinition} is {@link ValueObjectDefinitionBuilder}. * For example: *
     * javersBuilder.registerValueObject(ValueObjectDefinitionBuilder.valueObjectDefinition(Address.class)
     *     .withIgnoredProperties(ignoredProperties)
     *     .withTypeName(typeName)
     *     .build();
     * 
* * For simple cases, you can use {@link ValueObjectDefinition} constructors, * for example: *
     * javersBuilder.registerValueObject( new ValueObjectDefinition(Address.class, "ignored") );
     * 
* * @see http://javers.org/documentation/domain-configuration/#value-object * @see ValueObjectDefinitionBuilder#valueObjectDefinition(Class) */ public JaversBuilder registerValueObject(ValueObjectDefinition valueObjectDefinition) { argumentIsNotNull(valueObjectDefinition); registerClassDefinition(valueObjectDefinition); return this; } /** * Comma separated list of packages.
* Allows you to registerClassDefinition all your classes with @{@link TypeName} annotation * in order to use them in all kinds of JQL queries
* (without getting TYPE_NAME_NOT_FOUND exception). * * @param packagesToScan e.g. "my.company.domain.person, my.company.domain.finance" * @since 2.3 */ public JaversBuilder withPackagesToScan(String packagesToScan) { if (packagesToScan == null || packagesToScan.trim().isEmpty()) { return this; } long start = System.currentTimeMillis(); logger.info("scanning package(s): {}", packagesToScan); List> scan = findClasses(TypeName.class, packagesToScan.replaceAll(" ","").split(",")); for (Class c : scan) { scanTypeName(c); } long delta = System.currentTimeMillis() - start; logger.info("found {} ManagedClasse(s) with @TypeName in {} ms", scan.size(), delta); return this; } /** * Register your class with @{@link TypeName} annotation * in order to use it in all kinds of JQL queries. *

* * You can also use {@link #withPackagesToScan(String)} * to scan all your classes. *

* * Technically, this method is the convenient alias for {@link Javers#getTypeMapping(Type)} * * @since 1.4 */ public JaversBuilder scanTypeName(Class userType){ classesToScan.add(userType); return this; } /** * Registers a simple value type (see {@link ValueType}). *

* * For example, values are: BigDecimal, LocalDateTime. *

* * Use this method if you are not willing to use {@link Value} annotation. *

* * Values are compared using default {@link Object#equals(Object)}. * If you don't want to use it, * registerClassDefinition a custom value comparator with {@link #registerValue(Class, CustomValueComparator)}. * * @see http://javers.org/documentation/domain-configuration/#ValueType */ public JaversBuilder registerValue(Class valueClass) { argumentIsNotNull(valueClass); registerClassDefinition(new ValueDefinition(valueClass)); return this; } /** * Registers a {@link ValueType} with a custom comparator to be used instead of * default {@link Object#equals(Object)}. *

* * Given comparator is used when given Value type is: *
    *
  • simple property *
  • List item *
  • Array item *
  • Map value *
* * Since this comparator is not aligned with {@link Object#hashCode()}, * it is not used when given Value type is: * *
    *
  • Map key *
  • Set item *
* * For example, BigDecimals are (by default) ValueTypes * compared using {@link java.math.BigDecimal#equals(Object)}. * If you want to compare them in the smarter way, ignoring trailing zeros: * *
     * javersBuilder.registerValue(BigDecimal.class, (a,b) -> a.compareTo(b) == 0);
     * 
* * @see http://javers.org/documentation/domain-configuration/#ValueType * @since 3.3 */ public JaversBuilder registerValue(Class valueClass, CustomValueComparator customValueComparator) { argumentsAreNotNull(valueClass, customValueComparator); if (!clientsClassDefinitions.containsKey(valueClass)){ registerClassDefinition(new ValueDefinition(valueClass)); } ValueDefinition def = getClassDefinition(valueClass); def.setCustomValueComparator(customValueComparator); return this; } /** * For complex ValueType classes that are used as Entity Id. *

* * Registers a custom toString function that will be used for creating * GlobalId for Entities, * instead of default {@link ReflectionUtil#reflectiveToString(Object)}. *

* * For example: * *
     * class Entity {
     *     @Id Point id
     *     String data
     * }
     *
     * class Point {
     *     double x
     *     double y
     *
     *     String myToString() {
     *         "("+ (int)x +"," +(int)y + ")"
     *     }
     * }
     *
     * def "should use custom toString function for complex Id"(){
     *   given:
     *     Entity entity = new Entity(
     *     id: new Point(x: 1/3, y: 4/3))
     *
     *   when: "default reflectiveToString function"
     *     def javers = JaversBuilder.javers().build()
     *     GlobalId id = javers.getTypeMapping(Entity).createIdFromInstance(entity)
     *
     *   then:
     *     id.value() == "com.mypackage.Entity/0.3333333333,1.3333333333"
     *
     *   when: "custom toString function"
     *     javers = JaversBuilder.javers()
     *             .registerValueWithCustomToString(Point, {it.myToString()}).build()
     *     id = javers.getTypeMapping(Entity).createIdFromInstance(entity)
     *
     *   then:
     *     id.value() == "com.mypackage.Entity/(0,1)"
     * }
     * 
* * For ValueType you can register both * custom toString function and CustomValueComparator. * * @param toString should return String value of a given object * @see ValueType * @see #registerValue(Class, CustomValueComparator) * @since 3.7.6 */ public JaversBuilder registerValueWithCustomToString(Class valueClass, Function toString) { argumentsAreNotNull(valueClass, toString); if (!clientsClassDefinitions.containsKey(valueClass)){ registerClassDefinition(new ValueDefinition(valueClass)); } ValueDefinition def = getClassDefinition(valueClass); def.setToStringFunction((Function)toString); return this; } /** * Marks given class as ignored by JaVers. *

* * Use this method if you are not willing to use {@link DiffIgnore} annotation. * * @see DiffIgnore */ public JaversBuilder registerIgnoredClass(Class ignoredClass) { argumentIsNotNull(ignoredClass); registerClassDefinition(new IgnoredTypeDefinition(ignoredClass)); return this; } /** * Registers a {@link ValueType} and its custom JSON adapter. *

* * Useful for not trivial ValueTypes when Gson's default representation isn't appropriate * * @see http://javers.org/documentation/repository-configuration/#json-type-adapters * @see JsonTypeAdapter */ public JaversBuilder registerValueTypeAdapter(JsonTypeAdapter typeAdapter) { for (Class c : (List)typeAdapter.getValueTypes()){ registerValue(c); } jsonConverterBuilder().registerJsonTypeAdapter(typeAdapter); return this; } /** * INCUBATING
* * For complex structures like Multimap * @since 3.1 */ public JaversBuilder registerJsonAdvancedTypeAdapter(JsonAdvancedTypeAdapter adapter) { jsonConverterBuilder().registerJsonAdvancedTypeAdapter(adapter); return this; } /** * Registers {@link ValueType} and its custom native * Gson adapter. *

* * Useful when you already have Gson {@link TypeAdapter}s implemented. * * @see TypeAdapter */ public JaversBuilder registerValueGsonTypeAdapter(Class valueType, TypeAdapter nativeAdapter) { registerValue(valueType); jsonConverterBuilder().registerNativeTypeAdapter(valueType, nativeAdapter); return this; } /** * Switch on when you need a type safe serialization for * heterogeneous collections like List, List<Object>. *

* * Heterogeneous collections are collections which contains items of different types * (or types unknown at compile time). *

* * This approach is generally discouraged, prefer statically typed collections * with exactly one type of items like List<String>. * * @see org.javers.core.json.JsonConverterBuilder#typeSafeValues(boolean) * @param typeSafeValues default false */ public JaversBuilder withTypeSafeValues(boolean typeSafeValues) { jsonConverterBuilder().typeSafeValues(typeSafeValues); return this; } /** * choose between JSON pretty or concise printing style, i.e. : * *
  • pretty: *
         * {
         *     "value": 5
         * }
         * 
    *
  • concise: *
         * {"value":5}
         * 
    *
* * @param prettyPrint default true */ public JaversBuilder withPrettyPrint(boolean prettyPrint) { jsonConverterBuilder().prettyPrint(prettyPrint); return this; } public JaversBuilder registerEntities(Class... entityClasses) { for(Class clazz : entityClasses) { registerEntity(clazz); } return this; } public JaversBuilder registerValueObjects(Class... valueObjectClasses) { for(Class clazz : valueObjectClasses) { registerValueObject(clazz); } return this; } /** * Default style is {@link MappingStyle#FIELD}. * * @see http://javers.org/documentation/domain-configuration/#property-mapping-style */ public JaversBuilder withMappingStyle(MappingStyle mappingStyle) { argumentIsNotNull(mappingStyle); coreConfiguration().withMappingStyle(mappingStyle); return this; } /** * @deprecated RANDOM CommitIdGenerator is deprecated because it don't play along with Shadow queries. Only the default algorithm (SYNCHRONIZED_SEQUENCE) is supported. * @since 2.6 */ @Deprecated public JaversBuilder withCommitIdGenerator(CommitIdGenerator commitIdGenerator) { argumentIsNotNull(commitIdGenerator); coreConfiguration().withCommitIdGenerator(commitIdGenerator); return this; } /** * When enabled, {@link Javers#compare(Object oldVersion, Object currentVersion)} * generates additional 'Snapshots' of new objects (objects added in currentVersion graph). *
* For each new object, state of its properties is captured and returned as a Set of PropertyChanges. * These Changes have null at the left side and a current property value at the right side. *

* * Disabled by default. */ public JaversBuilder withNewObjectsSnapshot(boolean newObjectsSnapshot){ coreConfiguration().withNewObjectsSnapshot(newObjectsSnapshot); return this; } public JaversBuilder withObjectAccessHook(ObjectAccessHook objectAccessHook) { removeComponent(ObjectAccessHook.class); bindComponent(ObjectAccessHook.class, objectAccessHook); return this; } /** * Registers a custom property comparator for a given Custom type. *

* * Custom comparators are used by diff algorithm to calculate property-to-property diff. *

* * Comparator has to calculate and return a subtype of {@link PropertyChange}. *

* * Internally, given type is mapped as {@link CustomType}. *

* * @param custom type * @param customType class literal to define a custom type * @see CustomType */ public JaversBuilder registerCustomComparator(CustomPropertyComparator comparator, Class customType){ registerClassDefinition(new CustomDefinition(customType)); bindComponent(comparator, new CustomToNativeAppenderAdapter(comparator, customType)); return this; } /** * Choose between two algorithms for comparing list: ListCompareAlgorithm.SIMPLE * or ListCompareAlgorithm.LEVENSHTEIN_DISTANCE. *

* Generally, we recommend using LEVENSHTEIN_DISTANCE, because it's smarter. * However, it can be slow for long lists, so SIMPLE is enabled by default. *

* * Refer to http://javers.org/documentation/diff-configuration/#list-algorithms * for description of both algorithms * * @param algorithm ListCompareAlgorithm.SIMPLE is used by default */ public JaversBuilder withListCompareAlgorithm(ListCompareAlgorithm algorithm) { argumentIsNotNull(algorithm); coreConfiguration().withListCompareAlgorithm(algorithm); return this; } /** * DateProvider providers current util for {@link Commit#getCommitDate()}. *
* By default, now() is used. *
* Overriding default dateProvider probably makes sense only in test environment. */ public JaversBuilder withDateTimeProvider(DateProvider dateProvider) { argumentIsNotNull(dateProvider); this.dateProvider = dateProvider; return this; } private void mapRegisteredClasses() { TypeMapper typeMapper = typeMapper(); for (ClientsClassDefinition def : clientsClassDefinitions.values()) { typeMapper.registerClientsClass(def); } } private TypeMapper typeMapper() { return getContainerComponent(TypeMapper.class); } private JaversCoreConfiguration coreConfiguration() { return getContainerComponent(JaversCoreConfiguration.class); } private JsonConverterBuilder jsonConverterBuilder(){ return getContainerComponent(JsonConverterBuilder.class); } private Set bootAddOns() { Set additionalTypes = new HashSet<>(); for (ConditionalTypesPlugin plugin : conditionalTypesPlugins) { logger.info("loading "+plugin.getClass().getSimpleName()+" ..."); plugin.beforeAssemble(this); additionalTypes.addAll(plugin.getNewTypes()); AddOnsModule addOnsModule = new AddOnsModule(getContainer(), (Collection)plugin.getPropertyChangeAppenders()); addModule(addOnsModule); } return additionalTypes; } private void bootManagedTypeModule() { addModule(new TypeMapperModule(getContainer())); mapRegisteredClasses(); } /** * boots JsonConverter and registers domain aware typeAdapters */ private Collection bootJsonConverter() { JsonConverterBuilder jsonConverterBuilder = jsonConverterBuilder(); addModule(new ChangeTypeAdaptersModule(getContainer())); addModule(new CommitTypeAdaptersModule(getContainer())); if (new RequiredMongoSupportPredicate().test(repository)) { jsonConverterBuilder.registerNativeGsonDeserializer(Long.class, new MongoLong64JsonDeserializer()); } jsonConverterBuilder.registerJsonTypeAdapters(getComponents(JsonTypeAdapter.class)); JsonConverter jsonConverter = jsonConverterBuilder.build(); addComponent(jsonConverter); return Lists.transform(jsonConverterBuilder.getValueTypes(), c -> new ValueType(c)); } private void bootDateTimeProvider() { if (dateProvider == null) { dateProvider = new DefaultDateProvider(); } addComponent(dateProvider); } private void bootRepository(){ if (repository == null){ logger.info("using fake InMemoryRepository, registerClassDefinition actual implementation via JaversBuilder.registerJaversRepository()"); addModule(new InMemoryRepositoryModule(getContainer())); repository = getContainerComponent(JaversRepository.class); } else { repository.setJsonConverter( getContainerComponent(JsonConverter.class)); addComponent(repository); } //JaversExtendedRepository can be created after users calls JaversBuilder.registerJaversRepository() addComponent(JaversExtendedRepository.class); } private T getClassDefinition(Class baseJavaClass) { return (T)clientsClassDefinitions.get(baseJavaClass); } private void registerClassDefinition(ClientsClassDefinition clientsClassDefinition) { argumentIsNotNull(clientsClassDefinition); clientsClassDefinitions.put(clientsClassDefinition.getBaseJavaClass(), clientsClassDefinition); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy