Added IBankingPersistence to be able to store and deserialize added accounts and retrieved account transactions. Implemented BankingPersistenceJson not meant to be a real IBankingPersistence implementation but to get one up and running fast.

This commit is contained in:
dankl 2020-01-15 23:55:16 +01:00 committed by dankito
parent ab61064d35
commit 0c8870446e
15 changed files with 158 additions and 14 deletions

View File

@ -19,9 +19,11 @@ def title = 'Banking'
dependencies { dependencies {
api project(':BankingJavaFxControls') implementation project(':BankingJavaFxControls')
api project(':fints4javaBankingClient') implementation project(':fints4javaBankingClient')
implementation project(':BankingPersistenceJson')
implementation "ch.qos.logback:logback-classic:$logbackVersion" implementation "ch.qos.logback:logback-classic:$logbackVersion"
} }

View File

@ -2,6 +2,7 @@ package net.dankito.banking.javafx.dialogs.mainwindow
import javafx.scene.control.SplitPane import javafx.scene.control.SplitPane
import net.dankito.banking.fints4javaBankingClientCreator import net.dankito.banking.fints4javaBankingClientCreator
import net.dankito.banking.persistence.BankingPersistenceJson
import net.dankito.banking.ui.javafx.RouterJavaFx import net.dankito.banking.ui.javafx.RouterJavaFx
import net.dankito.banking.ui.javafx.controls.AccountTransactionsView import net.dankito.banking.ui.javafx.controls.AccountTransactionsView
import net.dankito.banking.ui.javafx.controls.AccountsView import net.dankito.banking.ui.javafx.controls.AccountsView
@ -10,11 +11,12 @@ import net.dankito.banking.ui.javafx.util.Base64ServiceJava8
import net.dankito.banking.ui.presenter.MainWindowPresenter import net.dankito.banking.ui.presenter.MainWindowPresenter
import tornadofx.* import tornadofx.*
import tornadofx.FX.Companion.messages import tornadofx.FX.Companion.messages
import java.io.File
class MainWindow : View(messages["application.title"]) { class MainWindow : View(messages["application.title"]) {
private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), Base64ServiceJava8(), RouterJavaFx()) private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File("data/accounts.json")), Base64ServiceJava8(), RouterJavaFx())

View File

@ -23,7 +23,7 @@ open class AccountTransactionsView(private val presenter: MainWindowPresenter) :
protected val balance = SimpleStringProperty("") protected val balance = SimpleStringProperty("")
protected val transactionsToDisplay = FXCollections.observableArrayList<AccountTransaction>(presenter.allTransactions) protected val transactionsToDisplay = FXCollections.observableArrayList<AccountTransaction>(listOf())
protected var currentMenu: ContextMenu? = null protected var currentMenu: ContextMenu? = null
@ -37,6 +37,8 @@ open class AccountTransactionsView(private val presenter: MainWindowPresenter) :
} }
transactionsFilter.addListener { _, _, newValue -> updateTransactionsToDisplay(newValue) } transactionsFilter.addListener { _, _, newValue -> updateTransactionsToDisplay(newValue) }
handleSelectedBankAccountsChanged(presenter.selectedBankAccounts) // so that isAccountSelected and transactionsToDisplay get set
} }

View File

@ -0,0 +1,12 @@
package net.dankito.banking.persistence
import net.dankito.banking.ui.model.Account
interface IBankingPersistence {
fun saveOrUpdateAccount(account: Account, allAccounts: List<Account>)
fun readPersistedAccounts(): List<Account>
}

View File

