From 08f603a9a04388b40031a768de19e21f3fc6604e Mon Sep 17 00:00:00 2001 From: dankl Date: Sat, 25 Jan 2020 20:29:44 +0100 Subject: [PATCH] Implemented restoring fints4java BankingClient's data --- .../javafx/dialogs/mainwindow/MainWindow.kt | 4 +- .../net/dankito/banking/ui/IBankingClient.kt | 2 + .../ui/presenter/MainWindowPresenter.kt | 25 ++++---- .../fints4java/android/MainActivity.kt | 5 +- .../banking/fints4javaBankingClient.kt | 41 +++++++++++++ .../banking/fints4javaBankingClientCreator.kt | 15 ++++- .../banking/mapper/fints4javaModelMapper.kt | 58 ++++++++++++++++++- .../implementierte/tan/TanMedium.kt | 3 + .../net/dankito/fints/model/AccountData.kt | 2 +- .../net/dankito/fints/model/BankData.kt | 3 + .../net/dankito/fints/model/CustomerData.kt | 6 ++ .../net/dankito/fints/model/TanProcedure.kt | 4 ++ .../fints/response/segments/JobParameters.kt | 5 +- 13 files changed, 152 insertions(+), 21 deletions(-) diff --git a/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt b/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt index b4012caa..796ae08a 100755 --- a/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt +++ b/BankingJavaFxApp/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/mainwindow/MainWindow.kt @@ -16,7 +16,9 @@ import java.io.File class MainWindow : View(messages["application.title"]) { - private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File("data/accounts.json")), Base64ServiceJava8(), RouterJavaFx()) + private val dataFolder = File("data") + + private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder, BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceJava8(), RouterJavaFx()) diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/IBankingClient.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/IBankingClient.kt index 1aaff3e2..717f9521 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/IBankingClient.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/IBankingClient.kt @@ -20,4 +20,6 @@ interface IBankingClient { fun transferMoneyAsync(data: TransferMoneyData, bankAccount: BankAccount, callback: (BankingClientResponse) -> Unit) + fun restoreData() + } \ No newline at end of file diff --git a/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt b/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt index 7aaeb602..31c957d2 100644 --- a/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt +++ b/BankingUiCommon/src/main/java/net/dankito/banking/ui/presenter/MainWindowPresenter.kt @@ -25,6 +25,7 @@ import net.dankito.utils.extensions.ofMaxLength import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.OkHttpWebClient import org.slf4j.LoggerFactory +import java.io.File import java.math.BigDecimal import java.util.* import kotlin.collections.ArrayList @@ -32,6 +33,7 @@ import kotlin.collections.ArrayList open class MainWindowPresenter( protected val bankingClientCreator: IBankingClientCreator, + protected val dataFolder: File, protected val persister: IBankingPersistence, protected val base64Service: IBase64Service, protected val router: IRouter, @@ -102,7 +104,15 @@ open class MainWindowPresenter( val bank = account.bank val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null) - val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password, webClient, base64Service, threadPool, callback) + val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password, + dataFolder, webClient, base64Service, threadPool, callback) + + try { + newClient.restoreData() + } catch (e: Exception) { + log.error("Could not deserialize account data of $account", e) + // TODO: show error message to user + } addClientForAccount(account, newClient) } @@ -123,7 +133,7 @@ open class MainWindowPresenter( // TODO: move BankInfo out of fints4javaLib open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) { - val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, webClient, base64Service, threadPool, this.callback) + val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, dataFolder, webClient, base64Service, threadPool, this.callback) newClient.addAccountAsync { response -> val account = response.account @@ -299,16 +309,7 @@ open class MainWindowPresenter( protected open fun getClientForAccount(account: Account): IBankingClient? { - clientsForAccounts.get(account)?.let { client -> - // TODO: is this code still needed after updating data model is implemented? -// account.selectedTanProcedure?.let { selectedTanProcedure -> -// client.customer.selectedTanProcedure = fints4javaModelMapper.mapTanProcedureBack(selectedTanProcedure) -// } - - return client - } - - return null + return clientsForAccounts.get(account) } diff --git a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt index 5c47ea69..27ccd852 100644 --- a/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt +++ b/fints4javaAndroidApp/src/main/java/net/dankito/banking/fints4java/android/MainActivity.kt @@ -45,7 +45,10 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File(this.filesDir, "data/accounts.json")), Base64ServiceAndroid(), RouterAndroid(this)) + val dataFolder = File(this.filesDir, "data") + + presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder, + BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceAndroid(), RouterAndroid(this)) initUi() } diff --git a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClient.kt b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClient.kt index efa73a8b..97f4f208 100644 --- a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClient.kt +++ b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClient.kt @@ -17,14 +17,18 @@ import net.dankito.fints.model.mapper.BankDataMapper import net.dankito.fints.util.IBase64Service import net.dankito.utils.IThreadPool import net.dankito.utils.ThreadPool +import net.dankito.utils.serialization.JacksonJsonSerializer import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.OkHttpWebClient +import org.slf4j.LoggerFactory +import java.io.File open class fints4javaBankingClient( bankInfo: BankInfo, customerId: String, pin: String, + protected val dataFolder: File, webClient: IWebClient = OkHttpWebClient(), base64Service: IBase64Service, threadPool: IThreadPool = ThreadPool(), @@ -32,10 +36,20 @@ open class fints4javaBankingClient( ) : IBankingClient { + companion object { + val fints4javaClientDataFilename = "fints4javaClientData.json" + + private val log = LoggerFactory.getLogger(fints4javaBankingClient::class.java) + } + + protected val mapper = net.dankito.banking.mapper.fints4javaModelMapper() protected val bankDataMapper = BankDataMapper() + protected val serializer = JacksonJsonSerializer() + + protected val bank = bankDataMapper.mapFromBankInfo(bankInfo) protected val customer = CustomerData(customerId, pin) @@ -74,6 +88,8 @@ open class fints4javaBankingClient( this.account = mapper.mapAccount(customer, bank) val mappedResponse = mapper.mapResponse(account, response) + saveData() + callback(mappedResponse) } } @@ -89,6 +105,8 @@ open class fints4javaBankingClient( val mappedResponse = mapper.mapResponse(bankAccount, response) + saveData() + callback(mappedResponse) } } @@ -104,9 +122,32 @@ open class fints4javaBankingClient( val mappedData = BankTransferData(data.creditorName, data.creditorIban, data.creditorBic, data.amount, data.usage) client.doBankTransferAsync(mappedData, account) { response -> + saveData() + callback(mapper.mapResponse(response)) } } } + + override fun restoreData() { + val deserializedCustomer = serializer.deserializeObject(getFints4javaClientDataFile(), CustomerData::class.java) + + deserializedCustomer?.let { + mapper.updateCustomer(customer, deserializedCustomer) + } + } + + protected open fun saveData() { + try { + serializer.serializeObject(customer, getFints4javaClientDataFile()) + } catch (e: Exception) { + log.error("Could not save customer data for $customer", e) + } + } + + protected open fun getFints4javaClientDataFile(): File { + return File(dataFolder, "${bank.bankCode}_${customer.customerId}_$fints4javaClientDataFilename") + } + } \ No newline at end of file diff --git a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClientCreator.kt b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClientCreator.kt index b9d79d68..6698733a 100644 --- a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClientCreator.kt +++ b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/fints4javaBankingClientCreator.kt @@ -9,14 +9,23 @@ import net.dankito.fints.model.BankInfo import net.dankito.utils.IThreadPool import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.OkHttpWebClient +import java.io.File open class fints4javaBankingClientCreator : IBankingClientCreator { - override fun createClient(bankInfo: BankInfo, customerId: String, pin: String, webClient: IWebClient, - base64Service: IBase64Service, threadPool: IThreadPool, callback: BankingClientCallback): IBankingClient { + override fun createClient( + bankInfo: BankInfo, + customerId: String, + pin: String, + dataFolder: File, + webClient: IWebClient, + base64Service: IBase64Service, + threadPool: IThreadPool, + callback: BankingClientCallback + ): IBankingClient { - return fints4javaBankingClient(bankInfo, customerId, pin, OkHttpWebClient(), UiCommonBase64ServiceWrapper(base64Service), threadPool, callback) + return fints4javaBankingClient(bankInfo, customerId, pin, dataFolder, OkHttpWebClient(), UiCommonBase64ServiceWrapper(base64Service), threadPool, callback) } } \ No newline at end of file diff --git a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/mapper/fints4javaModelMapper.kt b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/mapper/fints4javaModelMapper.kt index 510190e4..63b68b9f 100644 --- a/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/mapper/fints4javaModelMapper.kt +++ b/fints4javaBankingClient/src/main/kotlin/net/dankito/banking/mapper/fints4javaModelMapper.kt @@ -61,6 +61,10 @@ open class fints4javaModelMapper { } + open fun mapBank(bank: BankData): Bank { + return Bank(bank.name, bank.bankCode, bank.bic, bank.finTs3ServerAddress) + } + open fun mapAccount(customer: CustomerData, bank: BankData): Account { val mappedBank = mapBank(bank) @@ -73,10 +77,24 @@ open class fints4javaModelMapper { return account } - open fun mapBank(bank: BankData): Bank { - return Bank(bank.name, bank.bankCode, bank.bic, bank.finTs3ServerAddress) + // TODO: move to a fints4java internal mapper + open fun updateCustomer(customer: CustomerData, updatedCustomer: CustomerData) { + customer.pin = updatedCustomer.pin + customer.name = updatedCustomer.name + + customer.supportedTanProcedures = updatedCustomer.supportedTanProcedures + customer.selectedTanProcedure = updatedCustomer.selectedTanProcedure + customer.tanMedia = updatedCustomer.tanMedia + + customer.updVersion = updatedCustomer.updVersion + customer.selectedLanguage = updatedCustomer.selectedLanguage + customer.customerSystemId = updatedCustomer.customerSystemId + customer.customerSystemStatus = updatedCustomer.customerSystemStatus + + updateBankAccounts(customer, updatedCustomer.accounts) } + open fun mapBankAccounts(account: Account, accountData: List): List { return accountData.map { mapBankAccount(account, it) } } @@ -103,6 +121,38 @@ open class fints4javaModelMapper { } } + // TODO: move to a fints4java internal mapper + open fun updateBankAccounts(customer: CustomerData, updatedAccounts: List) { + val accounts = customer.accounts + + updatedAccounts.forEach { updatedAccount -> + val matchingExistingAccount = findMatchingBankAccount(accounts, updatedAccount) + + if (matchingExistingAccount == null) { + customer.addAccount(updatedAccount) + } + else { + updateBankAccount(matchingExistingAccount, updatedAccount) + } + } + + customer.accounts.forEach { account -> + val updatedAccount = findMatchingBankAccount(updatedAccounts, account) + + if (updatedAccount == null) { + customer.removeAccount(account) + } + } + } + + open fun updateBankAccount(account: AccountData, updatedAccount: AccountData) { + account.allowedJobs = updatedAccount.allowedJobs + + account.supportsRetrievingAccountTransactions = updatedAccount.supportsRetrievingAccountTransactions + account.supportsRetrievingBalance = updatedAccount.supportsRetrievingBalance + account.supportsTransferringMoney = updatedAccount.supportsTransferringMoney + } + open fun findAccountForBankAccount(customer: CustomerData, bankAccount: BankAccount): AccountData? { return customer.accounts.firstOrNull { bankAccount.identifier == it.accountIdentifier } } @@ -111,6 +161,10 @@ open class fints4javaModelMapper { return account.bankAccounts.firstOrNull { it.identifier == accountData.accountIdentifier } } + open fun findMatchingBankAccount(accounts: List, accountData: AccountData): AccountData? { + return accounts.firstOrNull { it.accountIdentifier == accountData.accountIdentifier } + } + open fun mapTransactions(bankAccount: BankAccount, transactions: List): List { return transactions.map { mapTransaction(bankAccount, it) } diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanMedium.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanMedium.kt index 9728674a..dc14be39 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanMedium.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/messages/datenelemente/implementierte/tan/TanMedium.kt @@ -14,6 +14,9 @@ open class TanMedium( ) { + internal constructor() : this(TanMediumKlasse.AlleMedien, TanMediumStatus.Verfuegbar) + + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/AccountData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/AccountData.kt index 87bce611..35542cbb 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/AccountData.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/AccountData.kt @@ -26,7 +26,7 @@ open class AccountData( var triedToRetrieveTransactionsOfLast90DaysWithoutTan: Boolean = false ) { - internal constructor() : this("", null, Laenderkennzeichen.Germany, "", null, "", null, null, "", null, null, listOf()) + internal constructor() : this("", null, Laenderkennzeichen.Germany, "", null, "", null, null, "", null, null, listOf()) // for object deserializers override fun toString(): String { return "$productName $accountIdentifier $accountHolderName" diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt index 0610acf1..5c58900d 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/BankData.kt @@ -29,6 +29,9 @@ open class BankData( ) { + internal constructor() : this("", 0, "", "") // for object deserializers + + open fun resetBpdVersion() { bpdVersion = BPDVersion.VersionNotReceivedYet } diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt index 8e757f94..f62353cb 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/CustomerData.kt @@ -32,6 +32,8 @@ open class CustomerData( // for Java constructor(customerId: String, pin: String) : this(customerId, pin, customerId) + internal constructor() : this("", "") // for object deserializers + val isTanProcedureSelected: Boolean get() = selectedTanProcedure != TanProcedureNotSelected @@ -50,6 +52,10 @@ open class CustomerData( (accounts as? MutableList)?.add(account) } + open fun removeAccount(account: AccountData) { + (accounts as? MutableList)?.remove(account) + } + override fun toString(): String { return customerId diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/TanProcedure.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/TanProcedure.kt index a4b25297..d8901b14 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/model/TanProcedure.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/model/TanProcedure.kt @@ -9,6 +9,10 @@ open class TanProcedure( val type: TanProcedureType ) { + + internal constructor() : this("", Sicherheitsfunktion.Einschritt_Verfahren, TanProcedureType.EnterTan) // for object deserializers + + override fun toString(): String { return "$displayName ($type, ${securityFunction.code})" } diff --git a/fints4javaLib/src/main/kotlin/net/dankito/fints/response/segments/JobParameters.kt b/fints4javaLib/src/main/kotlin/net/dankito/fints/response/segments/JobParameters.kt index 825ab0f8..f39d31f5 100644 --- a/fints4javaLib/src/main/kotlin/net/dankito/fints/response/segments/JobParameters.kt +++ b/fints4javaLib/src/main/kotlin/net/dankito/fints/response/segments/JobParameters.kt @@ -6,11 +6,14 @@ open class JobParameters( val maxCountJobs: Int, val minimumCountSignatures: Int, val securityClass: Int?, - segmentString: String + segmentString: String // TODO: when serializing / deserializing we don't care for segment string -> remove it ) : ReceivedSegment(segmentString) { + internal constructor() : this("", 0, 0, null, "0:0:0") // for object deserializers + + constructor(parameters: JobParameters) : this(parameters.jobName, parameters.maxCountJobs, parameters.minimumCountSignatures, parameters.securityClass, parameters.segmentString)