Pārlūkot izejas kodu

теперь почти всё сохраняется в монгу, кроме очереди

κρμγ 5 gadi atpakaļ
vecāks
revīzija
756b2c8320
21 mainītis faili ar 748 papildinājumiem un 78 dzēšanām
  1. 1 0
      db/2020-05/indexes.js
  2. 14 5
      src/main/java/inn/ocsf/bee/freigeld/core/model/Bank.java
  3. 2 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/BankAccount.java
  4. 2 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/NaturalPerson.java
  5. 10 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/PersonIdentity.java
  6. 5 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/PersonType.java
  7. 130 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/data/BankAccountData.java
  8. 60 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/data/CoinAccountLink.java
  9. 119 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/data/PersonData.java
  10. 12 0
      src/main/java/inn/ocsf/bee/freigeld/core/model/data/PersonDataPrj0.java
  11. 9 0
      src/main/java/inn/ocsf/bee/freigeld/core/repo/BankAccountRepository.java
  12. 20 0
      src/main/java/inn/ocsf/bee/freigeld/core/repo/CoinAccountLinkRepository.java
  13. 5 0
      src/main/java/inn/ocsf/bee/freigeld/core/repo/CoinRepository.java
  14. 18 0
      src/main/java/inn/ocsf/bee/freigeld/core/repo/PersonRepository.java
  15. 176 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/data/CentralBankAccountLevel.kt
  16. 6 2
      src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalEmitterImpl.kt
  17. 67 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalWorldImpl.kt
  18. 60 71
      src/main/kotlin/inn/ocsf/bee/freigeld/core/demo/DemoInMem.kt
  19. 3 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/model/CentralBank.kt
  20. 18 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/model/GlobalWorld.kt
  21. 11 0
      src/main/kotlin/inn/ocsf/bee/freigeld/core/model/PersonIdentityString.kt

+ 1 - 0
db/2020-05/indexes.js

@@ -0,0 +1 @@
+db.getCollection("coin-to-account-link").createIndex({coinId: 1, accountId: 1}, {unique: true})

+ 14 - 5
src/main/java/inn/ocsf/bee/freigeld/core/model/Bank.java

@@ -1,18 +1,27 @@
 package inn.ocsf.bee.freigeld.core.model;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.util.Collection;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
 
 public interface Bank extends PrivatePerson {
 
-    boolean hasAccount(UUID account);
+    boolean hasAccount(@NotNull UUID account);
+
+    boolean hasPerson(@NotNull UUID person);
 
-    boolean hasPerson(UUID person);
+    @NotNull
+    BankAccount addAccount(@NotNull UUID person);
 
-    BankAccount addAccount(UUID person);
+    @NotNull
+    Collection<BankAccount> getAccounts(@NotNull UUID person);
 
-    Collection<BankAccount> getAccounts(UUID person);
+    @NotNull
+    CompletableFuture<Object> exchange(@NotNull BankAccount to, BankAccount from, @NotNull Long amount);
 
-    CompletableFuture<Object> exchange(BankAccount to, BankAccount from, Long amount);
+    @NotNull
+    Stream<BankAccount> getAccounts();
 }

+ 2 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/BankAccount.java

