contents;
+}
diff --git a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractMessagePayloadModel.java b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractMessagePayloadModel.java
new file mode 100644
index 0000000..00a7d76
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractMessagePayloadModel.java
@@ -0,0 +1,27 @@
+package org.mercury_im.messenger.data.model;
+
+import org.mercury_im.messenger.data.enums.MessageContentType;
+
+import io.requery.Column;
+import io.requery.Entity;
+import io.requery.Generated;
+import io.requery.Key;
+import io.requery.ManyToOne;
+import io.requery.Table;
+
+@Entity
+@Table(name = "msg_payloads")
+public abstract class AbstractMessagePayloadModel {
+
+ @Key @Generated
+ long id;
+
+ @ManyToOne
+ MessagePayloadContainerModel payloadContainer;
+
+ @Column
+ String body;
+
+ @Column(nullable = false)
+ MessageContentType type;
+}
diff --git a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractPeerModel.java b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractPeerModel.java
new file mode 100644
index 0000000..5a4e13e
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractPeerModel.java
@@ -0,0 +1,56 @@
+package org.mercury_im.messenger.data.model;
+
+import org.mercury_im.messenger.data.converter.SubscriptionDirectionConverter;
+import org.mercury_im.messenger.entity.contact.SubscriptionDirection;
+
+import java.util.UUID;
+
+import io.requery.CascadeAction;
+import io.requery.Column;
+import io.requery.Convert;
+import io.requery.Entity;
+import io.requery.ForeignKey;
+import io.requery.Generated;
+import io.requery.Index;
+import io.requery.Key;
+import io.requery.ManyToOne;
+import io.requery.Persistable;
+import io.requery.ReferentialAction;
+import io.requery.Table;
+import io.requery.converter.UUIDConverter;
+
+@Entity
+@Table(name = "contacts", uniqueIndexes = "unique_address")
+public abstract class AbstractPeerModel implements Persistable {
+
+ @Key
+ @Convert(UUIDConverter.class)
+ UUID id;
+
+ @Index("unique_address")
+ @ManyToOne(cascade = CascadeAction.NONE)
+ @ForeignKey(referencedColumn = "id")
+ AccountModel account;
+
+ @Index("unique_address")
+ @Column(nullable = false)
+ String address;
+
+ @Column
+ String name;
+
+ @Convert(SubscriptionDirectionConverter.class)
+ SubscriptionDirection subscriptionDirection;
+
+ boolean subscriptionPending;
+
+ boolean subscriptionPreApproved;
+
+ @Override
+ public String toString() {
+ return "Peer[" + id + ", " +
+ name + ", " +
+ address + ", " +
+ account + "]";
+ }
+}
diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractSaslAuthenticationResultModel.java b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractSaslAuthenticationResultModel.java
similarity index 75%
rename from persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractSaslAuthenticationResultModel.java
rename to data/src/main/java/org/mercury_im/messenger/data/model/AbstractSaslAuthenticationResultModel.java
index 1753071..f13b416 100644
--- a/persistence/src/main/java/org/mercury_im/messenger/persistence/entity/AbstractSaslAuthenticationResultModel.java
+++ b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractSaslAuthenticationResultModel.java
@@ -1,6 +1,6 @@
-package org.mercury_im.messenger.persistence.entity;
+package org.mercury_im.messenger.data.model;
-import org.mercury_im.messenger.persistence.enums.SaslCondition;
+import org.mercury_im.messenger.data.enums.SaslCondition;
import io.requery.Entity;
import io.requery.Key;
diff --git a/data/src/main/java/org/mercury_im/messenger/data/model/package-info.java b/data/src/main/java/org/mercury_im/messenger/data/model/package-info.java
new file mode 100644
index 0000000..77cc9dd
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/model/package-info.java
@@ -0,0 +1,12 @@
+/**
+ * The model package contains requery model definitions.
+ * All of those classes are abstract, since the requery framework generates concrete implementations
+ * during compilation. Those files can later be found in
+ * build/generated/sources/annotationProcessor/java/main/...
.
+ *
+ * The structure of the model classes closely mimics the structure of their entity pendants
+ * declared in the entity
module.
+ *
+ * @see requery wiki on model definitions
+ */
+package org.mercury_im.messenger.data.model;
diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RequeryRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/RequeryRepository.java
similarity index 70%
rename from persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RequeryRepository.java
rename to data/src/main/java/org/mercury_im/messenger/data/repository/RequeryRepository.java
index 4cf203d..8b814b2 100644
--- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RequeryRepository.java
+++ b/data/src/main/java/org/mercury_im/messenger/data/repository/RequeryRepository.java
@@ -1,4 +1,8 @@
-package org.mercury_im.messenger.persistence.repository;
+package org.mercury_im.messenger.data.repository;
+
+import org.mercury_im.messenger.util.ThreadUtils;
+
+import javax.inject.Named;
import io.reactivex.Scheduler;
import io.requery.Persistable;
@@ -12,8 +16,8 @@ public abstract class RequeryRepository {
private final ReactiveEntityStore data;
protected RequeryRepository(ReactiveEntityStore data,
- Scheduler subscriberScheduler,
- Scheduler observerScheduler) {
+ @Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
+ @Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler) {
this.data = data;
this.subscriberScheduler = subscriberScheduler;
this.observerScheduler = observerScheduler;
diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/XmppAccountRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppAccountRepository.java
new file mode 100644
index 0000000..f683023
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppAccountRepository.java
@@ -0,0 +1,146 @@
+package org.mercury_im.messenger.data.repository;
+
+import org.mercury_im.messenger.data.mapping.AccountMapping;
+import org.mercury_im.messenger.data.model.AccountModel;
+import org.mercury_im.messenger.data.repository.dao.AccountDao;
+import org.mercury_im.messenger.util.Optional;
+import org.mercury_im.messenger.entity.Account;
+import org.mercury_im.messenger.util.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Scheduler;
+import io.reactivex.Single;
+import io.requery.Persistable;
+import io.requery.query.ResultDelegate;
+import io.requery.reactivex.ReactiveEntityStore;
+import io.requery.reactivex.ReactiveResult;
+
+public class XmppAccountRepository
+ extends RequeryRepository
+ implements AccountRepository {
+
+ private final AccountMapping accountMapping;
+ private final AccountDao dao;
+
+ @Inject
+ public XmppAccountRepository(ReactiveEntityStore data,
+ @Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
+ @Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
+ AccountMapping accountMapping) {
+ super(data, subscriberScheduler, observerScheduler);
+ this.accountMapping = accountMapping;
+ this.dao = new AccountDao(data);
+ }
+
+ @Override
+ public Single insertAccount(Account account) {
+ return Single.just(account)
+ .map(accountMapping::toModel)
+ .flatMap(dao::insert)
+ .map(model -> accountMapping.toEntity(model, account))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeAccount(UUID accountId) {
+ return dao.get(accountId).observableResult()
+ .map(result -> new Optional<>(result.firstOrNull()))
+ .map(accountMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Maybe getAccount(UUID accountId) {
+ return dao.get(accountId).maybe()
+ .map(accountMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeAccountByAddress(String address) {
+ return dao.get(address).observableResult()
+ .map(result -> new Optional<>(result.firstOrNull()))
+ .map(accountMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Maybe getAccountByAddress(String address) {
+ return dao.get(address).maybe()
+ .map(accountMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeAllAccounts() {
+ return dao.getAll().observableResult()
+ .map(ResultDelegate::toList)
+ .map(this::modelsToEntities)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable observeAccounts() {
+ return dao.getAll().observableResult()
+ .flatMap(ReactiveResult::observable)
+ .map(accountMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Single updateAccount(Account account) {
+ // Since we cannot access setId() of AccountModel, we have to query the model by ID and update it manually.
+ // https://github.com/requery/requery/issues/616#issuecomment-315685460
+
+ return dao.get(account.getId()).maybe().toSingle()
+ .map(model -> accountMapping.toModel(account, model))
+ .flatMap(updatedModel -> data().update(updatedModel))
+ .map(model -> accountMapping.toEntity(model, account))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Single upsertAccount(Account account) {
+ return dao.get(account.getId()).maybe()
+ .switchIfEmpty(
+ Single.just(account).map(accountMapping::toModel).flatMap(dao::insert))
+ .map(model -> accountMapping.toModel(account, model))
+ .flatMap(data()::update)
+ .map(model -> accountMapping.toEntity(model, account))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Completable deleteAccount(UUID accountId) {
+ return dao.delete(accountId).ignoreElement()
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ private List modelsToEntities(List models) {
+ List entities = new ArrayList<>(models.size());
+ for (AccountModel model : models) {
+ entities.add(accountMapping.toEntity(model));
+ }
+ return entities;
+ }
+}
diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/XmppDirectChatRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppDirectChatRepository.java
new file mode 100644
index 0000000..08462a4
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppDirectChatRepository.java
@@ -0,0 +1,153 @@
+package org.mercury_im.messenger.data.repository;
+
+import org.mercury_im.messenger.data.mapping.DirectChatMapping;
+import org.mercury_im.messenger.data.model.DirectChatModel;
+import org.mercury_im.messenger.data.repository.dao.DirectChatDao;
+import org.mercury_im.messenger.util.Optional;
+import org.mercury_im.messenger.entity.chat.DirectChat;
+import org.mercury_im.messenger.entity.chat.IDirectChat;
+import org.mercury_im.messenger.entity.contact.Peer;
+import org.mercury_im.messenger.util.ThreadUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Scheduler;
+import io.reactivex.Single;
+import io.requery.Persistable;
+import io.requery.query.ResultDelegate;
+import io.requery.reactivex.ReactiveEntityStore;
+import io.requery.reactivex.ReactiveResult;
+
+public class XmppDirectChatRepository
+ extends RequeryRepository
+ implements DirectChatRepository {
+
+ private final DirectChatMapping directChatMapping;
+
+ private final DirectChatDao dao;
+
+ @Inject
+ public XmppDirectChatRepository(
+ ReactiveEntityStore data,
+ @Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
+ @Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
+ DirectChatMapping directChatMapping) {
+ super(data, subscriberScheduler, observerScheduler);
+ this.directChatMapping = directChatMapping;
+ this.dao = new DirectChatDao(data);
+ }
+
+ @Override
+ public Single insertDirectChat(DirectChat chat) {
+ return Single.just(chat)
+ // map entity to model
+ .map(directChatMapping::toModel)
+ .flatMap(dao::insert)
+ // map back to entity
+ .map(model -> directChatMapping.toEntity(model, chat))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeDirectChat(UUID chatId) {
+ return dao.get(chatId).observableResult()
+ .map(result -> new Optional<>(result.firstOrNull()))
+ .map(directChatMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Maybe getDirectChat(UUID chatId) {
+ return dao.get(chatId).maybe()
+ .map(directChatMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Single getOrCreateChatWithPeer(Peer peer) {
+ return getDirectChatByPeer(peer)
+ .switchIfEmpty(Single.just(new IDirectChat())
+ .map(chat -> {
+ chat.setAccount(peer.getAccount());
+ chat.setPeer(peer);
+ return chat;
+ })
+ .flatMap(this::insertDirectChat))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeDirectChatByPeer(Peer peer) {
+ return dao.getByPeer(peer.getId()).observableResult()
+ .map(result -> new Optional<>(result.firstOrNull()))
+ .map(directChatMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Maybe getDirectChatByPeer(Peer peer) {
+ return dao.getByPeer(peer.getId()).maybe()
+ .map(directChatMapping::toEntity)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Observable> observeAllDirectChats() {
+ return dao.getAll().observableResult()
+ .map(ResultDelegate::toList)
+ .map(this::chatModelsToEntities)
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ private List chatModelsToEntities(List models) {
+ List entities = new ArrayList<>(models.size());
+ for (DirectChatModel model : models) {
+ entities.add(directChatMapping.toEntity(model));
+ }
+ return entities;
+ }
+
+ @Override
+ public Single updateDirectChat(DirectChat chat) {
+ return dao.get(chat.getId()).maybe().toSingle()
+ .map(model -> directChatMapping.toModel(chat, model))
+ .flatMap(data()::update)
+ .map(model -> directChatMapping.toEntity(model, chat))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Single upsertDirectChat(DirectChat chat) {
+ return dao.get(chat.getId()).maybe()
+ .switchIfEmpty(dao.insert(directChatMapping.toModel(chat)))
+ .map(directChatModel -> directChatMapping.toModel(chat, directChatModel))
+ .flatMap(data()::update)
+ .map(model -> directChatMapping.toEntity(model, chat))
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+
+ @Override
+ public Completable deleteDirectChat(UUID chatId) {
+ return dao.delete(chatId)
+ .ignoreElement()
+ .subscribeOn(subscriberScheduler())
+ .observeOn(observerScheduler());
+ }
+}
diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/XmppEntityCapsRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppEntityCapsRepository.java
new file mode 100644
index 0000000..ce9d4cf
--- /dev/null
+++ b/data/src/main/java/org/mercury_im/messenger/data/repository/XmppEntityCapsRepository.java
@@ -0,0 +1,91 @@
+package org.mercury_im.messenger.data.repository;
+
+import org.mercury_im.messenger.data.mapping.EntityCapsMapping;
+import org.mercury_im.messenger.data.model.EntityCapsModel;
+import org.mercury_im.messenger.entity.caps.EntityCapsRecord;
+import org.mercury_im.messenger.util.ThreadUtils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Observable;
+import io.reactivex.Scheduler;
+import io.requery.Persistable;
+import io.requery.query.Expression;
+import io.requery.query.ResultDelegate;
+import io.requery.reactivex.ReactiveEntityStore;
+import io.requery.reactivex.ReactiveResult;
+
+public class XmppEntityCapsRepository extends RequeryRepository implements EntityCapsRepository {
+
+ private final EntityCapsMapping entityCapsMapping;
+
+ @Inject
+ public XmppEntityCapsRepository(
+ ReactiveEntityStore data,
+ @Named(value = ThreadUtils.SCHEDULER_IO) Scheduler subscriberScheduler,
+ @Named(value = ThreadUtils.SCHEDULER_UI) Scheduler observerScheduler,
+ EntityCapsMapping mapping) {
+ super(data, subscriberScheduler, observerScheduler);
+ this.entityCapsMapping = mapping;
+ }
+
+ @Override
+ public Observable