@ -1,11 +1,14 @@
package net.dankito.banking.ui.model package net.dankito.banking.ui.model
import com.fasterxml.jackson.annotation.JsonIdentityInfo
import com.fasterxml.jackson.annotation.ObjectIdGenerators
import net.dankito.banking.ui.model.tan.TanMedium import net.dankito.banking.ui.model.tan.TanMedium
import net.dankito.banking.ui.model.tan.TanMediumStatus import net.dankito.banking.ui.model.tan.TanMediumStatus
import net.dankito.banking.ui.model.tan.TanProcedure import net.dankito.banking.ui.model.tan.TanProcedure
import java.math.BigDecimal import java.math.BigDecimal
@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object
open class Account( open class Account(
val bank: Bank, val bank: Bank,
val customerId: String, val customerId: String,
@ -15,6 +18,7 @@ open class Account(
var bankAccounts: List<BankAccount> = listOf() var bankAccounts: List<BankAccount> = listOf()
) { ) {
internal constructor() : this(Bank(), "", "", "") // for object deserializers internal constructor() : this(Bank(), "", "", "") // for object deserializers

View File

@ -1,10 +1,13 @@
package net.dankito.banking.ui.model package net.dankito.banking.ui.model
import com.fasterxml.jackson.annotation.JsonIdentityInfo
import com.fasterxml.jackson.annotation.ObjectIdGenerators
import java.math.BigDecimal import java.math.BigDecimal
import java.text.DateFormat import java.text.DateFormat
import java.util.* import java.util.*
@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object
open class AccountTransaction( open class AccountTransaction(
val amount: BigDecimal, val amount: BigDecimal,
val bookingDate: Date, val bookingDate: Date,

View File

@ -1,8 +1,11 @@
package net.dankito.banking.ui.model package net.dankito.banking.ui.model
import com.fasterxml.jackson.annotation.JsonIdentityInfo
import com.fasterxml.jackson.annotation.ObjectIdGenerators
import java.math.BigDecimal import java.math.BigDecimal
@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator::class) // to avoid stack overflow due to circular references // TODO: remove again, add custom domain object
open class BankAccount @JvmOverloads constructor( open class BankAccount @JvmOverloads constructor(
val account: Account, val account: Account,
val identifier: String, val identifier: String,

View File

@ -6,6 +6,10 @@ open class TanMedium(
val status: TanMediumStatus val status: TanMediumStatus
) { ) {
internal constructor() : this("", TanMediumStatus.Available) // for object deserializers
override fun toString(): String { override fun toString(): String {
return "$displayName $status" return "$displayName $status"
} }

View File

@ -7,6 +7,10 @@ open class TanProcedure(
val bankInternalProcedureCode: String val bankInternalProcedureCode: String
) { ) {
internal constructor() : this("", TanProcedureType.EnterTan, "") // for object deserializers
override fun toString(): String { override fun toString(): String {
return "$displayName ($type, ${bankInternalProcedureCode})" return "$displayName ($type, ${bankInternalProcedureCode})"
} }

View File

@ -1,5 +1,6 @@
package net.dankito.banking.ui.presenter package net.dankito.banking.ui.presenter
import net.dankito.banking.persistence.IBankingPersistence
import net.dankito.banking.ui.BankingClientCallback import net.dankito.banking.ui.BankingClientCallback
import net.dankito.banking.ui.IBankingClient import net.dankito.banking.ui.IBankingClient
import net.dankito.banking.ui.IBankingClientCreator import net.dankito.banking.ui.IBankingClientCreator
@ -21,7 +22,9 @@ import net.dankito.fints.model.BankInfo
import net.dankito.utils.IThreadPool import net.dankito.utils.IThreadPool
import net.dankito.utils.ThreadPool import net.dankito.utils.ThreadPool
import net.dankito.utils.extensions.ofMaxLength import net.dankito.utils.extensions.ofMaxLength
import net.dankito.utils.web.client.IWebClient
import net.dankito.utils.web.client.OkHttpWebClient import net.dankito.utils.web.client.OkHttpWebClient
import org.slf4j.LoggerFactory
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -29,19 +32,22 @@ import kotlin.collections.ArrayList
open class MainWindowPresenter( open class MainWindowPresenter(
protected val bankingClientCreator: IBankingClientCreator, protected val bankingClientCreator: IBankingClientCreator,
protected val persister: IBankingPersistence,
protected val base64Service: IBase64Service, protected val base64Service: IBase64Service,
protected val router: IRouter protected val router: IRouter,
protected val webClient: IWebClient = OkHttpWebClient(),
protected val threadPool: IThreadPool = ThreadPool()
) { ) {
companion object { companion object {
protected const val OneDayMillis = 24 * 60 * 60 * 1000 protected const val OneDayMillis = 24 * 60 * 60 * 1000
private val log = LoggerFactory.getLogger(MainWindowPresenter::class.java)
} }
protected val bankFinder: BankFinder = BankFinder() protected val bankFinder: BankFinder = BankFinder()
protected val threadPool: IThreadPool = ThreadPool()
protected val clientsForAccounts = mutableMapOf<Account, IBankingClient>() protected val clientsForAccounts = mutableMapOf<Account, IBankingClient>()
@ -68,19 +74,54 @@ open class MainWindowPresenter(
} }
init {
threadPool.runAsync {
readPersistedAccounts()
}
}
protected open fun readPersistedAccounts() {
try {
val deserializedAccounts = persister.readPersistedAccounts()
deserializedAccounts.forEach { account ->
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)
addClientForAccount(account, newClient)
}
callAccountsChangedListeners()
selectedAllBankAccounts() // TODO: save last selected bank account(s)
} catch (e: Exception) {
log.error("Could not deserialize persisted accounts with persister $persister", e)
}
}
protected open fun addClientForAccount(account: Account, client: IBankingClient) {
clientsForAccounts.put(account, client)
}
// TODO: move BankInfo out of fints4javaLib // TODO: move BankInfo out of fints4javaLib
open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) { open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) {
val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, OkHttpWebClient(), base64Service, threadPool, this.callback) val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, webClient, base64Service, threadPool, this.callback)
newClient.addAccountAsync { response -> newClient.addAccountAsync { response ->
val account = response.account val account = response.account
if (response.isSuccessful) { if (response.isSuccessful) {
clientsForAccounts.put(account, newClient) addClientForAccount(account, newClient)
callAccountsChangedListeners() callAccountsChangedListeners()
persistAccount(account)
if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) { if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) {
account.bankAccounts.forEach { bankAccount -> account.bankAccounts.forEach { bankAccount ->
retrievedAccountTransactions(bankAccount, response) retrievedAccountTransactions(bankAccount, response)
@ -155,6 +196,12 @@ open class MainWindowPresenter(
response.balances.forEach { entry -> response.balances.forEach { entry ->
entry.key.balance = entry.value entry.key.balance = entry.value
} }
persistAccount(bankAccount.account)
}
protected open fun persistAccount(account: Account) {
persister.saveOrUpdateAccount(account, accounts)
} }

View File

@ -48,21 +48,23 @@ android {
} }
dependencies { dependencies {
api project(':BankingUiCommon') implementation project(':BankingUiCommon')
implementation project(':fints4javaBankingClient') implementation project(':fints4javaBankingClient')
implementation project(':BankingPersistenceJson')
implementation "com.github.clans:fab:$clansFloatingActionButtonVersion" implementation "com.github.clans:fab:$clansFloatingActionButtonVersion"
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
api "net.dankito.utils:android-utils:$androidUtilsVersion", { implementation "net.dankito.utils:android-utils:$androidUtilsVersion", {
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7' exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'design' exclude group: 'com.android.support', module: 'design'
} }
api "org.slf4j:slf4j-android:$slf4JVersion" implementation "org.slf4j:slf4j-android:$slf4JVersion"
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:design:28.0.0'

View File

@ -18,9 +18,11 @@ import kotlinx.android.synthetic.main.action_view_account_menu_item.view.*
import net.dankito.banking.fints4java.android.ui.views.MainActivityFloatingActionMenuButton import net.dankito.banking.fints4java.android.ui.views.MainActivityFloatingActionMenuButton
import net.dankito.banking.fints4java.android.util.Base64ServiceAndroid import net.dankito.banking.fints4java.android.util.Base64ServiceAndroid
import net.dankito.banking.fints4javaBankingClientCreator import net.dankito.banking.fints4javaBankingClientCreator
import net.dankito.banking.persistence.BankingPersistenceJson
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.presenter.MainWindowPresenter import net.dankito.banking.ui.presenter.MainWindowPresenter
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -37,12 +39,14 @@ class MainActivity : AppCompatActivity() {
private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton private lateinit var floatingActionMenuButton: MainActivityFloatingActionMenuButton
val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), Base64ServiceAndroid(), RouterAndroid(this)) lateinit var presenter: MainWindowPresenter
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File(this.filesDir, "data/accounts.json")), Base64ServiceAndroid(), RouterAndroid(this))
initUi() initUi()
} }

View File

@ -0,0 +1,7 @@
# BankingPersistenceJson
A simple IBankingPersistence implementation based on Json.
Not meant to be a real or useful implementation. Just there to get you up and running fast.
Preferably use another IBankingPersistence implementation, e.g. a JPA based one.

View File

@ -0,0 +1,20 @@
apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
compileKotlin {
kotlinOptions.jvmTarget = "1.6"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.6"
}
dependencies {
implementation project(':BankingUiCommon')
}

View File

@ -0,0 +1,28 @@
package net.dankito.banking.persistence
import net.dankito.banking.ui.model.Account
import net.dankito.utils.serialization.ISerializer
import net.dankito.utils.serialization.JacksonJsonSerializer
import java.io.File
open class BankingPersistenceJson(
protected val jsonFile: File,
protected val serializer: ISerializer = JacksonJsonSerializer()
) : IBankingPersistence {
init {
jsonFile.absoluteFile.parentFile.mkdirs()
}
override fun saveOrUpdateAccount(account: Account, allAccounts: List<Account>) {
serializer.serializeObject(allAccounts, jsonFile)
}
override fun readPersistedAccounts(): List<Account> {
return serializer.deserializeListOr(jsonFile, Account::class.java, listOf())
}
}