Add CustomAuditHandler for supporting OffsetDateTime and store in UTC

This commit is contained in:
Adam Saghy
2022-07-26 09:56:30 +02:00
committed by Arnold Gálovics
parent da920b1824
commit b8f8217148
12 changed files with 626 additions and 0 deletions
@@ -0,0 +1,53 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.auditing;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.auditing.DateTimeProvider;
public enum CustomDateTimeProvider implements DateTimeProvider {
INSTANCE, TENANT;
/*
* (non-Javadoc)
*
* @see org.springframework.data.auditing.DateTimeProvider#getNow()
*/
@NotNull
@Override
public Optional<TemporalAccessor> getNow() {
switch (this) {
case INSTANCE -> {
return Optional.of(LocalDateTime.now(ZoneId.systemDefault()));
}
case TENANT -> {
return Optional.of(OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
}
}
throw new UnsupportedOperationException(this + " is not supported!");
}
}
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.auditing;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.auditing.CustomAuditingHandler;
public class JpaAuditingHandlerRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("jpaAuditingHandler", BeanDefinitionBuilder.rootBeanDefinition(CustomAuditingHandler.class)
.addConstructorArgReference("jpaMappingContext").addConstructorArgReference("auditorAware").getBeanDefinition());
}
}
@@ -20,6 +20,7 @@
package org.apache.fineract.infrastructure.core.config;
import java.util.Map;
import org.apache.fineract.infrastructure.core.auditing.JpaAuditingHandlerRegistrar;
import org.apache.fineract.infrastructure.core.domain.AuditorAwareImpl;
import org.apache.fineract.infrastructure.core.persistence.DatabaseSelectingPersistenceUnitPostProcessor;
import org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
@@ -36,6 +37,7 @@ import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@@ -53,6 +55,7 @@ import org.springframework.transaction.support.TransactionTemplate;
@EnableJpaAuditing
@EnableJpaRepositories(basePackages = "org.apache.fineract.**.domain")
@EnableConfigurationProperties(JpaProperties.class)
@Import(JpaAuditingHandlerRegistrar.class)
public class JPAConfig extends JpaBaseConfiguration {
private final DatabaseTypeResolver databaseTypeResolver;
@@ -0,0 +1,108 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_BY_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.CREATED_DATE_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_BY_DB_FIELD;
import static org.apache.fineract.infrastructure.core.domain.AuditableFieldsConstants.LAST_MODIFIED_DATE_DB_FIELD;
import java.time.OffsetDateTime;
import java.util.Optional;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.springframework.data.domain.Auditable;
import org.springframework.data.jpa.domain.AbstractAuditable;
/**
* A custom copy of {@link AbstractAuditable} to override the column names used on database. It also uses OffsetDateTime
* for created and modified. The datetimes will be converted from tenant TZ to UTC before storing (automatically
* happens) and converted from System TZ to tenant TZ after fetching from DB
*
* Abstract base class for auditable entities. Stores the audit values in persistent fields.
*/
@MappedSuperclass
public abstract class AbstractAuditableWithUTCDateTimeCustom extends AbstractPersistableCustom
implements Auditable<Long, Long, OffsetDateTime> {
private static final long serialVersionUID = 141481953116476081L;
@Column(name = CREATED_BY_DB_FIELD, nullable = false)
private Long createdBy;
@Column(name = CREATED_DATE_DB_FIELD, nullable = false)
private OffsetDateTime createdDate;
@Column(name = LAST_MODIFIED_BY_DB_FIELD, nullable = false)
private Long lastModifiedBy;
@Column(name = LAST_MODIFIED_DATE_DB_FIELD, nullable = false)
private OffsetDateTime lastModifiedDate;
@Override
public Optional<Long> getCreatedBy() {
return Optional.ofNullable(this.createdBy);
}
@Override
public void setCreatedBy(final Long createdBy) {
this.createdBy = createdBy;
}
@Override
public Optional<OffsetDateTime> getCreatedDate() {
if (this.createdDate == null) {
return Optional.empty();
} else {
return Optional.of(this.createdDate
.withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.createdDate.toInstant())));
}
}
@Override
public void setCreatedDate(final OffsetDateTime createdDate) {
this.createdDate = createdDate;
}
@Override
public Optional<Long> getLastModifiedBy() {
return Optional.ofNullable(this.lastModifiedBy);
}
@Override
public void setLastModifiedBy(final Long lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
@Override
public Optional<OffsetDateTime> getLastModifiedDate() {
if (this.lastModifiedDate == null) {
return Optional.empty();
} else {
return Optional.of(this.lastModifiedDate
.withOffsetSameInstant(DateUtils.getDateTimeZoneOfTenant().getRules().getOffset(this.lastModifiedDate.toInstant())));
}
}
@Override
public void setLastModifiedDate(final OffsetDateTime lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
@@ -0,0 +1,34 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.domain;
public final class AuditableFieldsConstants {
public static final String CREATED_BY = "createdBy";
public static final String CREATED_DATE = "createdDate";
public static final String LAST_MODIFIED_BY = "lastModifiedBy";
public static final String LAST_MODIFIED_DATE = "lastModifiedDate";
public static final String CREATED_BY_DB_FIELD = "created_by";
public static final String CREATED_DATE_DB_FIELD = "created_on_utc";
public static final String LAST_MODIFIED_BY_DB_FIELD = "last_modified_by";
public static final String LAST_MODIFIED_DATE_DB_FIELD = "last_modified_on_utc";
private AuditableFieldsConstants() {}
}
@@ -25,6 +25,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
@@ -35,6 +36,7 @@ import org.apache.fineract.infrastructure.core.api.JodaMonthDayAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateAdapter;
import org.apache.fineract.infrastructure.core.api.LocalDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.LocalTimeAdapter;
import org.apache.fineract.infrastructure.core.api.OffsetDateTimeAdapter;
import org.apache.fineract.infrastructure.core.api.ParameterListExclusionStrategy;
import org.apache.fineract.infrastructure.core.api.ParameterListInclusionStrategy;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
@@ -115,5 +117,6 @@ public final class GoogleGsonSerializerHelper {
builder.registerTypeAdapter(ZonedDateTime.class, new JodaDateTimeAdapter());
builder.registerTypeAdapter(MonthDay.class, new JodaMonthDayAdapter());
builder.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter());
builder.registerTypeAdapter(OffsetDateTime.class, new OffsetDateTimeAdapter());
}
}
@@ -0,0 +1,168 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.springframework.data.auditing;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fineract.infrastructure.core.auditing.CustomDateTimeProvider;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.springframework.core.log.LogMessage;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.util.Assert;
/**
* Due to the package-private visibility of the Auditor, temporarely The CustomAuditingHandler must be placed in the
* same package. Later when we don't need to distinct the Auditable entities by interface anymore, it will be reworked.
*/
public class CustomAuditingHandler extends AuditingHandler {
private static final Log logger = LogFactory.getLog(CustomAuditingHandler.class);
private final AuditableBeanWrapperFactory factory;
private boolean dateTimeForNow = true;
private boolean modifyOnCreation = true;
/**
* Creates a new {@link AuditableBeanWrapper} using the given {@link PersistentEntities} when looking up auditing
* metadata via reflection.
*
* @param entities
* must not be {@literal null}.
* @since 1.10
*/
public CustomAuditingHandler(PersistentEntities entities) {
super(entities);
this.factory = new MappingAuditableBeanWrapperFactory(entities);
}
/**
* Creates a new {@link AuditableBeanWrapper} using the given {@link MappingContext} when looking up auditing
* metadata via reflection.
*
* @param mappingContext
* must not be {@literal null}.
* @since 1.8
* @deprecated use {@link AuditingHandler(PersistentEntities)} instead.
*/
public CustomAuditingHandler(MappingContext<? extends PersistentEntity<?, ?>, ? extends PersistentProperty<?>> mappingContext,
AuditorAware<?> auditorAware) {
this(PersistentEntities.of(mappingContext));
setAuditorAware(auditorAware);
}
private Optional<TemporalAccessor> touchDate(AuditableBeanWrapper<?> wrapper, boolean isNew) {
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null");
DateTimeProvider dateTimeProvider = fetchDateTimeProvider(wrapper.getBean());
Optional<TemporalAccessor> now = dateTimeProvider.getNow();
Assert.notNull(now, () -> String.format("Now must not be null Returned by: %s", dateTimeProvider.getClass()));
now.filter(__ -> isNew).ifPresent(wrapper::setCreatedDate);
now.filter(__ -> !isNew || modifyOnCreation).ifPresent(wrapper::setLastModifiedDate);
return now;
}
private DateTimeProvider fetchDateTimeProvider(Object bean) {
return bean instanceof AbstractAuditableWithUTCDateTimeCustom ? CustomDateTimeProvider.TENANT : CustomDateTimeProvider.INSTANCE;
}
/**
* Marks the given object as created.
*
* @param auditor
* can be {@literal null}.
* @param source
* must not be {@literal null}.
*/
@Override
<T> T markCreated(Auditor auditor, T source) {
Assert.notNull(source, "Source entity must not be null");
return touch(auditor, source, true);
}
/**
* Marks the given object as modified.
*
* @param auditor
* @param source
*/
@Override
<T> T markModified(Auditor auditor, T source) {
Assert.notNull(source, "Source entity must not be null");
return touch(auditor, source, false);
}
private <T> T touch(Auditor auditor, T target, boolean isNew) {
Optional<AuditableBeanWrapper<T>> wrapper = factory.getBeanWrapperFor(target);
return wrapper.map(it -> {
touchAuditor(auditor, it, isNew);
Optional<TemporalAccessor> now = dateTimeForNow ? touchDate(it, isNew) : Optional.empty();
if (logger.isDebugEnabled()) {
Object defaultedNow = now.map(Object::toString).orElse("not set");
Object defaultedAuditor = auditor.isPresent() ? auditor.toString() : "unknown";
logger.debug(LogMessage.format("Touched %s - Last modification at %s by %s", target, defaultedNow, defaultedAuditor));
}
return it.getBean();
}).orElse(target);
}
/**
* Sets modifying and creating auditor. Creating auditor is only set on new auditables.
*
* @param auditor
* @param wrapper
* @param isNew
* @return
*/
private void touchAuditor(Auditor auditor, AuditableBeanWrapper<?> wrapper, boolean isNew) {
if (!auditor.isPresent()) {
return;
}
Assert.notNull(wrapper, "AuditableBeanWrapper must not be null");
if (isNew) {
wrapper.setCreatedBy(auditor.getValue());
}
if (!isNew || modifyOnCreation) {
wrapper.setLastModifiedBy(auditor.getValue());
}
}
}
@@ -132,3 +132,5 @@ springdoc.use-management-port=${SPRINGDOC_USE_MANAGEMENT_PORT:false}
springdoc.show-actuator=${SPRINGDOC_SHOW_ACTUATOR:false}
spring.web.resources.static-locations=classpath:/static/
spring.main.allow-bean-definition-overriding=true
@@ -24,4 +24,5 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<include file="parts/0003_reset_postgresql_sequences.xml" relativeToChangelogFile="true"/>
<include file="parts/0004_readonly_database_connection.xml" relativeToChangelogFile="true"/>
<include file="parts/0005_jdbc_connection_string.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1" context="tenant_store_db">
<update tableName="tenant_server_connections">
<column name="schema_connection_parameters" value="serverTimezone=UTC&amp;useLegacyDatetimeCode=false&amp;sessionVariables=time_zone='+00:00'"/>
</update>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,71 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.infrastructure.core.auditing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAccessor;
import java.util.Optional;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class CustomDateTimeProviderTest {
@BeforeEach
public void init() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
}
@Test
public void instanceDateProvider() {
Optional<TemporalAccessor> dateTimeProvider = CustomDateTimeProvider.INSTANCE.getNow();
LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
assertTrue(dateTimeProvider.isPresent());
assertTrue(dateTimeProvider.get() instanceof LocalDateTime);
assertEquals(now.getYear(), ((LocalDateTime) dateTimeProvider.get()).getYear());
assertEquals(now.getMonth(), ((LocalDateTime) dateTimeProvider.get()).getMonth());
assertEquals(now.getDayOfMonth(), ((LocalDateTime) dateTimeProvider.get()).getDayOfMonth());
assertEquals(now.getHour(), ((LocalDateTime) dateTimeProvider.get()).getHour());
assertEquals(now.getMinute(), ((LocalDateTime) dateTimeProvider.get()).getMinute());
}
@Test
public void tenantDateProvider() {
Optional<TemporalAccessor> dateTimeProvider = CustomDateTimeProvider.TENANT.getNow();
OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
assertTrue(dateTimeProvider.isPresent());
assertTrue(dateTimeProvider.get() instanceof OffsetDateTime);
assertEquals(now.getYear(), ((OffsetDateTime) dateTimeProvider.get()).getYear());
assertEquals(now.getMonth(), ((OffsetDateTime) dateTimeProvider.get()).getMonth());
assertEquals(now.getDayOfMonth(), ((OffsetDateTime) dateTimeProvider.get()).getDayOfMonth());
assertEquals(now.getHour(), ((OffsetDateTime) dateTimeProvider.get()).getHour());
assertEquals(now.getMinute(), ((OffsetDateTime) dateTimeProvider.get()).getMinute());
}
}
@@ -0,0 +1,119 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.springframework.data.auditing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.Map;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
public class CustomAuditingHandlerTest {
@BeforeEach
public void init() {
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil
.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.of("Asia/Kolkata")))));
}
@Test
public void markCreated() {
Auditor auditor = Mockito.mock(Auditor.class);
MappingContext mappingContext = Mockito.mock(MappingContext.class);
CustomAuditingHandler testInstance = new CustomAuditingHandler(PersistentEntities.of(mappingContext));
AbstractAuditableWithUTCDateTimeCustom targetObject = Mockito.spy(AbstractAuditableWithUTCDateTimeCustom.class);
targetObject = testInstance.markCreated(auditor, targetObject);
OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
assertTrue(targetObject.getCreatedDate().isPresent());
assertEquals(now.getYear(), targetObject.getCreatedDate().get().getYear());
assertEquals(now.getMonth(), targetObject.getCreatedDate().get().getMonth());
assertEquals(now.getDayOfMonth(), targetObject.getCreatedDate().get().getDayOfMonth());
assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getCreatedDate().get().getMinute());
}
@Test
public void markModified() {
Auditor auditor = Mockito.mock(Auditor.class);
MappingContext mappingContext = Mockito.mock(MappingContext.class);
CustomAuditingHandler testInstance = new CustomAuditingHandler(PersistentEntities.of(mappingContext));
AbstractAuditableWithUTCDateTimeCustom targetObject = Mockito.spy(AbstractAuditableWithUTCDateTimeCustom.class);
targetObject = testInstance.markModified(auditor, targetObject);
OffsetDateTime now = OffsetDateTime.now(DateUtils.getDateTimeZoneOfTenant());
assertTrue(targetObject.getLastModifiedDate().isPresent());
assertEquals(now.getYear(), targetObject.getLastModifiedDate().get().getYear());
assertEquals(now.getMonth(), targetObject.getLastModifiedDate().get().getMonth());
assertEquals(now.getDayOfMonth(), targetObject.getLastModifiedDate().get().getDayOfMonth());
assertEquals(now.getHour(), targetObject.getLastModifiedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getLastModifiedDate().get().getMinute());
}
@Test
public void markModifiedOldDateTimeProvider() {
Auditor auditor = Mockito.mock(Auditor.class);
MappingContext mappingContext = Mockito.mock(MappingContext.class);
CustomAuditingHandler testInstance = new CustomAuditingHandler(PersistentEntities.of(mappingContext));
AbstractAuditableCustom targetObject = Mockito.spy(AbstractAuditableCustom.class);
targetObject = testInstance.markModified(auditor, targetObject);
LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
assertTrue(targetObject.getLastModifiedDate().isPresent());
assertEquals(now.getYear(), targetObject.getLastModifiedDate().get().getYear());
assertEquals(now.getMonth(), targetObject.getLastModifiedDate().get().getMonth());
assertEquals(now.getDayOfMonth(), targetObject.getLastModifiedDate().get().getDayOfMonth());
assertEquals(now.getHour(), targetObject.getLastModifiedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getLastModifiedDate().get().getMinute());
}
@Test
public void markCreatedOldDateTimeProvider() {
Auditor auditor = Mockito.mock(Auditor.class);
MappingContext mappingContext = Mockito.mock(MappingContext.class);
CustomAuditingHandler testInstance = new CustomAuditingHandler(PersistentEntities.of(mappingContext));
AbstractAuditableCustom targetObject = Mockito.spy(AbstractAuditableCustom.class);
targetObject = testInstance.markCreated(auditor, targetObject);
LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
assertTrue(targetObject.getCreatedDate().isPresent());
assertEquals(now.getYear(), targetObject.getCreatedDate().get().getYear());
assertEquals(now.getMonth(), targetObject.getCreatedDate().get().getMonth());
assertEquals(now.getDayOfMonth(), targetObject.getCreatedDate().get().getDayOfMonth());
assertEquals(now.getHour(), targetObject.getCreatedDate().get().getHour());
assertEquals(now.getMinute(), targetObject.getCreatedDate().get().getMinute());
}
}