Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
*
* @see http://javers.org/documentation/domain-configuration
* @author bartosz walacik
*/
public class JaversBuilder extends AbstractContainerBuilder {
public static final Logger logger = LoggerFactory.getLogger(JaversBuilder.class);
private final Map clientsClassDefinitions = new LinkedHashMap<>();
private final Map> mappedToStringFunction = new ConcurrentHashMap<>();
private final Set classesToScan = new HashSet<>();
private final Set conditionalTypesPlugins;
private CoreConfigurationBuilder coreConfigurationBuilder = CoreConfigurationBuilder.coreConfiguration();
private JaversRepository repository;
private DateProvider dateProvider;
private long bootStart = System.currentTimeMillis();
private IgnoredClassesStrategy ignoredClassesStrategy;
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 pico container & core module
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(){
CoreConfiguration coreConfiguration = configurationBuilder().build();
addComponent(coreConfiguration);
// boot 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()));
addModule(new JqlModule(getContainer()));
// boot add-ons modules
Set additionalTypes = bootAddOns();
// boot TypeMapper module
addComponent(new DynamicMappingStrategy(ignoredClassesStrategy));
addModule(new TypeMapperModule(getContainer()));
// boot JSON beans & domain aware typeAdapters
bootJsonConverter();
bootDateTimeProvider();
// clases to scan & additionalTypes
for (Class c : classesToScan){
typeMapper().getJaversType(c);
}
typeMapper().addPluginTypes(additionalTypes);
mapRegisteredClasses();
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);
registerType(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:
*
*
* 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);
return registerType(entityDefinition);
}
/**
* Generic version of {@link #registerEntity(EntityDefinition)} and
* {@link #registerValueObject(ValueObjectDefinition)}
*/
public JaversBuilder registerType(ClientsClassDefinition clientsClassDefinition) {
argumentIsNotNull(clientsClassDefinition);
clientsClassDefinitions.put(clientsClassDefinition.getBaseJavaClass(), clientsClassDefinition);
return this;
}
public JaversBuilder registerTypes(Collection clientsClassDefinitions) {
argumentIsNotNull(clientsClassDefinitions);
clientsClassDefinitions.forEach(it -> registerType(it));
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:
*
*
* 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);
registerType(valueObjectDefinition);
return this;
}
/**
* Comma separated list of packages scanned by Javers in search of
* your classes with the {@link TypeName} annotation.
*
*
* It's important to declare here all of your packages containing classes with {@literal @}TypeName,
* because Javers needs live class definitions to properly deserialize Snapshots from {@link JaversRepository}.
*
*
* For example, consider this class:
*
*
* {@literal @}Entity
* {@literal @}TypeName("Person")
* class Person {
* {@literal @}Id
* private int id;
* private String name;
* }
*
*
* In the scenario when Javers reads a Snapshot of type named 'Person'
* before having a chance to map the Person class definition,
* the 'Person' type will be mapped to generic {@link UnknownType}.
*
*
* Since 5.8.4, Javers logs WARNING when UnknownType is created
* because Snapshots with UnknownType can't be properly deserialized from {@link JaversRepository}.
*
* @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 {} ManagedClass(es) 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 can't use the {@link Value} annotation.
*
*
* By default, Values are compared using {@link Object#equals(Object)}.
* You can provide external equals() function
* by registering a {@link CustomValueComparator}.
* See {@link #registerValue(Class, CustomValueComparator)}.
*
* @see http://javers.org/documentation/domain-configuration/#ValueType
*/
public JaversBuilder registerValue(Class valueClass) {
argumentIsNotNull(valueClass);
registerType(new ValueDefinition(valueClass));
return this;
}
/**
* Registers a {@link ValueType} with a custom comparator to be used instead of
* {@link Object#equals(Object)}.
*
*
* For example, by default, BigDecimals are Values
* compared using {@link java.math.BigDecimal#equals(Object)},
* sadly it isn't the correct mathematical equality:
*
*
* new BigDecimal("1.000").equals(new BigDecimal("1.00")) == false
*
*
* If you want to compare them in the right way — ignoring trailing zeros —
* register this comparator:
*
*
* JaversBuilder.javers()
* .registerValue(BigDecimal.class, new BigDecimalComparatorWithFixedEquals())
* .build();
*
*
* For example, you can register the comparator for BigDecimals with fixed equals:
*
*
* Javers javers = JaversBuilder.javers()
* .registerValue(BigDecimal.class, (a, b) -> a.compareTo(b) == 0,
* a -> a.stripTrailingZeros().toString())
* .build();
*
*
* @param Value Type
* @see #registerValue(Class, CustomValueComparator)
* @since 5.8
*/
public JaversBuilder registerValue(Class valueClass,
BiFunction equalsFunction,
Function toStringFunction) {
Validate.argumentsAreNotNull(valueClass, equalsFunction, toStringFunction);
return registerValue(valueClass, new CustomValueComparator() {
@Override
public boolean equals(T a, T b) {
return equalsFunction.apply(a,b);
}
@Override
public String toString(@Nonnull T value) {
return toStringFunction.apply(value);
}
});
}
/**
* Deprecated, use {@link #registerValue(Class, CustomValueComparator)}.
*
*
*
* Since this comparator is not aligned with {@link Object#hashCode()},
* it calculates incorrect results when a given Value is used in hashing context
* (when comparing Sets with Values or Maps with Values as keys).
*
* @see CustomValueComparator
*/
@Deprecated
public JaversBuilder registerValue(Class valueClass, BiFunction equalsFunction) {
Validate.argumentsAreNotNull(valueClass, equalsFunction);
return registerValue(valueClass, new CustomValueComparator() {
@Override
public boolean equals(T a, T b) {
return equalsFunction.apply(a,b);
}
@Override
public String toString(@Nonnull T value) {
return value.toString();
}
});
}
/**
* Deprecated, use {@link #registerValue(Class, CustomValueComparator)}.
*
* @see CustomValueComparator
* @since 3.7.6
*/
@Deprecated
public JaversBuilder registerValueWithCustomToString(Class valueClass, Function toStringFunction) {
Validate.argumentsAreNotNull(valueClass, toStringFunction);
return registerValue(valueClass, (a,b) -> Objects.equals(a,b), toStringFunction);
}
/**
* Marks given class as ignored by JaVers.
*
*
* Use this method as an alternative to the {@link DiffIgnore} annotation.
*
* @see DiffIgnore
*/
public JaversBuilder registerIgnoredClass(Class ignoredClass) {
argumentIsNotNull(ignoredClass);
registerType(new IgnoredTypeDefinition(ignoredClass));
return this;
}
/**
* A dynamic version of {@link JaversBuilder#registerIgnoredClass(Class)}.
*
* Registers a custom strategy for marking certain classes as ignored.
*
*
* For example, you can ignore classes by package naming convention:
*
*
*
* Use this method as the alternative to the {@link DiffIgnore} annotation
* or multiple calls of {@link JaversBuilder#registerIgnoredClass(Class)}.
*/
public JaversBuilder registerIgnoredClassesStrategy(IgnoredClassesStrategy ignoredClassesStrategy) {
argumentIsNotNull(ignoredClassesStrategy);
this.ignoredClassesStrategy = ignoredClassesStrategy;
return this;
}
/**
* Registers a {@link ValueType} and its custom JSON TypeAdapter.
*
*
* Useful for ValueTypes when Gson's default representation isn't good enough.
*
* @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;
}
/**
* Registers an advanced variant of custom JSON TypeAdapter.
* @see JsonAdvancedTypeAdapter
*/
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. :
*
*
* {@link CommitIdGenerator#SYNCHRONIZED_SEQUENCE} — for non-distributed applications
* {@link CommitIdGenerator#RANDOM} — for distributed applications
*
* SYNCHRONIZED_SEQUENCE is used by default.
*/
public JaversBuilder withCommitIdGenerator(CommitIdGenerator commitIdGenerator) {
configurationBuilder().withCommitIdGenerator(commitIdGenerator);
return this;
}
JaversBuilder withCustomCommitIdGenerator(Supplier commitIdGenerator) {
configurationBuilder().withCustomCommitIdGenerator(commitIdGenerator);
return this;
}
/**
* The Initial Changes switch, enabled by default since Javers 6.0.
*
*
* When the switch is enabled, {@link Javers#compare(Object oldVersion, Object currentVersion)}
* and {@link Javers#findChanges(JqlQuery)}
* generate additional set of Initial Changes for each
* property of a NewObject to capture its state.
*
* Internally, Javers generates Initial Changes by comparing a virtual, totally empty object
* with a real NewObject.
*
*
* For Primitives and Values
* an Initial Change is modeled as {@link InitialValueChange} (subtype of {@link ValueChange})
* with null on left, and a property value on right.
*
* For Collections, there are no specific subtypes to mark Initial Changes.
* So, for example, an Initial Change for a List is a regular {@link ListChange}
* with all elements from this list reflected as {@link ValueAdded}.
*
*
* In Javers Spring Boot starter you can disabled initial Value Changes in `application.yml`:
*
*
* javers:
* initialChanges: false
*
* @see NewObject
*/
public JaversBuilder withInitialChanges(boolean initialChanges){
configurationBuilder().withInitialChanges(initialChanges);
return this;
}
/**
* Use {@link #withInitialChanges(boolean)}
*/
@Deprecated
public JaversBuilder withNewObjectsSnapshot(boolean newObjectsSnapshot){
return this.withInitialChanges(newObjectsSnapshot);
}
/**
* Enabled by default since Javers 6.0.
*
*
* When the switch is enabled, {@link Javers#compare(Object oldVersion, Object currentVersion)}
* and {@link Javers#findChanges(JqlQuery)}
* generate additional set of Terminal Changes for each
* property of a Removed Object to capture its state.
*
* Internally, Javers generates Terminal Changes by comparing
* a real Removed Object with a virtual, totally empty object.
*
*
* In Javers Spring Boot starter you can disabled terminal Value Changes in `application.yml`:
*
*
* javers:
* terminalChanges: false
*
*
* @since 6.0
* @see ObjectRemoved
*/
public JaversBuilder withTerminalChanges(boolean terminalChanges){
configurationBuilder().withTerminalChanges(terminalChanges);
return this;
}
public JaversBuilder withObjectAccessHook(ObjectAccessHook objectAccessHook) {
removeComponent(ObjectAccessHook.class);
bindComponent(ObjectAccessHook.class, objectAccessHook);
return this;
}
/**
* Registers a {@link CustomPropertyComparator} for a given class and maps this class
* to {@link CustomType}.
*
*
*
* Custom Types are not easy to manage, use it as a last resort,
* only for corner cases like comparing custom Collection types.
*
*
* In most cases, it's better to customize the Javers' diff algorithm using
* much more simpler {@link CustomValueComparator},
* see {@link #registerValue(Class, CustomValueComparator)}.
*
* @param Custom Type
* @see https://javers.org/documentation/diff-configuration/#custom-comparators
*/
public JaversBuilder registerCustomType(Class customType, CustomPropertyComparator comparator){
registerType(new CustomDefinition(customType, comparator));
bindComponent(comparator, new CustomToNativeAppenderAdapter(comparator, customType));
return this;
}
/**
* @deprecated Renamed to {@link #registerCustomType(Class, CustomPropertyComparator)}
*/
@Deprecated
public JaversBuilder registerCustomComparator(CustomPropertyComparator comparator, Class customType){
return registerCustomType(customType, comparator);
}
/**
* 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);
configurationBuilder().withListCompareAlgorithm(algorithm);
return this;
}
/**
* DateProvider providers current timestamp 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;
}
public JaversBuilder withPrettyPrintDateFormats(PrettyPrintDateFormats prettyPrintDateFormats) {
configurationBuilder().withPrettyPrintDateFormats(prettyPrintDateFormats);
return this;
}
public JaversBuilder withProperties(JaversCoreProperties javersProperties) {
if (javersProperties.getMappingStyle() != null) {
withMappingStyle(MappingStyle.valueOf(javersProperties.getMappingStyle().toUpperCase()));
}
if (javersProperties.getCommitIdGenerator() != null) {
withCommitIdGenerator(CommitIdGenerator.valueOf(javersProperties.getCommitIdGenerator().toUpperCase()));
}
if (javersProperties.getPackagesToScan() != null) {
withPackagesToScan(javersProperties.getPackagesToScan());
}
if (javersProperties.isTypeSafeValues() != null) {
withTypeSafeValues(javersProperties.isTypeSafeValues());
}
if (javersProperties.getAlgorithm() != null) {
withListCompareAlgorithm(ListCompareAlgorithm.valueOf(javersProperties.getAlgorithm().toUpperCase()));
}
if (javersProperties.isPrettyPrint() != null) {
withPrettyPrint(javersProperties.isPrettyPrint());
}
if (javersProperties.isInitialChanges() != null) {
withInitialChanges(javersProperties.isInitialChanges());
}
if (javersProperties.isTerminalChanges() != null) {
withTerminalChanges(javersProperties.isTerminalChanges());
}
withPrettyPrintDateFormats(javersProperties.getPrettyPrintDateFormats());
return this;
}
private void mapRegisteredClasses() {
TypeMapper typeMapper = typeMapper();
for (ClientsClassDefinition def : clientsClassDefinitions.values()) {
typeMapper.registerClientsClass(def);
}
}
private TypeMapper typeMapper() {
return getContainerComponent(TypeMapper.class);
}
private CoreConfigurationBuilder configurationBuilder() {
return this.coreConfigurationBuilder;
}
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;
}
/**
* boots JsonConverter and registers domain aware typeAdapters
*/
private void bootJsonConverter() {
JsonConverterBuilder jsonConverterBuilder = jsonConverterBuilder();
jsonConverterBuilder.prettyPrint(coreConfiguration().isPrettyPrint());
addModule(new ChangeTypeAdaptersModule(getContainer()));
addModule(new CommitTypeAdaptersModule(getContainer()));
if (new RequiredMongoSupportPredicate().test(repository)) {
jsonConverterBuilder.registerNativeGsonDeserializer(Long.class, new MongoLong64JsonDeserializer());
}
jsonConverterBuilder.registerJsonTypeAdapters(getComponents(JsonTypeAdapter.class));
jsonConverterBuilder.registerNativeGsonDeserializer(Diff.class, new DiffTypeDeserializer());
JsonConverter jsonConverter = jsonConverterBuilder.build();
addComponent(jsonConverter);
}
private void bootDateTimeProvider() {
if (dateProvider == null) {
dateProvider = new DefaultDateProvider();
}
addComponent(dateProvider);
}
private void bootRepository(){
CoreConfiguration coreConfiguration = coreConfiguration();
if (repository == null){
logger.info("using fake InMemoryRepository, register actual Repository implementation via JaversBuilder.registerJaversRepository()");
repository = new InMemoryRepository();
}
repository.setJsonConverter( getContainerComponent(JsonConverter.class));
if (repository instanceof ConfigurationAware){
((ConfigurationAware) repository).setConfiguration(coreConfiguration);
}
bindComponent(JaversRepository.class, repository);
//JaversExtendedRepository can be created after users calls JaversBuilder.registerJaversRepository()
addComponent(JaversExtendedRepository.class);
}
private T getClassDefinition(Class baseJavaClass) {
return (T)clientsClassDefinitions.get(baseJavaClass);
}
private CoreConfiguration coreConfiguration() {
return getContainerComponent(CoreConfiguration.class);
}
}