wildfire chat server source initial submit

This commit is contained in:
heavyrain2012
2019-03-16 17:04:23 +08:00
parent e59217bd10
commit cab3ca0916
1466 changed files with 528862 additions and 15919 deletions
@@ -0,0 +1,57 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class ChannelLoader implements MapStore<String, WFCMessage.ChannelInfo> {
@Override
public void store(String s, WFCMessage.ChannelInfo channelInfo) {
getDatabaseStore().updateChannelInfo(channelInfo);
}
@Override
public void storeAll(Map<String, WFCMessage.ChannelInfo> map) {
}
@Override
public void delete(String s) {
getDatabaseStore().removeChannelInfo(s);
}
@Override
public void deleteAll(Collection<String> collection) {
}
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
@Override
public WFCMessage.ChannelInfo load(String key) {
return getDatabaseStore().getPersistChannelInfo(key);
}
@Override
public Map<String, WFCMessage.ChannelInfo> loadAll(Collection<String> keys) {
return null;
}
@Override
public Iterable<String> loadAllKeys() {
return null;
}
}
@@ -0,0 +1,78 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class ChatroomLoader implements MapStore<String, WFCMessage.ChatroomInfo> {
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
/**
* Loads the value of a given key. If distributed map doesn't contain the value
* for the given key then Hazelcast will call implementation's load (key) method
* to obtain the value. Implementation can use any means of loading the given key;
* such as an O/R mapping tool, simple SQL or reading a file etc.
*
* @param key@return value of the key, value cannot be null
*/
@Override
public WFCMessage.ChatroomInfo load(String key) {
return getDatabaseStore().getPersistChatroomInfo(key);
}
/**
* Loads given keys. This is batch load operation so that implementation can
* optimize the multiple loads.
* <p>
* For any key in the input keys, there should be a single mapping in the resulting map. Also the resulting
* map should not have any keys that are not part of the input keys.
* <p>
* The given collection should not contain any <code>null</code> keys.
* The returned Map should not contain any <code>null</code> keys or values.
*
* @param keys keys of the values entries to load
* @return map of loaded key-value pairs.
*/
@Override
public Map<String, WFCMessage.ChatroomInfo> loadAll(Collection<String> keys) {
return null;
}
@Override
public Iterable<String> loadAllKeys() {
return null;
}
@Override
public void store(String s, WFCMessage.ChatroomInfo chatroomInfo) {
getDatabaseStore().updateChatroomInfo(s, chatroomInfo);
}
@Override
public void storeAll(Map<String, WFCMessage.ChatroomInfo> map) {
}
@Override
public void delete(String s) {
getDatabaseStore().removeChatroomInfo(s);
}
@Override
public void deleteAll(Collection<String> collection) {
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.core.MapLoader;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class GroupLoader implements MapLoader<String, WFCMessage.GroupInfo> {
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
/**
* Loads the value of a given key. If distributed map doesn't contain the value
* for the given key then Hazelcast will call implementation's load (key) method
* to obtain the value. Implementation can use any means of loading the given key;
* such as an O/R mapping tool, simple SQL or reading a file etc.
*
* @param key@return value of the key, value cannot be null
*/
@Override
public WFCMessage.GroupInfo load(String key) {
return getDatabaseStore().getPersistGroupInfo(key);
}
/**
* Loads given keys. This is batch load operation so that implementation can
* optimize the multiple loads.
* <p>
* For any key in the input keys, there should be a single mapping in the resulting map. Also the resulting
* map should not have any keys that are not part of the input keys.
* <p>
* The given collection should not contain any <code>null</code> keys.
* The returned Map should not contain any <code>null</code> keys or values.
*
* @param keys keys of the values entries to load
* @return map of loaded key-value pairs.
*/
@Override
public Map<String, WFCMessage.GroupInfo> loadAll(Collection<String> keys) {
return null;
}
/**
* Loads all of the keys from the store. The returned {@link Iterable} may return the keys lazily
* by loading them in batches. The {@link Iterator} of this {@link Iterable} may implement the
* {@link Closeable} interface in which case it will be closed once iteration is over.
* This is intended for releasing resources such as closing a JDBC result set.
* <p>
* The returned Iterable should not contain any <code>null</code> keys.
*
* @return all the keys. Keys inside the Iterable cannot be null.
*/
@Override
public Iterable<String> loadAllKeys() {
return null;
}
}
@@ -1,65 +0,0 @@
package io.moquette.persistence;
import io.moquette.BrokerConstants;
import io.moquette.broker.IQueueRepository;
import io.moquette.broker.IRetainedRepository;
import io.moquette.broker.ISubscriptionsRepository;
import io.moquette.broker.config.IConfig;
import org.h2.mvstore.MVStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class H2Builder {
private static final Logger LOG = LoggerFactory.getLogger(H2Builder.class);
private final String storePath;
private final int autosaveInterval; // in seconds
private final ScheduledExecutorService scheduler;
private MVStore mvStore;
public H2Builder(IConfig props, ScheduledExecutorService scheduler) {
this.storePath = props.getProperty(BrokerConstants.PERSISTENT_STORE_PROPERTY_NAME, "");
final String autosaveProp = props.getProperty(BrokerConstants.AUTOSAVE_INTERVAL_PROPERTY_NAME, "30");
this.autosaveInterval = Integer.parseInt(autosaveProp);
this.scheduler = scheduler;
}
@SuppressWarnings("FutureReturnValueIgnored")
public H2Builder initStore() {
LOG.info("Initializing H2 store");
if (storePath == null || storePath.isEmpty()) {
throw new IllegalArgumentException("H2 store path can't be null or empty");
}
mvStore = new MVStore.Builder()
.fileName(storePath)
.autoCommitDisabled()
.open();
LOG.trace("Scheduling H2 commit task");
scheduler.scheduleWithFixedDelay(() -> {
LOG.trace("Committing to H2");
mvStore.commit();
}, autosaveInterval, autosaveInterval, TimeUnit.SECONDS);
return this;
}
public ISubscriptionsRepository subscriptionsRepository() {
return new H2SubscriptionsRepository(mvStore);
}
public void closeStore() {
mvStore.close();
}
public IQueueRepository queueRepository() {
return new H2QueueRepository(mvStore);
}
public IRetainedRepository retainedRepository() {
return new H2RetainedRepository(mvStore);
}
}
@@ -1,104 +0,0 @@
/*
* Copyright (c) 2012-2018 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import java.util.AbstractQueue;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
class H2PersistentQueue<T> extends AbstractQueue<T> {
private final MVMap<Long, T> queueMap;
private final MVMap<String, Long> metadataMap;
private final AtomicLong head;
private final AtomicLong tail;
H2PersistentQueue(MVStore store, String queueName) {
if (queueName == null || queueName.isEmpty()) {
throw new IllegalArgumentException("queueName parameter can't be empty or null");
}
this.queueMap = store.openMap("queue_" + queueName);
this.metadataMap = store.openMap("queue_" + queueName + "_meta");
//setup head index
long headIdx = 0L;
if (this.metadataMap.containsKey("head")) {
headIdx = this.metadataMap.get("head");
} else {
this.metadataMap.put("head", headIdx);
}
this.head = new AtomicLong(headIdx);
//setup tail index
long tailIdx = 0L;
if (this.metadataMap.containsKey("tail")) {
tailIdx = this.metadataMap.get("tail");
} else {
this.metadataMap.put("tail", tailIdx);
}
this.tail = new AtomicLong(tailIdx);
}
static void dropQueue(MVStore store, String queueName) {
store.removeMap(store.openMap("queue_" + queueName));
store.removeMap(store.openMap("queue_" + queueName + "_meta"));
}
@Override
public Iterator<T> iterator() {
return null;
}
@Override
public int size() {
return this.head.intValue() - this.tail.intValue();
}
@Override
public boolean offer(T t) {
if (t == null) {
throw new NullPointerException("Inserted element can't be null");
}
final long nextHead = head.getAndIncrement();
this.queueMap.put(nextHead, t);
this.metadataMap.put("head", nextHead + 1);
return true;
}
@Override
public T poll() {
if (head.equals(tail)) {
return null;
}
final long nextTail = tail.getAndIncrement();
final T tail = this.queueMap.get(nextTail);
queueMap.remove(nextTail);
this.metadataMap.put("tail", nextTail + 1);
return tail;
}
@Override
public T peek() {
if (head.equals(tail)) {
return null;
}
return this.queueMap.get(tail.get());
}
}
@@ -1,25 +0,0 @@
package io.moquette.persistence;
import io.moquette.broker.IQueueRepository;
import io.moquette.broker.SessionRegistry;
import org.h2.mvstore.MVStore;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class H2QueueRepository implements IQueueRepository {
private MVStore mvStore;
public H2QueueRepository(MVStore mvStore) {
this.mvStore = mvStore;
}
@Override
public Queue<SessionRegistry.EnqueuedMessage> createQueue(String cli, boolean clean) {
if (!clean) {
return new H2PersistentQueue<>(mvStore, cli);
}
return new ConcurrentLinkedQueue<>();
}
}
@@ -1,55 +0,0 @@
package io.moquette.persistence;
import io.moquette.broker.IRetainedRepository;
import io.moquette.broker.RetainedMessage;
import io.moquette.broker.subscriptions.Topic;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.mqtt.MqttPublishMessage;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class H2RetainedRepository implements IRetainedRepository {
private final MVMap<Topic, RetainedMessage> queueMap;
public H2RetainedRepository(MVStore mvStore) {
this.queueMap = mvStore.openMap("retained_store");
}
@Override
public void cleanRetained(Topic topic) {
queueMap.remove(topic);
}
@Override
public void retain(Topic topic, MqttPublishMessage msg) {
final ByteBuf payload = msg.content();
byte[] rawPayload = new byte[payload.readableBytes()];
payload.getBytes(0, rawPayload);
final RetainedMessage toStore = new RetainedMessage(msg.fixedHeader().qosLevel(), rawPayload);
queueMap.put(topic, toStore);
}
@Override
public boolean isEmpty() {
return queueMap.isEmpty();
}
@Override
public List<RetainedMessage> retainedOnTopic(String topic) {
final Topic searchTopic = new Topic(topic);
final List<RetainedMessage> matchingMessages = new ArrayList<>();
for (Map.Entry<Topic, RetainedMessage> entry : queueMap.entrySet()) {
final Topic scanTopic = entry.getKey();
if (searchTopic.match(scanTopic)) {
matchingMessages.add(entry.getValue());
}
}
return matchingMessages;
}
}
@@ -1,48 +0,0 @@
package io.moquette.persistence;
import io.moquette.broker.ISubscriptionsRepository;
import io.moquette.broker.subscriptions.Subscription;
import org.h2.mvstore.Cursor;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class H2SubscriptionsRepository implements ISubscriptionsRepository {
private static final Logger LOG = LoggerFactory.getLogger(H2SubscriptionsRepository.class);
private static final String SUBSCRIPTIONS_MAP = "subscriptions";
private MVMap<String, Subscription> subscriptions;
H2SubscriptionsRepository(MVStore mvStore) {
this.subscriptions = mvStore.openMap(SUBSCRIPTIONS_MAP);
}
@Override
public List<Subscription> listAllSubscriptions() {
LOG.debug("Retrieving existing subscriptions");
List<Subscription> results = new ArrayList<>();
Cursor<String, Subscription> mapCursor = subscriptions.cursor(null);
while (mapCursor.hasNext()) {
String subscriptionStr = mapCursor.next();
results.add(mapCursor.getValue());
}
LOG.debug("Loaded {} subscriptions", results.size());
return results;
}
@Override
public void addNewSubscription(Subscription subscription) {
subscriptions.put(subscription.getTopicFilter() + "-" + subscription.getClientId(), subscription);
}
@Override
public void removeSubscription(String topicFilter, String clientID) {
subscriptions.remove(topicFilter + "-" + clientID);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,586 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.server.Constants;
import io.moquette.server.Server;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.ISessionsStore;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.ErrorCode;
import win.liyufan.im.UserSettingScope;
import win.liyufan.im.Utility;
import java.util.*;
import java.util.concurrent.*;
public class MemorySessionStore implements ISessionsStore {
private static int dumy = 1;
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
public static class Session implements Comparable<Session>{
final String clientID;
String username;
private String appName;
private String deviceToken;
private String voipDeviceToken;
private String secret;
private String dbSecret;
private Map<String, String> slientConvs = new ConcurrentHashMap<>();
private boolean globalSlient = false;
private boolean hiddenNotifyDetail = false;
private long lastActiveTime;
private long lastChatroomActiveTime;
private volatile int unReceivedMsgs;
public long getLastActiveTime() {
return lastActiveTime;
}
public long getLastChatroomActiveTime() {
return lastChatroomActiveTime;
}
public void refreshLastChatroomActiveTime() {
this.lastChatroomActiveTime = System.currentTimeMillis();
}
public void refreshLastActiveTime() {
this.lastActiveTime = System.currentTimeMillis();
}
public int getUnReceivedMsgs() {
return unReceivedMsgs;
}
public void setUnReceivedMsgs(int unReceivedMsgs) {
this.unReceivedMsgs = unReceivedMsgs;
}
public int getPushType() {
return pushType;
}
public void setPushType(int pushType) {
this.pushType = pushType;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getDeviceVersion() {
return deviceVersion;
}
public void setDeviceVersion(String deviceVersion) {
this.deviceVersion = deviceVersion;
}
public String getPhoneName() {
return phoneName;
}
public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getCarrierName() {
return carrierName;
}
public void setCarrierName(String carrierName) {
this.carrierName = carrierName;
}
public long getUpdateDt() {
return updateDt;
}
public void setUpdateDt(long updateDt) {
this.updateDt = updateDt;
}
private int pushType;
private int platform;
private String deviceName;
private String deviceVersion;
private String phoneName;
private String language;
private String carrierName;
private long updateDt;
final ClientSession clientSession;
final BlockingQueue<StoredMessage> queue = new ArrayBlockingQueue<>(Constants.MAX_MESSAGE_QUEUE);
final Map<Integer, StoredMessage> secondPhaseStore = new ConcurrentHashMap<>();
final Map<Integer, StoredMessage> outboundFlightMessages =
Collections.synchronizedMap(new HashMap<Integer, StoredMessage>());
final Map<Integer, StoredMessage> inboundFlightMessages = new ConcurrentHashMap<>();
public Session(String username, String clientID, ClientSession clientSession) {
this.clientID = clientID;
this.clientSession = clientSession;
this.username = username;
}
public boolean isHiddenNotifyDetail() {
return hiddenNotifyDetail;
}
public void setHiddenNotifyDetail(boolean hiddenNotifyDetail) {
this.hiddenNotifyDetail = hiddenNotifyDetail;
}
public Map<String, String> getSlientConvs() {
return slientConvs;
}
public boolean isGlobalSlient() {
return globalSlient;
}
public void setGlobalSlient(boolean globalSlient) {
this.globalSlient = globalSlient;
}
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getDeviceToken() {
return deviceToken;
}
public void setDeviceToken(String deviceToken) {
this.deviceToken = deviceToken;
}
public int getPlatform() {
return platform;
}
public void setPlatform(int platform) {
this.platform = platform;
}
public String getClientID() {
return clientID;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public ClientSession getClientSession() {
return clientSession;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getDbSecret() {
return dbSecret;
}
public void setDbSecret(String dbSecret) {
this.dbSecret = dbSecret;
}
public String getVoipDeviceToken() {
return voipDeviceToken;
}
public void setVoipDeviceToken(String voipDeviceToken) {
this.voipDeviceToken = voipDeviceToken;
}
@Override
public int compareTo(Session o) {
// TODO Auto-generated method stub
if (clientID.equals(o.clientID) && username.equals(o.username)) {
return 0;
}
if (clientID.equals(o.clientID)) {
return username.compareTo(o.username);
} else {
return clientID.compareTo(o.clientID);
}
}
}
private final Map<String, Session> sessions = new ConcurrentHashMap<>();
private final Map<String, ConcurrentSkipListSet<String>> userSessions = new ConcurrentHashMap<>();
private final Server mServer;
private final DatabaseStore databaseStore;
public MemorySessionStore(Server server, DatabaseStore databaseStore) {
mServer = server;
this.databaseStore = databaseStore;
}
@Override
public Session getSession(String clientID) {
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("Can't find the session for client <{}>", clientID);
}
return session;
}
@Override
public void initStore() {
}
@Override
public boolean contains(String clientID) {
return sessions.containsKey(clientID);
}
@Override
public void updateSessionToken(Session session, boolean voip) {
databaseStore.updateSessionToken(session.getUsername(), session.getClientID(), voip ? session.getVoipDeviceToken() : session.getDeviceToken(), voip);
}
@Override
public Session createUserSession(String username, String clientID) {
LOG.debug("createUserSession for client <{}>, user <{}>", clientID, username);
ClientSession clientSession = new ClientSession(clientID, this);
Session session = databaseStore.getSession(username, clientID, clientSession);
if (session == null) {
session = databaseStore.createSession(username, clientID, clientSession);
}
return session;
}
@Override
public ErrorCode createNewSession(String username, String clientID, boolean cleanSession) {
LOG.debug("createNewSession for client <{}>", clientID);
Session session = sessions.get(clientID);
if (session != null) {
LOG.error("already exists a session for client <{}>, bad condition", clientID);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
ClientSession clientSession = new ClientSession(clientID, this);
session = databaseStore.getSession(username, clientID, clientSession);
if (session == null) {
session = databaseStore.createSession(username, clientID, clientSession);
}
List<WFCMessage.UserSettingEntry> settings = mServer.getStore().messagesStore().getUserSetting(username, UserSettingScope.kUserSettingConversationSilent);
for (WFCMessage.UserSettingEntry entry : settings) {
if (!StringUtil.isNullOrEmpty(entry.getValue()) && !entry.getValue().equals("0")) {
session.slientConvs.put(entry.getKey(), entry.getValue());
break;
}
}
List<WFCMessage.UserSettingEntry> settings2 = mServer.getStore().messagesStore().getUserSetting(username, UserSettingScope.kUserSettingGlobalSilent);
for (WFCMessage.UserSettingEntry entry : settings2) {
if (!StringUtil.isNullOrEmpty(entry.getValue()) && !entry.getValue().equals("0")) {
session.globalSlient = true;
break;
}
}
sessions.put(clientID, session);
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
sessionSet.add(clientID);
return ErrorCode.ERROR_CODE_SUCCESS;
}
@Override
public ClientSession updateExistSession(String username, String clientID, WFCMessage.RouteRequest endpoint, boolean cleanSession) {
LOG.debug("updateExistSession for client <{}>", clientID);
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("already exists a session for client <{}>, bad condition", clientID);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
if (!session.getUsername().equals(username)) {
ConcurrentSkipListSet<String> sessionSet = userSessions.get(session.getUsername());
if(sessionSet != null) {
sessionSet.remove(clientID);
}
}
session.setUsername(username);
sessions.put(clientID, session);
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
sessionSet.add(clientID);
if (endpoint != null) {
databaseStore.updateSession(username, clientID, session, endpoint);
}
return session.clientSession;
}
@Override
public Session sessionForClientAndUser(String username, String clientID) {
Session session = sessions.get(clientID);
if (session != null) {
if (session.getUsername().equals(username)) {
return session;
} else {
cleanSession(clientID);
}
}
return null;
}
@Override
public ClientSession sessionForClient(String clientID) {
if (!sessions.containsKey(clientID)) {
LOG.error("Can't find the session for client <{}>", clientID);
return null;
}
Session session = sessions.get(clientID);
ClientSession clientSession = new ClientSession(clientID, this);
return clientSession;
}
@Override
public Collection<Session> sessionForUser(String username) {
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<String>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
ArrayList<Session> out = new ArrayList<>();
for (String clientId : sessionSet
) {
Session session = sessions.get(clientId);
if (session != null && session.getUsername().equals(username)) {
out.add(session);
}
}
return out;
}
@Override
public Collection<ClientSession> getAllSessions() {
Collection<ClientSession> result = new ArrayList<>();
for (Session entry : sessions.values()) {
result.add(new ClientSession(entry.clientID, this));
}
return result;
}
@Override
public StoredMessage inFlightAck(String clientID, int messageID) {
return getSession(clientID).outboundFlightMessages.remove(messageID);
}
@Override
public void inFlight(String clientID, int messageID, StoredMessage msg) {
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("Can't find the session for client <{}>", clientID);
return;
}
session.outboundFlightMessages.put(messageID, msg);
}
/**
* Return the next valid packetIdentifier for the given client session.
*/
@Override
public int nextPacketID(String clientID) {
if (!sessions.containsKey(clientID)) {
LOG.error("Can't find the session for client <{}>", clientID);
return -1;
}
Map<Integer, StoredMessage> m = sessions.get(clientID).outboundFlightMessages;
int maxId = m.keySet().isEmpty() ? 0 : Collections.max(m.keySet());
int nextPacketId = (maxId + 1) % 0xFFFF;
m.put(nextPacketId, null);
return nextPacketId;
}
@Override
public BlockingQueue<StoredMessage> queue(String clientID) {
if (!sessions.containsKey(clientID)) {
LOG.error("Can't find the session for client <{}>", clientID);
return null;
}
return sessions.get(clientID).queue;
}
@Override
public void dropQueue(String clientID) {
sessions.get(clientID).queue.clear();
}
@Override
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID, StoredMessage msg) {
LOG.info("Moving msg inflight second phase store, clientID <{}> messageID {}", clientID, messageID);
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("Can't find the session for client <{}>", clientID);
return;
}
session.secondPhaseStore.put(messageID, msg);
session.outboundFlightMessages.put(messageID, msg);
}
@Override
public StoredMessage secondPhaseAcknowledged(String clientID, int messageID) {
LOG.info("Acknowledged message in second phase, clientID <{}> messageID {}", clientID, messageID);
return getSession(clientID).secondPhaseStore.remove(messageID);
}
@Override
public int getInflightMessagesNo(String clientID) {
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("Can't find the session for client <{}>", clientID);
return 0;
}
return session.inboundFlightMessages.size() + session.secondPhaseStore.size()
+ session.outboundFlightMessages.size();
}
@Override
public StoredMessage inboundInflight(String clientID, int messageID) {
return getSession(clientID).inboundFlightMessages.get(messageID);
}
@Override
public void markAsInboundInflight(String clientID, int messageID, StoredMessage msg) {
if (!sessions.containsKey(clientID))
LOG.error("Can't find the session for client <{}>", clientID);
sessions.get(clientID).inboundFlightMessages.put(messageID, msg);
}
@Override
public int getPendingPublishMessagesNo(String clientID) {
if (!sessions.containsKey(clientID)) {
LOG.error("Can't find the session for client <{}>", clientID);
return 0;
}
return sessions.get(clientID).queue.size();
}
@Override
public int getSecondPhaseAckPendingMessages(String clientID) {
if (!sessions.containsKey(clientID)) {
LOG.error("Can't find the session for client <{}>", clientID);
return 0;
}
return sessions.get(clientID).secondPhaseStore.size();
}
@Override
public void cleanSession(String clientID) {
LOG.error("Fooooooooo <{}>", clientID);
Session session = sessions.get(clientID);
if (session == null) {
LOG.error("Can't find the session for client <{}>", clientID);
return;
}
ConcurrentSkipListSet<String> sessionSet = userSessions.get(session.username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(session.username, sessionSet);
}
sessionSet = userSessions.get(session.username);
sessionSet.remove(clientID);
// remove also the messages stored of type QoS1/2
LOG.info("Removing stored messages with QoS 1 and 2. ClientId={}", clientID);
session.secondPhaseStore.clear();
session.outboundFlightMessages.clear();
session.inboundFlightMessages.clear();
LOG.info("Wiping existing subscriptions. ClientId={}", clientID);
//remove also the enqueued messages
dropQueue(clientID);
// TODO this missing last step breaks the junit test
sessions.remove(clientID);
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import cn.wildfirechat.server.ThreadPoolExecutorWrapper;
import io.moquette.server.Server;
import io.moquette.server.config.IConfig;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.IStore;
import java.util.concurrent.ScheduledExecutorService;
public class MemoryStorageService implements IStore {
private MemorySessionStore m_sessionsStore;
private MemoryMessagesStore m_messagesStore;
public MemoryStorageService(IConfig props, ThreadPoolExecutorWrapper scheduler, Server server) {
DatabaseStore databaseStore = new DatabaseStore(scheduler);
m_messagesStore = new MemoryMessagesStore(server, databaseStore);
m_sessionsStore = new MemorySessionStore(server, databaseStore);
}
@Override
public IMessagesStore messagesStore() {
return m_messagesStore;
}
@Override
public ISessionsStore sessionsStore() {
return m_sessionsStore;
}
@Override
public void initStore() {
m_messagesStore.initStore();
m_sessionsStore.initStore();
}
@Override
public void close() {
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2012-2018 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import io.moquette.broker.ISubscriptionsRepository;
import io.moquette.broker.subscriptions.Subscription;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MemorySubscriptionsRepository implements ISubscriptionsRepository {
private final List<Subscription> subscriptions = new ArrayList<>();
@Override
public List<Subscription> listAllSubscriptions() {
return Collections.unmodifiableList(subscriptions);
}
@Override
public void addNewSubscription(Subscription subscription) {
subscriptions.add(subscription);
}
@Override
public void removeSubscription(String topic, String clientID) {
subscriptions.stream()
.filter(s -> s.getTopicFilter().toString().equals(topic) && s.getClientId().equals(clientID))
.findFirst()
.ifPresent(subscriptions::remove);
}
}
@@ -0,0 +1,87 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import win.liyufan.im.MessageBundle;
import java.util.Collection;
import java.util.Map;
public class MessageLoader implements MapStore<Long, MessageBundle> {
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
/**
* Loads the value of a given key. If distributed map doesn't contain the value
* for the given key then Hazelcast will call implementation's load (key) method
* to obtain the value. Implementation can use any means of loading the given key;
* such as an O/R mapping tool, simple SQL or reading a file etc.
*
* @param key@return value of the key, value cannot be null
*/
@Override
public MessageBundle load(Long key) {
return getDatabaseStore().getMessage(key);
}
/**
* Loads given keys. This is batch load operation so that implementation can
* optimize the multiple loads.
* <p>
* For any key in the input keys, there should be a single mapping in the resulting map. Also the resulting
* map should not have any keys that are not part of the input keys.
* <p>
* The given collection should not contain any <code>null</code> keys.
* The returned Map should not contain any <code>null</code> keys or values.
*
* @param keys keys of the values entries to load
* @return map of loaded key-value pairs.
*/
@Override
public Map<Long, MessageBundle> loadAll(Collection<Long> keys) {
return getDatabaseStore().getMessages(keys);
}
/**
* Loads all of the keys from the store. The returned {@link Iterable} may return the keys lazily
* by loading them in batches. The {@link Iterator} of this {@link Iterable} may implement the
* {@link Closeable} interface in which case it will be closed once iteration is over.
* This is intended for releasing resources such as closing a JDBC result set.
* <p>
* The returned Iterable should not contain any <code>null</code> keys.
*
* @return all the keys. Keys inside the Iterable cannot be null.
*/
@Override
public Iterable<Long> loadAllKeys() {
return null;
}
@Override
public void store(Long aLong, MessageBundle messageBundle) {
getDatabaseStore().persistMessage(messageBundle.getMessage(), false);
}
@Override
public void storeAll(Map<Long, MessageBundle> map) {
}
@Override
public void delete(Long aLong) {
}
@Override
public void deleteAll(Collection<Long> collection) {
}
}
@@ -0,0 +1,88 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import io.moquette.persistence.remote.*;
import io.moquette.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.ErrorCode;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class RPCCenter {
private static final Logger LOG = LoggerFactory.getLogger(RPCCenter.class);
public static final String CHECK_USER_ONLINE_REQUEST = "check_user_online";
public static final String KICKOFF_USER_REQUEST = "kickoff_user";
private Server server;
public ConcurrentHashMap<Integer, RequestInfo> requestMap = new ConcurrentHashMap<>();
private AtomicInteger aiRequestId = new AtomicInteger(1);
public ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
public void init(Server server) {
this.server = server;
}
public interface Callback {
void onSuccess(byte[] response);
void onError(ErrorCode errorCode);
void onTimeout();
Executor getResponseExecutor();
}
private static RPCCenter instance;
public static RPCCenter getInstance() {
if (instance == null) {
instance = new RPCCenter();
}
return instance;
}
protected RPCCenter() {}
public void sendRequest(String fromUser, String clientId, String request, byte[] message, String target, TargetEntry.Type type, Callback callback) {
int requestId = 0;
if (callback != null) {
requestId = aiRequestId.incrementAndGet();
if (requestId == Integer.MAX_VALUE) {
if(!aiRequestId.compareAndSet(Integer.MAX_VALUE, 1)) {
requestId = aiRequestId.incrementAndGet();
}
}
requestMap.put(requestId, new RequestInfo(fromUser, clientId, callback, message, requestId, request));
}
server.internalRpcMsg(fromUser, clientId, message, requestId, "", request);
}
public void sendResponse(int errorCode, byte[] message, String toUuid, int requestId) {
LOG.debug("send async reponse to {} with requestId {}", toUuid, requestId);
if (requestId > 0) {
RequestInfo info = requestMap.remove(requestId);
LOG.debug("receive async reponse requestId {}, errorCode {}", requestId, errorCode);
if(info != null) {
info.future.cancel(true);
if (info.callback != null) {
info.callback.getResponseExecutor().execute(() -> {
if (errorCode == 0) {
info.callback.onSuccess(message);
} else {
info.callback.onError(ErrorCode.fromCode(errorCode));
}
});
} else {
}
}
}
}
}
@@ -0,0 +1,57 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class RobotLoader implements MapStore<String, WFCMessage.Robot> {
@Override
public void store(String s, WFCMessage.Robot robot) {
getDatabaseStore().updateRobot(robot);
}
@Override
public void storeAll(Map<String, WFCMessage.Robot> map) {
}
@Override
public void delete(String s) {
getDatabaseStore().deleteRobot(s);
}
@Override
public void deleteAll(Collection<String> collection) {
}
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
@Override
public WFCMessage.Robot load(String key) {
return getDatabaseStore().getRobot(key);
}
@Override
public Map<String, WFCMessage.Robot> loadAll(Collection<String> keys) {
return null;
}
@Override
public Iterable<String> loadAllKeys() {
return null;
}
}
@@ -0,0 +1,24 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
public class TargetEntry {
public TargetEntry(Type type, String target) {
this.type = type;
this.target = target;
}
public enum Type {
TARGET_TYPE_USER,
TARGET_TYPE_CHATROOM,
TARGET_TYPE_MASTER_NODE,
}
public Type type;
public String target;
}
@@ -0,0 +1,21 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import java.io.Serializable;
public class UserClientEntry implements Serializable {
public UserClientEntry(String userId, String clientId) {
this.userId = userId;
this.clientId = clientId;
}
public String userId;
public String clientId;
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.core.MapLoader;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class UserLoader implements MapStore<String, WFCMessage.User> {
@Override
public void store(String s, WFCMessage.User user) {
getDatabaseStore().updateUser(user);
}
@Override
public void storeAll(Map<String, WFCMessage.User> map) {
}
@Override
public void delete(String s) {
getDatabaseStore().deleteUser(s);
}
@Override
public void deleteAll(Collection<String> collection) {
}
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
@Override
public WFCMessage.User load(String key) {
return getDatabaseStore().getPersistUser(key);
}
@Override
public Map<String, WFCMessage.User> loadAll(Collection<String> keys) {
return null;
}
@Override
public Iterable<String> loadAllKeys() {
return null;
}
}
@@ -0,0 +1,56 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence;
import com.hazelcast.core.MapStore;
import io.moquette.server.Server;
import java.util.Collection;
import java.util.Map;
public class UserStatusLoader implements MapStore<String, Integer> {
@Override
public void store(String s, Integer status) {
getDatabaseStore().updateUserStatus(s, status);
}
@Override
public void storeAll(Map<String, Integer> map) {
}
@Override
public void delete(String s) {
getDatabaseStore().deleteUser(s);
}
@Override
public void deleteAll(Collection<String> collection) {
}
private DatabaseStore getDatabaseStore() {
return Server.getServer().getStore().messagesStore().getDatabaseStore();
}
@Override
public Integer load(String key) {
return getDatabaseStore().getUserStatus(key);
}
@Override
public Map<String, Integer> loadAll(Collection<String> keys) {
return null;
}
@Override
public Iterable<String> loadAllKeys() {
return null;
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.persistence.remote;
import io.moquette.persistence.RPCCenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class RequestInfo {
private static final Logger LOG = LoggerFactory.getLogger(RequestInfo.class);
public final RPCCenter.Callback callback;
final byte[] message;
final int requestId;
final String fromUser;
final String request;
final String clientId;
public final ScheduledFuture future;
public RequestInfo(String fromUser, String clientId, RPCCenter.Callback callback, byte[] message, int requestId, String request) {
this.callback = callback;
this.message = message;
this.requestId = requestId;
this.fromUser = fromUser;
this.clientId = clientId;
this.request = request;
this.future = RPCCenter.getInstance().scheduledExecutorService.schedule(() -> {
RequestInfo info = RPCCenter.getInstance().requestMap.remove(requestId);
if (info != null) {
LOG.error("Request timeout. fromUser {}, cliendId {}, requestId {}, request {}", fromUser, clientId, requestId, request);
info.callback.getResponseExecutor().execute(() -> {
info.callback.onTimeout();
});
}
}, 5, TimeUnit.SECONDS);
}
}