Mercury-IM/domain/src/main/java/org/mercury_im/messenger/core/xmpp/MercuryConnectionManager.java

228 lines
9.4 KiB
Java

package org.mercury_im.messenger.core.xmpp;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.data.repository.AccountRepository;
import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
import org.mercury_im.messenger.core.data.repository.MessageRepository;
import org.mercury_im.messenger.core.data.repository.PeerRepository;
import org.mercury_im.messenger.core.data.repository.Repositories;
import org.mercury_im.messenger.core.store.message.MercuryMessageStoreFactory;
import org.mercury_im.messenger.core.xmpp.state.ConnectionPoolState;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.core.store.caps.MercuryEntityCapsStore;
import org.mercury_im.messenger.core.store.message.MercuryMessageStore;
import org.mercury_im.messenger.core.usecase.RosterStoreBinder;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.xmpp.state.ConnectionState;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.BehaviorSubject;
@Singleton
public class MercuryConnectionManager {
private static final Logger LOGGER = Logger.getLogger("ConnectionManager");
private final XmppConnectionFactory connectionFactory;
private final MercuryMessageStoreFactory messageStoreFactory;
private final AccountRepository accountRepository;
private final RosterStoreBinder rosterStoreBinder;
private final MercuryEntityCapsStore entityCapsStore;
private final PeerRepository peerRepository;
private final DirectChatRepository directChatRepository;
private final MessageRepository messageRepository;
private final SchedulersFacade schedulers;
private final Map<UUID, MercuryConnection> connectionsMap = new ConcurrentHashMap<>();
private final Map<UUID, Disposable> connectionDisposables = new ConcurrentHashMap<>();
private final BehaviorSubject<ConnectionPoolState> connectionPoolObservable =
BehaviorSubject.createDefault(new ConnectionPoolState());
private final CompositeDisposable disposable = new CompositeDisposable();
static {
SmackConfig.staticConfiguration();
}
@Inject
public MercuryConnectionManager(Repositories repositories,
RosterStoreBinder rosterStoreBinder,
MercuryEntityCapsStore entityCapsStore,
MercuryMessageStoreFactory messageStoreFactory,
XmppConnectionFactory connectionFactory,
SchedulersFacade schedulers) {
this.accountRepository = repositories.getAccountRepository();
this.rosterStoreBinder = rosterStoreBinder;
this.entityCapsStore = entityCapsStore;
this.peerRepository = repositories.getPeerRepository();
this.directChatRepository = repositories.getDirectChatRepository();
this.messageRepository = repositories.getMessageRepository();
this.connectionFactory = connectionFactory;
this.messageStoreFactory = messageStoreFactory;
this.schedulers = schedulers;
EntityCapsManager.setPersistentCache(entityCapsStore);
start();
}
public void start() {
disposable.add(accountRepository.observeAllAccounts()
.subscribeOn(schedulers.getIoScheduler())
.subscribe(this::doRegisterConnections));
}
public List<MercuryConnection> getConnections() {
return new ArrayList<>(connectionsMap.values());
}
public Observable<ConnectionPoolState> observeConnectionPool() {
return connectionPoolObservable;
}
public MercuryConnection getConnection(Account account) {
return getConnection(account.getId());
}
public MercuryConnection getConnection(UUID id) {
return connectionsMap.get(id);
}
public MercuryConnection createConnection(Account account) {
return new MercuryConnection(connectionFactory.createConnection(account), account);
}
public void doRegisterConnections(List<Account> accounts) {
for (Account account : accounts) {
if (!connectionsMap.containsKey(account.getId())) {
MercuryConnection connection = createConnection(account);
doRegisterConnection(connection);
}
}
}
public Completable registerConnection(MercuryConnection connection) {
return Completable.fromAction(() -> doRegisterConnection(connection));
}
public void doRegisterConnection(MercuryConnection connection) {
LOGGER.log(Level.INFO, "Register Connection " + connection.getAccountId());
putConnection(connection);
disposable.add(accountRepository
.observeAccount(connection.getAccountId())
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe(event ->
handleOptionalAccountChangedEvent(connection, event)));
}
private void putConnection(MercuryConnection connection) {
connectionsMap.put(connection.getAccountId(), connection);
connectionDisposables.put(connection.getAccountId(), connection.observeConnection().subscribe(s ->
connectionPoolObservable.onNext(updatePoolState(connectionPoolObservable.getValue(), s))));
bindConnection(connection);
}
private ConnectionPoolState updatePoolState(ConnectionPoolState poolState, ConnectionState conState) {
Map<UUID, ConnectionState> states = poolState.getConnectionStates();
states.put(conState.getId(), conState);
return new ConnectionPoolState(states);
}
public void bindConnection(MercuryConnection connection) {
rosterStoreBinder.setRosterStoreOn(connection);
disposable.add(accountRepository.getAccount(connection.getAccountId())
.subscribeOn(schedulers.getIoScheduler())
.subscribe(account -> {
MercuryMessageStore mercuryMessageStore = messageStoreFactory.createMessageStore(account);
ChatManager chatManager = ChatManager.getInstanceFor(connection.getConnection());
chatManager.addIncomingListener(mercuryMessageStore);
chatManager.addOutgoingListener(mercuryMessageStore);
}));
}
private void handleOptionalAccountChangedEvent(MercuryConnection connection, Optional<Account> event) {
if (event.isPresent()) {
handleAccountChangedEvent(connection, event.getItem());
} else {
handleAccountRemoved(connection);
}
}
private void handleAccountChangedEvent(MercuryConnection connection, Account account) {
if (account.isEnabled()) {
handleAccountEnabled(connection);
} else {
handleAccountDisabled(connection);
}
}
private void handleAccountDisabled(MercuryConnection connection) {
LOGGER.log(Level.FINER, "HandleAccountDisabled: " + connection.getAccountId());
disposable.add(connection.shutdown().subscribeOn(Schedulers.newThread()).subscribe());
}
private void handleAccountEnabled(MercuryConnection connection) {
LOGGER.log(Level.FINER, "HandleAccountEnabled: " + connection.getAccountId());
connectionLogin(connection);
}
private void connectionLogin(MercuryConnection connection) {
disposable.add(connection.connect().andThen(connection.login())
.subscribeOn(Schedulers.io())
.subscribe(() -> LOGGER.log(Level.FINER, "Logged in."),
error -> LOGGER.log(Level.SEVERE, "Connection error!", error)));
}
private void handleAccountRemoved(MercuryConnection connection) {
LOGGER.log(Level.FINER, "HandleAccountRemove: " + connection.getAccountId());
disconnectAndRemoveConnection(connection);
}
private void disconnectAndRemoveConnection(MercuryConnection connection) {
disposable.add(connection.shutdown().subscribeOn(Schedulers.newThread()).subscribe());
removeConnection(connection);
}
private void removeConnection(MercuryConnection connection) {
LOGGER.log(Level.FINER, "Remove Connection: " + connection.getAccountId());
connectionsMap.remove(connection.getAccountId());
connectionDisposables.remove(connection.getAccountId()).dispose();
connectionPoolObservable.onNext(updatePoolState(connectionPoolObservable.getValue()));
}
private ConnectionPoolState updatePoolState(ConnectionPoolState value) {
Map<UUID, ConnectionState> states = value.getConnectionStates();
for (UUID id : connectionsMap.keySet()) {
if (!states.containsKey(id)) {
states.remove(id);
}
}
return new ConnectionPoolState(states);
}
public void doShutdownAllConnections() {
for (MercuryConnection connection : getConnections()) {
connection.doShutdown();
}
}
}