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" textInfoExtractorVersion = "1.0.1"
mapStructVersion = "1.4.0.Beta3"
hbci4jVersion = '3.1.37' hbci4jVersion = '3.1.37'

View File

@ -1,5 +1,6 @@
apply plugin: 'java-library' apply plugin: 'java-library'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
sourceCompatibility = "1.7" sourceCompatibility = "1.7"
@ -16,6 +17,10 @@ compileTestKotlin {
dependencies { dependencies {
implementation project(':BankingUiCommon') implementation project(':BankingUiCommon')
implementation("org.mapstruct:mapstruct:$mapStructVersion")
kapt("org.mapstruct:mapstruct-processor:$mapStructVersion")
testImplementation "junit:junit:$junitVersion" testImplementation "junit:junit:$junitVersion"

View File

@ -1,13 +1,13 @@
package net.dankito.banking.persistence 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.banking.persistence.model.CustomerEntity
import net.dankito.utils.multiplatform.File import net.dankito.utils.multiplatform.File
import net.dankito.banking.ui.model.Customer import net.dankito.banking.ui.model.Customer
import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.BankAccount import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.util.ISerializer import net.dankito.banking.util.ISerializer
import net.dankito.utils.multiplatform.Month import org.mapstruct.factory.Mappers
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URL import java.net.URL
@ -17,7 +17,7 @@ open class BankingPersistenceJson(
protected val serializer: ISerializer protected val serializer: ISerializer
) : IBankingPersistence { ) : IBankingPersistence {
protected val mapper = EntitiesMapper() protected val mapper = Mappers.getMapper(CustomerConverter::class.java)
init { 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(2),
createCustomer(3) 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) serializer.serializeObject(serializableCustomers, file)