@@ -11,6 +11,8 @@ public interface BankAccount {
 
     UUID getId();
 
+    UUID getOwnerId();
+
     Long getOverall();
 
     @Nullable

+ 2 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/NaturalPerson.java

@@ -1,4 +1,6 @@
 package inn.ocsf.bee.freigeld.core.model;
 
 public interface NaturalPerson extends Person {
+
+    String getFullName();
 }

+ 10 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/PersonIdentity.java

@@ -0,0 +1,10 @@
+package inn.ocsf.bee.freigeld.core.model;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface PersonIdentity {
+
+    @NotNull
+    String getKey();
+
+}

+ 5 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/PersonType.java

@@ -0,0 +1,5 @@
+package inn.ocsf.bee.freigeld.core.model;
+
+public enum PersonType {
+    natural, privotal
+}

+ 130 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/data/BankAccountData.java

@@ -0,0 +1,130 @@
+package inn.ocsf.bee.freigeld.core.model.data;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import inn.ocsf.bee.freigeld.core.model.BankAccount;
+import inn.ocsf.bee.freigeld.core.model.Coin;
+import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.annotation.Version;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Collection;
+import java.util.UUID;
+
+@Document("account")
+public class BankAccountData {
+
+    @Id
+    private ObjectId id;
+
+    @Version
+    private Long version;
+
+    private BankAccount account;
+
+    private UUID bankId;
+
+    public UUID getBankId() {
+        return bankId;
+    }
+
+    public void setBankId(UUID bankId) {
+        this.bankId = bankId;
+    }
+
+    public ObjectId getId() {
+        return id;
+    }
+
+    public void setId(ObjectId id) {
+        this.id = id;
+    }
+
+    public BankAccount getAccount() {
+        return account;
+    }
+
+    public void setAccount(BankAccount account) {
+        this.account = account;
+    }
+
+    public static class BankAccountDefault implements BankAccount {
+
+        private UUID id;
+        private UUID ownerId;
+        private Long overall;
+        @Transient
+        @JsonIgnore
+        private BankAccount transientDelegate;
+
+        public BankAccountDefault(UUID id, UUID ownerId) {
+            this.id = id;
+            this.ownerId = ownerId;
+            this.overall = 0L;
+        }
+
+        public BankAccountDefault() {
+        }
+
+        @Override
+        public UUID getId() {
+            return id;
+        }
+
+        public void setId(UUID id) {
+            this.id = id;
+        }
+
+        @Override
+        public UUID getOwnerId() {
+            return ownerId;
+        }
+
+        public void setOwnerId(UUID ownerId) {
+            this.ownerId = ownerId;
+        }
+
+        public BankAccount getTransientDelegate() {
+            return transientDelegate;
+        }
+
+        public void setTransientDelegate(BankAccount transientDelegate) {
+            this.transientDelegate = transientDelegate;
+        }
+
+        @Override
+        public Long getOverall() {
+            this.overall = transientDelegate.getOverall();
+            return this.overall;
+        }
+
+        public void setOverall(Long overall) {
+            this.overall = overall;
+        }
+
+        @Nullable
+        @Override
+        public Collection<Coin> getCoins() {
+            return transientDelegate.getCoins();
+        }
+
+        @Nullable
+        @Override
+        public Coin extractOne(@NotNull UUID coinId) {
+            return transientDelegate.extractOne(coinId);
+        }
+
+        @Override
+        public void acceptOne(@NotNull Coin coin) {
+            transientDelegate.acceptOne(coin);
+        }
+
+        @Override
+        public String toString() {
+            return id.toString();
+        }
+    }
+}

+ 60 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/data/CoinAccountLink.java

@@ -0,0 +1,60 @@
+package inn.ocsf.bee.freigeld.core.model.data;
+
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Version;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.Date;
+import java.util.UUID;
+
+@Document("coin-to-account-link")
+public class CoinAccountLink {
+
+    @Id
+    private ObjectId id;
+
+    private UUID coinId;
+
+    private UUID accountId;
+
+    @Version
+    private Long version;
+
+    private Date ts;
+
+    public CoinAccountLink() {
+    }
+
+    public ObjectId getId() {
+        return id;
+    }
+
+    public void setId(ObjectId id) {
+        this.id = id;
+    }
+
+    public UUID getCoinId() {
+        return coinId;
+    }
+
+    public void setCoinId(UUID coinId) {
+        this.coinId = coinId;
+    }
+
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(UUID accountId) {
+        this.accountId = accountId;
+    }
+
+    public Date getTs() {
+        return ts;
+    }
+
+    public void setTs(Date ts) {
+        this.ts = ts;
+    }
+}

+ 119 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/data/PersonData.java

@@ -0,0 +1,119 @@
+package inn.ocsf.bee.freigeld.core.model.data;
+
+import com.querydsl.core.annotations.QueryEntity;
+import inn.ocsf.bee.freigeld.core.model.*;
+import org.bson.types.ObjectId;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.Version;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import java.util.*;
+
+@Document("person")
+public class PersonData {
+
+    @Id
+    private ObjectId id;
+
+    @Version
+    private Long version;
+
+    private Person person;
+
+    private Map<String, PersonIdentity> identities = new HashMap<>();
+
+    private List<String> identitySet = new ArrayList<>();
+
+    private PersonType personType;
+
+    public ObjectId getId() {
+        return id;
+    }
+
+    public void setId(ObjectId id) {
+        this.id = id;
+    }
+
+    public Person getPerson() {
+        return person;
+    }
+
+    public void setPerson(Person person) {
+        this.person = person;
+    }
+
+    public Map<String, PersonIdentity> getIdentities() {
+        return identities;
+    }
+
+    public void setIdentities(Map<String, PersonIdentity> identities) {
+        this.identities = identities;
+    }
+
+    public List<String> getIdentitySet() {
+        return identitySet;
+    }
+
+    public void setIdentitySet(List<String> identitySet) {
+        this.identitySet = identitySet;
+    }
+
+    public PersonType getPersonType() {
+        return personType;
+    }
+
+    public void setPersonType(PersonType personType) {
+        this.personType = personType;
+    }
+
+    @QueryEntity
+    public static class NaturalPersonImpl implements NaturalPerson {
+
+        private String fullName;
+        private UUID id;
+
+        public NaturalPersonImpl() {
+        }
+
+        public NaturalPersonImpl(String fullName) {
+            this.fullName = fullName;
+            this.id = UUID.randomUUID();
+        }
+
+        @Override
+        public UUID getId() {
+            return id;
+        }
+
+        public void setId(UUID id) {
+            this.id = id;
+        }
+
+        @Override
+        public String getFullName() {
+            return fullName;
+        }
+
+        public void setFullName(String fullName) {
+            this.fullName = fullName;
+        }
+    }
+
+    @QueryEntity
+    public static class BankPrivate implements PrivatePerson {
+
+        private UUID id;
+
+        public BankPrivate() {
+        }
+
+        public BankPrivate(UUID id) {
+            this.id = id;
+        }
+
+        @Override
+        public UUID getId() {
+            return id;
+        }
+    }
+}

+ 12 - 0
src/main/java/inn/ocsf/bee/freigeld/core/model/data/PersonDataPrj0.java

@@ -0,0 +1,12 @@
+package inn.ocsf.bee.freigeld.core.model.data;
+
+import inn.ocsf.bee.freigeld.core.model.Person;
+import org.bson.types.ObjectId;
+
+public interface PersonDataPrj0 {
+
+    ObjectId getId();
+
+    Person getPerson();
+
+}

+ 9 - 0
src/main/java/inn/ocsf/bee/freigeld/core/repo/BankAccountRepository.java

@@ -0,0 +1,9 @@
+package inn.ocsf.bee.freigeld.core.repo;
+
+import inn.ocsf.bee.freigeld.core.model.data.BankAccountData;
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+
+public interface BankAccountRepository extends MongoRepository<BankAccountData, ObjectId>, QuerydslPredicateExecutor<BankAccountData> {
+}

+ 20 - 0
src/main/java/inn/ocsf/bee/freigeld/core/repo/CoinAccountLinkRepository.java

@@ -0,0 +1,20 @@
+package inn.ocsf.bee.freigeld.core.repo;
+
+import inn.ocsf.bee.freigeld.core.model.data.CoinAccountLink;
+import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface CoinAccountLinkRepository extends MongoRepository<CoinAccountLink, ObjectId>, QuerydslPredicateExecutor<CoinAccountLink> {
+
+    @NotNull
+    List<CoinAccountLink> findAllByAccountId(@NotNull UUID id);
+
+    @NotNull
+    Optional<CoinAccountLink> findOneByAccountIdAndCoinId(@NotNull UUID accountId, @NotNull UUID coinId);
+}

+ 5 - 0
src/main/java/inn/ocsf/bee/freigeld/core/repo/CoinRepository.java

@@ -3,9 +3,11 @@ package inn.ocsf.bee.freigeld.core.repo;
 import inn.ocsf.bee.freigeld.core.model.data.CoinData;
 import inn.ocsf.bee.freigeld.core.model.data.CoinStatus;
 import org.bson.types.ObjectId;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.data.mongodb.repository.MongoRepository;
 import org.springframework.data.querydsl.QuerydslPredicateExecutor;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
 import java.util.stream.Stream;
@@ -15,4 +17,7 @@ public interface CoinRepository extends MongoRepository<CoinData, ObjectId>, Que
     Optional<CoinData> findOneByCoinId(UUID coin_id);
 
     Stream<CoinData> findAllByStatus(CoinStatus status);
+
+    @NotNull
+    List<CoinData> findAllByCoinIdIn(@NotNull List<UUID> ids);
 }

