wildfire chat server source initial submit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user