This is an automated email from the ASF dual-hosted git repository. arnold pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit b8f8217148979c3b317f93b50578c6c46ba5245c Author: Adam Saghy <[email protected]> AuthorDate: Sun Jul 24 23:13:53 2022 +0200 Add CustomAuditHandler for supporting OffsetDateTime and store in UTC --- .../core/auditing/CustomDateTimeProvider.java | 53 +++++++ .../core/auditing/JpaAuditingHandlerRegistrar.java | 34 +++++ .../infrastructure/core/config/JPAConfig.java | 3 + .../AbstractAuditableWithUTCDateTimeCustom.java | 108 +++++++++++++ .../core/domain/AuditableFieldsConstants.java | 34 +++++ .../serialization/GoogleGsonSerializerHelper.java | 3 + .../data/auditing/CustomAuditingHandler.java | 168 +++++++++++++++++++++ .../src/main/resources/application.properties | 2 + .../tenant-store/changelog-tenant-store.xml | 1 + .../0005_jdbc_connection_string.xml} | 7 +- .../core/auditing/CustomDateTimeProviderTest.java | 71 +++++++++ .../data/auditing/CustomAuditingHandlerTest.java | 119 +++++++++++++++ 12 files changed, 601 insertions(+), 2 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java new file mode 100644 index 000000000..839b18341 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProvider.java @@ -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!"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/JpaAuditingHandlerRegistrar.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/JpaAuditingHandlerRegistrar.java new file mode 100644 index 000000000..6375b271e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/auditing/JpaAuditingHandlerRegistrar.java @@ -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()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JPAConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JPAConfig.java index b9d2afefb..9af44b9d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JPAConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/JPAConfig.java @@ -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; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java new file mode 100644 index 000000000..3d308397f --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AbstractAuditableWithUTCDateTimeCustom.java @@ -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; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditableFieldsConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditableFieldsConstants.java new file mode 100644 index 000000000..f21a963a4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/domain/AuditableFieldsConstants.java @@ -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() {} +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index 87825bca6..b1af11537 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -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()); } } diff --git a/fineract-provider/src/main/java/org/springframework/data/auditing/CustomAuditingHandler.java b/fineract-provider/src/main/java/org/springframework/data/auditing/CustomAuditingHandler.java new file mode 100644 index 000000000..863ca2020 --- /dev/null +++ b/fineract-provider/src/main/java/org/springframework/data/auditing/CustomAuditingHandler.java @@ -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()); + } + } +} diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index fa64cf072..a611949f5 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -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 diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml index 8c8440243..74dd115ee 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml @@ -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> diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0005_jdbc_connection_string.xml similarity index 78% copy from fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml copy to fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0005_jdbc_connection_string.xml index 8c8440243..dc7145986 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0005_jdbc_connection_string.xml @@ -22,6 +22,9 @@ <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"> - <include file="parts/0003_reset_postgresql_sequences.xml" relativeToChangelogFile="true"/> - <include file="parts/0004_readonly_database_connection.xml" relativeToChangelogFile="true"/> + <changeSet author="fineract" id="1" context="tenant_store_db"> + <update tableName="tenant_server_connections"> + <column name="schema_connection_parameters" value="serverTimezone=UTC&useLegacyDatetimeCode=false&sessionVariables=time_zone='+00:00'"/> + </update> + </changeSet> </databaseChangeLog> diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java new file mode 100644 index 000000000..059479e7b --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomDateTimeProviderTest.java @@ -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()); + } + +} diff --git a/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java b/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java new file mode 100644 index 000000000..4abe7b77f --- /dev/null +++ b/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java @@ -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()); + } + +}
