Using now MapStruct for mapping JSON entities

This commit is contained in:
dankito 2020-08-06 02:09:58 +02:00
parent 82485a29b1
commit 474d4d1f91
6 changed files with 132 additions and 4 deletions

View File

@ -24,6 +24,9 @@ ext {
textInfoExtractorVersion = "1.0.1"
mapStructVersion = "1.4.0.Beta3"
hbci4jVersion = '3.1.37'

View File

@ -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"

View File

@ -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 {

View File

@ -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<Customer>, @Context context: CycleAvoidingMappingContext): List<CustomerEntity>
open fun mapCustomers(customers: List<Customer>): List<CustomerEntity> {
// 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<CustomerEntity>, @Context context: CycleAvoidingMappingContext): List<Customer>
open fun mapCustomerEntities(customers: List<CustomerEntity>): List<Customer> {
// 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<BankAccount>, @Context context: CycleAvoidingMappingContext): List<BankAccountEntity>
abstract fun mapBankAccountEntities(accounts: List<BankAccountEntity>, @Context context: CycleAvoidingMappingContext): List<BankAccount>
@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<AccountTransaction>, @Context context: CycleAvoidingMappingContext): List<AccountTransactionEntity>
abstract fun mapTransactionEntities(transactions: List<AccountTransactionEntity>, @Context context: CycleAvoidingMappingContext): List<AccountTransaction>
}

View File

@ -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<Any, Any> = IdentityHashMap()
/**
* Gets an instance out of this context if it is already mapped.
*/
@BeforeMapping
open fun <T> getMappedInstance(source: Any, @TargetType targetType: Class<T>): 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
}
}

View File

@ -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)