+ 18 - 0
src/main/java/inn/ocsf/bee/freigeld/core/repo/PersonRepository.java

@@ -0,0 +1,18 @@
+package inn.ocsf.bee.freigeld.core.repo;
+
+import inn.ocsf.bee.freigeld.core.model.data.PersonData;
+import inn.ocsf.bee.freigeld.core.model.data.PersonDataPrj0;
+import org.bson.types.ObjectId;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+public interface PersonRepository extends MongoRepository<PersonData, ObjectId>, QuerydslPredicateExecutor<PersonData> {
+
+    Optional<PersonData> findOneByPersonId(UUID person_id);
+
+    Stream<PersonDataPrj0> findAllBy();
+}

+ 176 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/data/CentralBankAccountLevel.kt

@@ -0,0 +1,176 @@
+package inn.ocsf.bee.freigeld.core.data
+
+import com.querydsl.core.types.dsl.Expressions
+import inn.ocsf.bee.freigeld.core.model.BankAccount
+import inn.ocsf.bee.freigeld.core.model.CentralBank
+import inn.ocsf.bee.freigeld.core.model.Coin
+import inn.ocsf.bee.freigeld.core.model.GlobalWorld
+import inn.ocsf.bee.freigeld.core.model.data.BankAccountData
+import inn.ocsf.bee.freigeld.core.model.data.CoinAccountLink
+import inn.ocsf.bee.freigeld.core.model.data.PersonData
+import inn.ocsf.bee.freigeld.core.model.data.QBankAccountData.bankAccountData
+import inn.ocsf.bee.freigeld.core.repo.BankAccountRepository
+import inn.ocsf.bee.freigeld.core.repo.CoinAccountLinkRepository
+import inn.ocsf.bee.freigeld.core.repo.CoinRepository
+import org.slf4j.LoggerFactory
+import java.util.*
+import java.util.stream.Stream
+import java.util.stream.StreamSupport
+import javax.annotation.PostConstruct
+import javax.inject.Inject
+
+abstract class CentralBankAccountLevel : CentralBank {
+
+    @Inject
+    private lateinit var world: GlobalWorld
+
+    @Inject
+    private lateinit var accountRepo: BankAccountRepository
+
+    @Inject
+    private lateinit var coinRepo: CoinRepository
+
+    @Inject
+    private lateinit var linkRepo: CoinAccountLinkRepository
+
+    protected val selfId = UUID.fromString("a8486777-834c-4267-9f10-69f03714c523")
+
+    private val log = LoggerFactory.getLogger(javaClass)
+
+    private fun delegateAccount(account: BankAccount): BankAccount {
+        val delegate = object : WrappedBankAccountDelegate(account) {
+
+            override fun getAccountOverall(account: BankAccount): Long {
+                return getAccountCoins(account)?.map { it.current }?.sum() ?: 0L
+            }
+
+            override fun getAccountCoins(account: BankAccount): MutableCollection<Coin>? {
+                val ids = linkRepo.findAllByAccountId(account.id).map { it.coinId }
+                return coinRepo.findAllByCoinIdIn(ids).map { it.coin }.toMutableList()
+            }
+
+            override fun accountExtractOne(account: BankAccount, coinId: UUID): Coin? {
+                val link = linkRepo.findOneByAccountIdAndCoinId(account.id, coinId).orElseThrow { RuntimeException("${account.id} has no such coin ${coinId}") }
+                linkRepo.deleteById(link.id)
+                return coinRepo.findOneByCoinId(link.coinId).map { it.coin }.orElse(null)
+            }
+
+            override fun accountAcceptOne(account: BankAccount, coin: Coin) {
+                val link = CoinAccountLink()
+                link.accountId = account.id
+                link.coinId = coin.id
+                link.ts = Date()
+                linkRepo.save(link)
+            }
+        }
+        return WrappedBankAccount(account as BankAccountData.BankAccountDefault, delegate)
+    }
+
+    @PostConstruct
+    fun init() {
+        accountRepo.deleteAll()
+        linkRepo.deleteAll()
+        val thisBank = world.getPerson(selfId) ?: world.addPerson(PersonData.BankPrivate(selfId))
+        val thisAccounts = getAccounts(selfId)
+        if (thisAccounts.isEmpty()) {
+            addAccount(selfId)
+        }
+    }
+
+    override fun addAccount(personId: UUID): BankAccount {
+        val ad = BankAccountData()
+        ad.bankId = selfId
+        ad.account = BankAccountData.BankAccountDefault(UUID.randomUUID(), personId)
+        return delegateAccount(accountRepo.save(ad).account)
+    }
+
+    override fun getId(): UUID {
+        return selfId
+    }
+
+    override fun hasPerson(person: UUID): Boolean {
+        return accountRepo.count(Expressions.allOf(bankAccountData.bankId.eq(selfId), bankAccountData.account.ownerId.eq(person))) > 0
+    }
+
+    override fun hasAccount(account: UUID): Boolean {
+        return accountRepo.exists(Expressions.allOf(bankAccountData.bankId.eq(selfId), bankAccountData.account.id.eq(account)))
+    }
+
+    override fun getAccounts(person: UUID): MutableCollection<BankAccount> {
+        return accountRepo.findAll(Expressions.allOf(bankAccountData.bankId.eq(selfId), bankAccountData.account.ownerId.eq(person))).map { delegateAccount(it.account) }.toMutableList()
+    }
+
+    override fun getAccounts(): Stream<BankAccount> {
+        return StreamSupport.stream(accountRepo.findAll(bankAccountData.bankId.eq(selfId)).spliterator(), false).map { delegateAccount(it.account) }
+    }
+}
+
+private abstract class WrappedBankAccountDelegate(var inner: BankAccount) : BankAccount {
+
+    override fun getId(): UUID {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOwnerId(): UUID {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOverall(): Long {
+        return getAccountOverall(inner)
+    }
+
+    abstract fun getAccountOverall(account: BankAccount): Long
+
+    override fun getCoins(): MutableCollection<Coin>? {
+        return getAccountCoins(inner)
+    }
+
+    abstract fun getAccountCoins(account: BankAccount): MutableCollection<Coin>?
+
+    override fun extractOne(coinId: UUID): Coin? {
+        return accountExtractOne(inner, coinId)
+    }
+
+    abstract fun accountExtractOne(account: BankAccount, coinId: UUID): Coin?
+
+    override fun acceptOne(coin: Coin) {
+        accountAcceptOne(inner, coin)
+    }
+
+    abstract fun accountAcceptOne(account: BankAccount, coin: Coin)
+}
+
+private class WrappedBankAccount(val inner: BankAccountData.BankAccountDefault, delegate: WrappedBankAccountDelegate) : BankAccount {
+
+    init {
+        inner.transientDelegate = delegate
+    }
+
+    override fun getId(): UUID {
+        return inner.id
+    }
+
+    override fun getOwnerId(): UUID {
+        return inner.ownerId
+    }
+
+    override fun getOverall(): Long {
+        return inner.overall
+    }
+
+    override fun getCoins(): MutableCollection<Coin>? {
+        return inner.coins
+    }
+
+    override fun extractOne(coinId: UUID): Coin? {
+        return inner.extractOne(coinId)
+    }
+
+    override fun acceptOne(coin: Coin) {
+        inner.acceptOne(coin)
+    }
+
+    override fun toString(): String {
+        return inner.toString()
+    }
+}

+ 6 - 2
src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalEmitterImpl.kt

@@ -93,7 +93,7 @@ class GlobalEmitterImpl : GlobalEmitter {
     override fun emitOne(coin: Coin): UUID {
         return computeIfAbsent(coin) {
             val new = coinRepo.create(coin.value, coin.era)
-            log.info("emitter emit coin ${CoinUtils.sum(listOf(new.coin))}")
+            log.debug("emitter emit coin ${CoinUtils.sum(listOf(new.coin))}")
             new
         }.id
     }
@@ -102,7 +102,7 @@ class GlobalEmitterImpl : GlobalEmitter {
         (0 until count).forEach {
             computeIfAbsent { coinRepo.create(value, 0) }
         }
-        log.info("emitter emit ${count} of ${value}")
+        log.debug("emitter emit ${count} of ${value}")
     }
 
     override fun getOverall(): Long {
@@ -113,6 +113,10 @@ class GlobalEmitterImpl : GlobalEmitter {
         return null
     }
 
+    override fun getOwnerId(): UUID {
+        return globalId
+    }
+
     override fun getId(): UUID {
         return globalId
     }

+ 67 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/data/GlobalWorldImpl.kt

@@ -0,0 +1,67 @@
+package inn.ocsf.bee.freigeld.core.data
+
+import inn.ocsf.bee.freigeld.core.model.*
+import inn.ocsf.bee.freigeld.core.model.data.PersonData
+import inn.ocsf.bee.freigeld.core.model.data.QPersonData
+import inn.ocsf.bee.freigeld.core.repo.PersonRepository
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+import java.util.*
+import java.util.stream.Collectors
+import javax.annotation.PostConstruct
+import javax.inject.Inject
+
+@Service
+class GlobalWorldImpl : GlobalWorld {
+
+    @Inject
+    private lateinit var personRepo: PersonRepository
+
+    private val log = LoggerFactory.getLogger(javaClass)
+
+    @PostConstruct
+    fun init() {
+        personRepo.deleteAll()
+    }
+
+    override fun addPerson(person: Person): UUID {
+        return personRepo.findOneByPersonId(person.id!!).map { it.person.id }.orElseGet {
+            val pd = PersonData()
+            when (person) {
+                is NaturalPerson -> {
+                    pd.person = person
+                    pd.personType = PersonType.natural
+                }
+                is PrivatePerson -> {
+                    pd.person = person
+                    pd.personType = PersonType.natural
+                }
+                else -> throw RuntimeException("unsupported person type ${person.javaClass.name}")
+            }
+            personRepo.save(pd).person.id
+        }
+    }
+
+    override fun getPersonByIdentity(id: PersonIdentity): Person? {
+        return personRepo.findOne(QPersonData.personData.identitySet.contains(id.key)).map { it.person }.orElse(null)
+    }
+
+    override fun setPersonIdentity(person: Person, identity: PersonIdentity) {
+        val pd = personRepo.findOneByPersonId(person.id).orElseThrow { RuntimeException("person ${person.id} not found") }
+        pd.identities.put(identity.key, identity)
+        pd.identitySet = pd.identities.keys.toList()
+        personRepo.save(pd)
+    }
+
+    override fun getPersonIdentitySet(person: Person): Set<PersonIdentity>? {
+        return personRepo.findOneByPersonId(person.id).map { it.identities.values.toSet() }.orElse(null)
+    }
+
+    override fun getPerson(personId: UUID): Person? {
+        return personRepo.findOneByPersonId(personId).map { it.person }.orElse(null)
+    }
+
+    override fun getPersonIds(): Set<UUID> {
+        return personRepo.findAllBy().map { it.person.id }.collect(Collectors.toSet())
+    }
+}

+ 60 - 71
src/main/kotlin/inn/ocsf/bee/freigeld/core/demo/DemoInMem.kt

@@ -3,9 +3,10 @@ package inn.ocsf.bee.freigeld.core.demo
 import com.oblac.nomen.Nomen
 import inn.ocsf.bee.freigeld.core.calc.CoinStrategy
 import inn.ocsf.bee.freigeld.core.calc.CoinUtils
+import inn.ocsf.bee.freigeld.core.data.CentralBankAccountLevel
 import inn.ocsf.bee.freigeld.core.model.*
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.runBlocking
+import inn.ocsf.bee.freigeld.core.model.data.PersonData
+import kotlinx.coroutines.*
 import org.slf4j.LoggerFactory
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.scheduling.annotation.Scheduled
@@ -19,19 +20,20 @@ import kotlin.math.roundToLong
 @Service
 class DemoInMem {
 
-    val personMap = mutableMapOf<UUID, DemoHuman>()
+    @Autowired
+    private lateinit var world: GlobalWorld
 
     @Autowired
     private lateinit var emitter: GlobalEmitter
 
     @Autowired
-    private lateinit var bank: GlobalBank
+    private lateinit var bank: CentralBank
 
     private val log = LoggerFactory.getLogger(javaClass)
 
     @PostConstruct
     fun init() {
-        emitter.emit(10000, CoinValue.one)
+        /*emitter.emit(10000, CoinValue.one)
         emitter.emit(5000, CoinValue.three)
         emitter.emit(2000, CoinValue.five)
         emitter.emit(1000, CoinValue.ten)
@@ -40,10 +42,21 @@ class DemoInMem {
         emitter.emit(50, CoinValue.full)
         emitter.emit(25, CoinValue.bi)
         emitter.emit(10, CoinValue.mega)
-
+*/
         bank.exchange(bank.getSelfAccount(), emitter, 1_300_000)
-        (0 until 100).forEach { personMap.computeIfAbsent(UUID.randomUUID()) { id -> DemoHuman(id, Nomen.randomName()) } }
-        personMap.keys.map { bank.addAccount(it) }.forEach {
+        (0 until 100).map {
+            val newHuman: NaturalPerson = PersonData.NaturalPersonImpl(Nomen.randomName())
+            val oldHuman = world.getPersonByIdentity(PersonIdentityFullName(newHuman.fullName))
+            if (oldHuman != null) {
+                oldHuman as NaturalPerson
+            } else {
+                world.addPerson(newHuman)
+                world.setPersonIdentity(newHuman, PersonIdentityFullName(newHuman.fullName))
+                newHuman
+            }
+        }.forEach { log.info("human ${it.fullName} created") }
+
+        world.getPersonIds().map { bank.addAccount(it) }.forEach {
             bank.exchange(it, bank.getSelfAccount(), 1_200 + (500 * Math.random()).roundToLong())
                     .thenRun({ log.info("exchange ok") })
                     .exceptionally({ log.error("exchange error"); null })
@@ -51,8 +64,9 @@ class DemoInMem {
     }
 
     private fun doRand0() {
-        val fromPerson = personMap.keys.random()
-        val toPerson = personMap.keys.subtract(setOf(fromPerson)).random()
+        val personIds = world.getPersonIds()
+        val fromPerson = personIds.random()
+        val toPerson = personIds.subtract(setOf(fromPerson)).random()
         val fromAccount = bank.getAccounts(fromPerson).first()
         val toAccout = bank.getAccounts(toPerson).first()
         val dir = if (fromAccount.overall >= toAccout.overall) fromAccount to toAccout else toAccout to fromAccount
@@ -73,60 +87,24 @@ class DemoInMem {
     }
 }
 
-fun GlobalBank.getSelfAccount(): BankAccount {
+fun CentralBank.getSelfAccount(): BankAccount {
     return this.getAccounts(this.id).first()
 }
 
 @Service
-class GlobalBank : Bank {
+class GlobalBankDemo : CentralBankAccountLevel() {
 
     @Autowired
     private lateinit var emitter: GlobalEmitter
 
-    val accountMap = mutableMapOf<UUID, BankAccount>()
-    val personToAccountTable = mutableSetOf<Pair<UUID, UUID>>()
+    //val accountMap = mutableMapOf<UUID, BankAccount>()
+    //val personToAccountTable = mutableSetOf<Pair<UUID, UUID>>()
     val globalQueue: Deque<BankEvent> = ArrayDeque()
     val globalFutureMap = mutableMapOf<UUID, CompletableDeferred<Any>>()
     val coinToAccountMap = mutableMapOf<UUID, UUID>()
 
-    private lateinit var selfId: UUID
-    private lateinit var selfAccount: BankAccount
-
     private val log = LoggerFactory.getLogger(javaClass)
 
-    init {
-        selfId = UUID.randomUUID()
-        selfAccount = addAccount(selfId)
-    }
-
-    override fun getId(): UUID {
-        return selfId
-    }
-
-    override fun hasPerson(person: UUID): Boolean {
-        return personToAccountTable.any { it.first == person }
-    }
-
-    override fun hasAccount(account: UUID): Boolean {
-        return accountMap.containsKey(account)
-    }
-
-    final override fun addAccount(person: UUID): BankAccount {
-        val account = if (person != selfId) {
-            val accountId = if (!accountMap.containsKey(person)) person else UUID.randomUUID() //first account assign to personId
-            DemoOwnerAccout(DemoAccount(accountId, person), this)
-        } else {
-            DemoOwnerAccout(DemoSelfAccount(selfId), this)
-        }
-        accountMap[account.id] = account
-        personToAccountTable.add(person to account.id)
-        return account
-    }
-
-    override fun getAccounts(person: UUID): Collection<BankAccount> {
-        return personToAccountTable.filter { it.first == person }.map { accountMap[it.second] }.filterNotNull()
-    }
-
     override fun exchange(to: BankAccount, from: BankAccount, amount: Long): CompletableFuture<Any> {
         val e = ExchangeStartEvent(UUID.randomUUID(), to, from, amount)
         val ret = CompletableFuture<Any>()
@@ -142,7 +120,7 @@ class GlobalBank : Bank {
     suspend fun pollLater(e: BankEvent): Boolean {
         return when (e) {
             is ExchangeStartEvent -> {
-                CoinUtils.makeSomeStrategy(CoinStrategy(e.amount, if (e.from.id != emitter.id) e.from.coins else null, if (e.from.id != selfAccount.id) e.to.coins else null)).let { st ->
+                CoinUtils.makeSomeStrategy(CoinStrategy(e.amount, if (e.from.id != emitter.id) e.from.coins else null, if (e.from.id != selfId) e.to.coins else null)).let { st ->
                     if (st.res!!.isOk()) {
                         st
                     } else if (st.res!!.isErr()) {
@@ -152,7 +130,7 @@ class GlobalBank : Bank {
                         TODO()
                     }
                 }?.let { st ->
-                    log.info("${selfAccount} do ${st.res?.name} transaction: ${e.from} -> ${e.to} = ${e.amount}")
+                    log.info("${selfId} do ${st.res?.name} transaction: ${e.from} -> ${e.to} = ${e.amount}")
                     var fromAmount = e.from.overall
                     var toAmount = e.to.overall
 
@@ -168,7 +146,7 @@ class GlobalBank : Bank {
                         val newIds = emitter.emit(st.leftEmit!!)
                         val newCoins = emitter.extract(newIds)
                         e.from.accept(newCoins)
-                        if (e.from.id != selfAccount.id) {
+                        if (e.from.id != selfId) {
                             emitter.free(newCoins)
                         } else {
                             //do nothing
@@ -190,7 +168,7 @@ class GlobalBank : Bank {
                         }
                         val newCoins = emitter.extract(newIds)
                         e.to.accept(newCoins)
-                        if (e.to.id != selfAccount.id) {
+                        if (e.to.id != selfId) {
                             emitter.free(newCoins)
                         } else {
                             //do nothing
@@ -205,7 +183,7 @@ class GlobalBank : Bank {
                         e.to.accept(someCoins)
                         someCoins.forEach { localCoins.remove(it.id) }
                         emitter.accept(localCoins.values)
-                        if (e.to.id != selfAccount.id) {
+                        if (e.to.id != selfId) {
                             emitter.free(someCoins)
                         } else {
                             //do nothing
@@ -220,7 +198,7 @@ class GlobalBank : Bank {
                         e.from.accept(someCoins)
                         someCoins.forEach { localCoins.remove(it.id) }
                         emitter.accept(localCoins.values)
-                        if (e.from.id != selfAccount.id) {
+                        if (e.from.id != selfId) {
                             emitter.free(someCoins)
                         } else {
                             //do nothing
@@ -289,14 +267,25 @@ class GlobalBank : Bank {
 
     @PostConstruct
     fun start() {
+        GlobalScope.launch {
+            do {
+                tick()
+                delay(200)
+            } while (true)
+        }
         emitter.listen { e ->
             log.info("emitter event ${e.javaClass.name}")
             when (e) {
                 is EmitterStartRecalculationEvent -> {
-                    globalQueue.offerFirst(BankPauseOnRecalcEvent(UUID.randomUUID(), e))
+                    globalQueue.offerLast(BankPauseOnRecalcEvent(UUID.randomUUID(), e))
+                    runBlocking {
+                        do {
+                            delay(500)
+                        } while (!(globalQueue.peekFirst() is BankPauseOnRecalcEvent))
+                    }
                 }
                 is EmitterStopRecalculationEvent -> {
-                    accountMap.values.map { it to it.coins?.map { it.id }?.intersect(e.nullCoins) }.filter { it.second?.isNotEmpty() ?: false }.forEach { ap ->
+                    accounts.map { it to it.coins?.map { it.id }?.intersect(e.nullCoins) }.filter { it.second?.isNotEmpty() ?: false }.forEach { ap ->
                         val nullCoins = ap.second?.map {
                             //(ap.first as DemoAccount).internalCoins.get(it)?.incEra() //TODO убрать!
                             ap.first.extractOne(it)
@@ -309,7 +298,6 @@ class GlobalBank : Bank {
         }
     }
 
-    @Scheduled(initialDelay = 1000L, fixedDelay = 100L)
     fun tick() {
         var pollCount = 0
         while (globalQueue.isNotEmpty() && pollCount < maxPollCount) {
@@ -321,7 +309,7 @@ class GlobalBank : Bank {
         }
     }
 
-    class DemoOwnerAccout(val inner: BankAccount, val owner: GlobalBank) : BankAccount {
+    class DemoOwnerAccout(val inner: BankAccount, val owner: GlobalBankDemo) : BankAccount {
 
         override fun getOverall(): Long {
             return inner.overall
@@ -331,6 +319,10 @@ class GlobalBank : Bank {
             return inner.coins
         }
 
+        override fun getOwnerId(): UUID {
+            TODO("Not yet implemented")
+        }
+
         override fun acceptOne(coin: Coin) {
             if (owner.coinToAccountMap.containsKey(coin.id)) {
                 throw RuntimeException("already owned")
@@ -393,15 +385,8 @@ class ExchangeStartEvent(eventId: UUID, val to: BankAccount, val from: BankAccou
 
 class BankPauseOnRecalcEvent(eventId: UUID, val emitterEvent: EmitterStartRecalculationEvent, var paused: Boolean = true) : AbstractBankEvent(eventId)
 
-class DemoHuman(val selfId: UUID, val name: String) : NaturalPerson {
-
-    override fun getId(): UUID = selfId
-
-}
 
-class DemoSelfAccount(val selfId: UUID) : DemoAccount(selfId, selfId)
-
-open class DemoAccount(val accountId: UUID, personId: UUID) : BankAccount {
+open class DemoAccount(val accountId: UUID, val personId: UUID) : BankAccount {
 
     val internalCoins = mutableMapOf<UUID, Coin>()
     var _overall: Long? = null
@@ -417,10 +402,10 @@ open class DemoAccount(val accountId: UUID, personId: UUID) : BankAccount {
             val coin = internalCoins.remove(coinId)!!
             if (coin.current == 0L) {
                 _overall = null
-                log.info("${this} leave coin ${coin.current}")
+                log.debug("${this} leave coin ${coin.current}")
             } else {
                 decOverall(coin.current)
-                log.info("${this} credit coin ${coin.current}")
+                log.debug("${this} credit coin ${coin.current}")
             }
             coin
         } else {
@@ -441,6 +426,10 @@ open class DemoAccount(val accountId: UUID, personId: UUID) : BankAccount {
         return internalCoins.values
     }
 
+    override fun getOwnerId(): UUID {
+        return personId
+    }
+
     private fun decOverall(amount: Long) {
         if (amount == 0L) throw IllegalArgumentException("should not be 0")
         if (_overall != null) {
@@ -468,7 +457,7 @@ open class DemoAccount(val accountId: UUID, personId: UUID) : BankAccount {
         } else if (internalCoins.containsKey(coin.id)) {
             throw IllegalArgumentException("same coin is not acceptable")
         }
-        log.info("${this} debit coin ${CoinUtils.sum(listOf(coin))}")
+        log.debug("${this} debit coin ${CoinUtils.sum(listOf(coin))}")
     }
 
 

+ 3 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/model/CentralBank.kt

@@ -0,0 +1,3 @@
+package inn.ocsf.bee.freigeld.core.model
+
+interface CentralBank : Bank

+ 18 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/model/GlobalWorld.kt

@@ -0,0 +1,18 @@
+package inn.ocsf.bee.freigeld.core.model
+
+import java.util.*
+
+interface GlobalWorld {
+
+    fun addPerson(person: Person): UUID
+
+    fun getPersonByIdentity(id: PersonIdentity): Person?
+
+    fun setPersonIdentity(person: Person, identity: PersonIdentity)
+
+    fun getPersonIdentitySet(person: Person): Set<PersonIdentity>?
+
+    fun getPerson(personId: UUID): Person?
+
+    fun getPersonIds(): Set<UUID>
+}

+ 11 - 0
src/main/kotlin/inn/ocsf/bee/freigeld/core/model/PersonIdentityString.kt

@@ -0,0 +1,11 @@
+package inn.ocsf.bee.freigeld.core.model
+
+import org.apache.commons.codec.digest.DigestUtils
+
+data class PersonIdentityFullName(val fullName: String) : PersonIdentity {
+
+    override fun getKey(): String {
+        return DigestUtils.sha512Hex(fullName)
+    }
+}
+