From 474d4d1f916a7d1ccb6f04a4f2f40b63162360c4 Mon Sep 17 00:00:00 2001 From: dankito Date: Thu, 6 Aug 2020 02:09:58 +0200 Subject: [PATCH] Using now MapStruct for mapping JSON entities --- build.gradle | 3 + .../json/BankingPersistenceJson/build.gradle | 5 ++ .../persistence/BankingPersistenceJson.kt | 6 +- .../persistence/mapper/CustomerConverter.kt | 90 +++++++++++++++++++ .../mapper/CycleAvoidingMappingContext.kt | 30 +++++++ .../persistence/BankingPersistenceJsonTest.kt | 2 +- 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CustomerConverter.kt create mode 100644 persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CycleAvoidingMappingContext.kt diff --git a/build.gradle b/build.gradle index ab7e34a2..fd395b2e 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ ext { textInfoExtractorVersion = "1.0.1" + mapStructVersion = "1.4.0.Beta3" + + hbci4jVersion = '3.1.37' diff --git a/persistence/json/BankingPersistenceJson/build.gradle b/persistence/json/BankingPersistenceJson/build.gradle index 67ec3f72..c9eb8ad8 100644 --- a/persistence/json/BankingPersistenceJson/build.gradle +++ b/persistence/json/BankingPersistenceJson/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'java-library' apply plugin: 'kotlin' +apply plugin: 'kotlin-kapt' sourceCompatibility = "1.7" @@ -16,6 +17,10 @@ compileTestKotlin { dependencies { implementation project(':BankingUiCommon') + implementation("org.mapstruct:mapstruct:$mapStructVersion") + + kapt("org.mapstruct:mapstruct-processor:$mapStructVersion") + testImplementation "junit:junit:$junitVersion" diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt index 1b977ebe..909a906d 100644 --- a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/BankingPersistenceJson.kt @@ -1,13 +1,13 @@ package net.dankito.banking.persistence -import net.dankito.banking.persistence.mapper.EntitiesMapper +import net.dankito.banking.persistence.mapper.CustomerConverter import net.dankito.banking.persistence.model.CustomerEntity import net.dankito.utils.multiplatform.File import net.dankito.banking.ui.model.Customer import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.BankAccount import net.dankito.banking.util.ISerializer -import net.dankito.utils.multiplatform.Month +import org.mapstruct.factory.Mappers import java.io.FileOutputStream import java.net.URL @@ -17,7 +17,7 @@ open class BankingPersistenceJson( protected val serializer: ISerializer ) : IBankingPersistence { - protected val mapper = EntitiesMapper() + protected val mapper = Mappers.getMapper(CustomerConverter::class.java) init { diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CustomerConverter.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CustomerConverter.kt new file mode 100644 index 00000000..2a1936cb --- /dev/null +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CustomerConverter.kt @@ -0,0 +1,90 @@ +package net.dankito.banking.persistence.mapper + +import net.dankito.banking.persistence.model.AccountTransactionEntity +import net.dankito.banking.persistence.model.BankAccountEntity +import net.dankito.banking.persistence.model.CustomerEntity +import net.dankito.banking.ui.model.AccountTransaction +import net.dankito.banking.ui.model.BankAccount +import net.dankito.banking.ui.model.Customer +import org.mapstruct.* + + +@Mapper +abstract class CustomerConverter { + + // Context is needed to fix cycle dependencies issue + + protected val bankAccountCustomerField = BankAccount::class.java.getDeclaredField(BankAccount::customer.name) + + + init { + bankAccountCustomerField.isAccessible = true + } + + + @Mapping(source = "technicalId", target = "id") + abstract fun mapToEntity(customer: Customer, @Context context: CycleAvoidingMappingContext): CustomerEntity + + @InheritInverseConfiguration + abstract fun mapCustomer(customer: CustomerEntity, @Context context: CycleAvoidingMappingContext): Customer + + abstract fun mapCustomers(customers: List, @Context context: CycleAvoidingMappingContext): List + + open fun mapCustomers(customers: List): List { + // create a new context instance each time as otherwise just cached instance would be taken und BankAccounts and AccountTransactions would never get updated + return mapCustomers(customers, CycleAvoidingMappingContext()) + } + + abstract fun mapCustomerEntities(customers: List, @Context context: CycleAvoidingMappingContext): List + + open fun mapCustomerEntities(customers: List): List { + // create a new context instance each time as otherwise just cached instance would be taken und BankAccounts and AccountTransactions would never get updated + return mapCustomerEntities(customers, CycleAvoidingMappingContext()) + } + + + @Mapping(source = "technicalId", target = "id") + abstract fun mapBankAccount(account: BankAccount, @Context context: CycleAvoidingMappingContext): BankAccountEntity + + @InheritInverseConfiguration + abstract fun mapBankAccount(account: BankAccountEntity, @Context context: CycleAvoidingMappingContext): BankAccount + + abstract fun mapBankAccounts(accounts: List, @Context context: CycleAvoidingMappingContext): List + + abstract fun mapBankAccountEntities(accounts: List, @Context context: CycleAvoidingMappingContext): List + + @AfterMapping + open fun mapBankAccountCustomer(serializedAccount: BankAccountEntity, @MappingTarget account: BankAccount, @Context context: CycleAvoidingMappingContext) { + val mappedCustomer = mapCustomer(serializedAccount.customer, context) + + bankAccountCustomerField.set(account, mappedCustomer) + } + + + + @Mapping(source = "technicalId", target = "id") + abstract fun mapTransaction(transaction: AccountTransaction, @Context context: CycleAvoidingMappingContext): AccountTransactionEntity + + @InheritInverseConfiguration + fun mapTransaction(transaction: AccountTransactionEntity, @Context context: CycleAvoidingMappingContext): AccountTransaction { + val account = mapBankAccount(transaction.bankAccount, context) + + val mappedTransaction = AccountTransaction(account, transaction.amount, transaction.currency, transaction.unparsedUsage, transaction.bookingDate, + transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText, + transaction.valueDate, transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance, + transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode, + transaction.compensationAmount, transaction.originalAmount, transaction.sepaUsage, transaction.deviantOriginator, transaction.deviantRecipient, + transaction.usageWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement, transaction.currencyType, transaction.bookingKey, + transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails, + transaction.transactionReferenceNumber, transaction.relatedReferenceNumber) + + mappedTransaction.technicalId = transaction.id + + return mappedTransaction + } + + abstract fun mapTransactions(transactions: List, @Context context: CycleAvoidingMappingContext): List + + abstract fun mapTransactionEntities(transactions: List, @Context context: CycleAvoidingMappingContext): List + +} \ No newline at end of file diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CycleAvoidingMappingContext.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CycleAvoidingMappingContext.kt new file mode 100644 index 00000000..fb1cdc35 --- /dev/null +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/mapper/CycleAvoidingMappingContext.kt @@ -0,0 +1,30 @@ +package net.dankito.banking.persistence.mapper + +import org.mapstruct.BeforeMapping +import org.mapstruct.MappingTarget +import org.mapstruct.TargetType +import java.util.* + + +open class CycleAvoidingMappingContext { + + private val knownInstances: MutableMap = IdentityHashMap() + + + /** + * Gets an instance out of this context if it is already mapped. + */ + @BeforeMapping + open fun getMappedInstance(source: Any, @TargetType targetType: Class): T { + return targetType.cast(knownInstances[source]) + } + + /** + * Puts an instance into the cache, so that it can be remembered to avoid endless mapping. + */ + @BeforeMapping + open fun storeMappedInstance(source: Any, @MappingTarget target: Any) { + knownInstances[source] = target + } + +} \ No newline at end of file diff --git a/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt b/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt index 9720aab9..1eb72b0c 100644 --- a/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt +++ b/persistence/json/BankingPersistenceJson/src/test/kotlin/net/dankito/banking/persistence/BankingPersistenceJsonTest.kt @@ -107,7 +107,7 @@ class BankingPersistenceJsonTest { createCustomer(2), createCustomer(3) ) - val serializableCustomers = Mappers.getMapper(CustomerConverter::class.java).mapToEntities(customers, CycleAvoidingMappingContext()) + val serializableCustomers = Mappers.getMapper(CustomerConverter::class.java).mapCustomers(customers, CycleAvoidingMappingContext()) serializer.serializeObject(serializableCustomers, file)