final update
This commit is contained in:
BIN
Bungee & Mineplexer/Mineplexer.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer.jar
Normal file
Binary file not shown.
3
Bungee & Mineplexer/Mineplexer/.idea/.gitignore
generated
vendored
Normal file
3
Bungee & Mineplexer/Mineplexer/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
9
Bungee & Mineplexer/Mineplexer/.idea/Mineplexer.iml
generated
Normal file
9
Bungee & Mineplexer/Mineplexer/.idea/Mineplexer.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
17
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_Bungee_Mineplexer_jar.xml
generated
Normal file
17
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_Bungee_Mineplexer_jar.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="Mineplex.Bungee.Mineplexer:jar">
|
||||
<output-path>$PROJECT_DIR$/../</output-path>
|
||||
<root id="archive" name="Mineplexer.jar">
|
||||
<element id="module-output" name="Mineplex.Cache" />
|
||||
<element id="module-output" name="Mineplex.ServerData" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/gson-2.2.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/commons-pool2-2.9.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/jedis-2.8.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/httpclient-4.5.2.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/commons-dbcp2-2.0.1.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/Libraries/jooq-3.5.2.jar" path-in-jar="/" />
|
||||
<element id="module-output" name="Mineplex.Bungee.Mineplexer" />
|
||||
<element id="file-copy" path="$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/plugin.yml" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
9
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_Cache_jar.xml
generated
Normal file
9
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_Cache_jar.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="Mineplex.Cache:jar">
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/Mineplex_Cache_jar</output-path>
|
||||
<root id="archive" name="Mineplex.Cache.jar">
|
||||
<element id="module-output" name="Mineplex.Cache" />
|
||||
<element id="module-output" name="Mineplex.ServerData" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
8
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_ServerData_jar.xml
generated
Normal file
8
Bungee & Mineplexer/Mineplexer/.idea/artifacts/Mineplex_ServerData_jar.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="Mineplex.ServerData:jar">
|
||||
<output-path>$PROJECT_DIR$/out/artifacts/Mineplex_ServerData_jar</output-path>
|
||||
<root id="archive" name="Mineplex.ServerData.jar">
|
||||
<element id="module-output" name="Mineplex.ServerData" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
6
Bungee & Mineplexer/Mineplexer/.idea/misc.xml
generated
Normal file
6
Bungee & Mineplexer/Mineplexer/.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8 (2)" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
11
Bungee & Mineplexer/Mineplexer/.idea/modules.xml
generated
Normal file
11
Bungee & Mineplexer/Mineplexer/.idea/modules.xml
generated
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/Mineplex.Bungee.Mineplexer.iml" filepath="$PROJECT_DIR$/Mineplex.Bungee.Mineplexer/Mineplex.Bungee.Mineplexer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/Mineplex.Cache/Mineplex.Cache.iml" filepath="$PROJECT_DIR$/Mineplex.Cache/Mineplex.Cache.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/Mineplex.ServerData/Mineplex.ServerData.iml" filepath="$PROJECT_DIR$/Mineplex.ServerData/Mineplex.ServerData.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Mineplexer.iml" filepath="$PROJECT_DIR$/.idea/Mineplexer.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
BIN
Bungee & Mineplexer/Mineplexer/Libraries/Bungeecord.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/Bungeecord.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/commons-dbcp2-2.0.1.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/commons-dbcp2-2.0.1.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/commons-pool2-2.9.0.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/commons-pool2-2.9.0.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/craftbukkit.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/craftbukkit.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/gson-2.2.1.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/gson-2.2.1.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/httpclient-4.5.2.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/httpclient-4.5.2.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jd-gui-1.6.6.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jd-gui-1.6.6.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jedis-2.8.1.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jedis-2.8.1.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jooq-3.5.2.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jooq-3.5.2.jar
Normal file
Binary file not shown.
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jooq-codegen-3.5.2.jar
Normal file
BIN
Bungee & Mineplexer/Mineplexer/Libraries/jooq-codegen-3.5.2.jar
Normal file
Binary file not shown.
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="Mineplex.Cache" />
|
||||
<orderEntry type="module" module-name="Mineplex.ServerData" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/Bungeecord.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/commons-dbcp2-2.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/commons-pool2-2.9.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/craftbukkit.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/craftbukkit.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/gson-2.2.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/httpclient-4.5.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/jd-gui-1.6.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/jedis-2.8.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/jooq-3.5.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,4 @@
|
||||
name: Mineplexer
|
||||
main: mineplex.bungee.Mineplexer
|
||||
version: 1
|
||||
author: defek7
|
@ -0,0 +1,171 @@
|
||||
package mineplex.bungee;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
public class FileUpdater implements Runnable
|
||||
{
|
||||
private Plugin _plugin;
|
||||
private HashMap<String, String> _jarMd5Map = new HashMap<String, String>();
|
||||
|
||||
private boolean _needUpdate;
|
||||
private boolean _enabled = true;
|
||||
private int _timeTilRestart = 5;
|
||||
|
||||
public FileUpdater(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
|
||||
getPluginMd5s();
|
||||
|
||||
if (new File("IgnoreUpdates.dat").exists())
|
||||
_enabled = false;
|
||||
|
||||
_plugin.getProxy().getScheduler().schedule(_plugin, this, 2L, 2L, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public void checkForNewFiles()
|
||||
{
|
||||
if (_needUpdate || !_enabled)
|
||||
return;
|
||||
|
||||
boolean windows = System.getProperty("os.name").startsWith("Windows");
|
||||
|
||||
File updateDir = new File((windows ? "C:" : File.separator + "home" + File.separator + "mineplex") + File.separator + "update");
|
||||
|
||||
updateDir.mkdirs();
|
||||
|
||||
FilenameFilter statsFilter = new FilenameFilter()
|
||||
{
|
||||
public boolean accept(File paramFile, String paramString)
|
||||
{
|
||||
if (paramString.endsWith("jar"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
for (File f : updateDir.listFiles(statsFilter))
|
||||
{
|
||||
FileInputStream fis = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (_jarMd5Map.containsKey(f.getName()))
|
||||
{
|
||||
fis = new FileInputStream(f);
|
||||
String md5 = DigestUtils.md5Hex(fis);
|
||||
|
||||
if (!md5.equals(_jarMd5Map.get(f.getName())))
|
||||
{
|
||||
System.out.println(f.getName() + " old jar : " + _jarMd5Map.get(f.getName()));
|
||||
System.out.println(f.getName() + " new jar : " + md5);
|
||||
_needUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fis != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getPluginMd5s()
|
||||
{
|
||||
File pluginDir = new File("plugins");
|
||||
|
||||
pluginDir.mkdirs();
|
||||
|
||||
FilenameFilter statsFilter = new FilenameFilter()
|
||||
{
|
||||
public boolean accept(File paramFile, String paramString)
|
||||
{
|
||||
if (paramString.endsWith("jar"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
for (File f : pluginDir.listFiles(statsFilter))
|
||||
{
|
||||
FileInputStream fis = null;
|
||||
|
||||
try
|
||||
{
|
||||
fis = new FileInputStream(f);
|
||||
_jarMd5Map.put(f.getName(), DigestUtils.md5Hex(fis));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fis != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
checkForNewFiles();
|
||||
|
||||
if (_needUpdate)
|
||||
{
|
||||
BungeeCord.getInstance().broadcast(ChatColor.RED + "Connection Node" + ChatColor.DARK_GRAY + ">" + ChatColor.YELLOW + "This connection node will be restarting in " + _timeTilRestart + " minutes.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_timeTilRestart -= 2;
|
||||
|
||||
if (_timeTilRestart < 0 || !_enabled)
|
||||
{
|
||||
BungeeCord.getInstance().stop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package mineplex.bungee;
|
||||
|
||||
import mineplex.bungee.lobbyBalancer.LobbyBalancer;
|
||||
import mineplex.bungee.motd.MotdManager;
|
||||
import mineplex.bungee.playerCount.PlayerCount;
|
||||
import mineplex.bungee.playerStats.PlayerStats;
|
||||
import mineplex.bungee.playerTracker.PlayerTracker;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
public class Mineplexer extends Plugin
|
||||
{
|
||||
@Override
|
||||
public void onEnable() {
|
||||
new MotdManager(this);
|
||||
new LobbyBalancer(this);
|
||||
new PlayerCount(this);
|
||||
//new FileUpdater(this);
|
||||
new PlayerStats(this);
|
||||
new PlayerTracker(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package mineplex.bungee.lobbyBalancer;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.md_5.bungee.api.event.ServerConnectEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.data.MinecraftServer;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import mineplex.serverdata.servers.ServerRepository;
|
||||
|
||||
public class LobbyBalancer implements Listener, Runnable
|
||||
{
|
||||
private Plugin _plugin;
|
||||
private ServerRepository _repository;
|
||||
|
||||
private final Map<LobbyType, List<MinecraftServer>> _sortedLobbyMap = new EnumMap<>(LobbyType.class);
|
||||
private final Map<LobbyType, Integer> _nextIndexMap = new EnumMap<>(LobbyType.class);
|
||||
private static final LobbySorter LOBBY_SORTER = new LobbySorter();
|
||||
private static final Object _serverLock = new Object();
|
||||
|
||||
public LobbyBalancer(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
|
||||
Region region = !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||
_repository = ServerManager.getServerRepository(region);
|
||||
|
||||
run();
|
||||
|
||||
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||
_plugin.getProxy().getScheduler().schedule(_plugin, this, 500L, 500L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerConnect(ServerConnectEvent event)
|
||||
{
|
||||
Arrays.stream(LobbyType.values())
|
||||
.filter(type -> type.getConnectName().equalsIgnoreCase(event.getTarget().getName()))
|
||||
.findFirst()
|
||||
.ifPresent(lobbyType ->
|
||||
{
|
||||
synchronized (_serverLock)
|
||||
{
|
||||
List<MinecraftServer> lobbies = _sortedLobbyMap.get(lobbyType);
|
||||
System.out.println(lobbies.toString());
|
||||
if(lobbies.size() == 0) {
|
||||
event.getPlayer().disconnect("Sorry! There aren't any servers that are currently active right now. Come back later :3");
|
||||
System.out.println("NOTICE! The network doesn't have any active servers. Are we in development status?");
|
||||
return;
|
||||
}
|
||||
int nextIndex = _nextIndexMap.getOrDefault(lobbyType, 0);
|
||||
if (nextIndex >= lobbies.size())
|
||||
{
|
||||
nextIndex = 0;
|
||||
}
|
||||
MinecraftServer server = lobbies.get(nextIndex);
|
||||
event.setTarget(_plugin.getProxy().getServerInfo(server.getName()));
|
||||
server.incrementPlayerCount(1);
|
||||
System.out.println("Sending " + event.getPlayer().getName() + " to " + server.getName() + "(" + server.getPublicAddress() + ")");
|
||||
_nextIndexMap.put(lobbyType, ++nextIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
loadServers();
|
||||
|
||||
for (LobbyType type : LobbyType.values())
|
||||
{
|
||||
if (!_plugin.getProxy().getServers().containsKey(type.getConnectName()))
|
||||
{
|
||||
_plugin.getProxy().getServers().put(type.getConnectName(), _plugin.getProxy().constructServerInfo(type.getConnectName(), new InetSocketAddress("lobby.mineplex.com", 25565), "LobbyBalancer", false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadServers()
|
||||
{
|
||||
Collection<MinecraftServer> servers = _repository.getServerStatuses();
|
||||
synchronized (_serverLock)
|
||||
{
|
||||
long startTime = System.currentTimeMillis();
|
||||
_sortedLobbyMap.clear();
|
||||
for (LobbyType type : LobbyType.values())
|
||||
{
|
||||
_sortedLobbyMap.put(type, new ArrayList<>());
|
||||
}
|
||||
|
||||
for (MinecraftServer server : servers)
|
||||
{
|
||||
if (server.getName() == null)
|
||||
continue;
|
||||
|
||||
InetSocketAddress socketAddress = new InetSocketAddress(server.getPublicAddress(), server.getPort());
|
||||
_plugin.getProxy().getServers().put(server.getName(), _plugin.getProxy().constructServerInfo(server.getName(), socketAddress, "LobbyBalancer", false));
|
||||
|
||||
if (server.getMotd() != null && server.getMotd().contains("Restarting"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Arrays.stream(LobbyType.values())
|
||||
.filter(type -> server.getName().toUpperCase().startsWith(type.getUppercasePrefix()))
|
||||
.findFirst()
|
||||
.ifPresent(type -> _sortedLobbyMap.get(type).add(server));
|
||||
}
|
||||
|
||||
_sortedLobbyMap.values().forEach(lobbies -> Collections.sort(lobbies, LOBBY_SORTER));
|
||||
|
||||
long timeSpentInLock = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (timeSpentInLock > 50)
|
||||
System.out.println("[==] TIMING [==] Locked loading servers for " + timeSpentInLock + "ms");
|
||||
|
||||
_nextIndexMap.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package mineplex.bungee.lobbyBalancer;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import mineplex.serverdata.data.MinecraftServer;
|
||||
|
||||
public class LobbySorter implements Comparator<MinecraftServer>
|
||||
{
|
||||
@Override
|
||||
public int compare(MinecraftServer first, MinecraftServer second)
|
||||
{
|
||||
if (second.getPlayerCount() == 999)
|
||||
return -1;
|
||||
|
||||
if (first.getPlayerCount() == 999)
|
||||
return 1;
|
||||
|
||||
if (first.getPlayerCount() < (first.getMaxPlayerCount() / 2) && second.getPlayerCount() >= (second.getMaxPlayerCount() / 2))
|
||||
return -1;
|
||||
|
||||
if (second.getPlayerCount() < (second.getMaxPlayerCount() / 2) && first.getPlayerCount() >= (first.getMaxPlayerCount() / 2))
|
||||
return 1;
|
||||
|
||||
if (first.getPlayerCount() < (first.getMaxPlayerCount() / 2))
|
||||
{
|
||||
if (first.getPlayerCount() > second.getPlayerCount())
|
||||
return -1;
|
||||
|
||||
if (second.getPlayerCount() > first.getPlayerCount())
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (first.getPlayerCount() < second.getPlayerCount())
|
||||
return -1;
|
||||
|
||||
if (second.getPlayerCount() < first.getPlayerCount())
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Integer.parseInt(first.getName().split("-")[1]) < Integer.parseInt(second.getName().split("-")[1]))
|
||||
return -1;
|
||||
else if (Integer.parseInt(second.getName().split("-")[1]) < Integer.parseInt(first.getName().split("-")[1]))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package mineplex.bungee.lobbyBalancer;
|
||||
|
||||
public enum LobbyType
|
||||
{
|
||||
NORMAL("LOBBY", "LOBBY-", "MainMotd"),
|
||||
CLANS("ClansHub", "CLANSHUB-", "ClansMotd"),
|
||||
BETA("BetaHub","BETAHUB-", "BetaMotd"),
|
||||
MIN("MIN","MIN-", "MinMotd");
|
||||
private final String _connectName; // The name of the server the player is connecting to
|
||||
private final String _uppercasePrefix; // The (toUpperCase()) prefix given to servers of this lobby type
|
||||
private final String _redisMotdKey;
|
||||
|
||||
LobbyType(String connectName, String uppercasePrefix, String redisMotdKey)
|
||||
{
|
||||
_connectName = connectName;
|
||||
_uppercasePrefix = uppercasePrefix;
|
||||
_redisMotdKey = redisMotdKey;
|
||||
}
|
||||
|
||||
public String getConnectName()
|
||||
{
|
||||
return _connectName;
|
||||
}
|
||||
|
||||
public String getUppercasePrefix()
|
||||
{
|
||||
return _uppercasePrefix;
|
||||
}
|
||||
|
||||
public String getRedisMotdKey()
|
||||
{
|
||||
return _redisMotdKey;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/motd.iml" filepath="$PROJECT_DIR$/motd.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="bea7784e-3ad7-4932-98e1-587634f5a436" name="Default Changelist" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1sqbrMnQda6CEC49dy4Wm5DJWAn" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="bea7784e-3ad7-4932-98e1-587634f5a436" name="Default Changelist" comment="" />
|
||||
<created>1621603265666</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1621603265666</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,43 @@
|
||||
package mineplex.bungee.motd;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import mineplex.serverdata.data.Data;
|
||||
|
||||
/**
|
||||
* A GlobalMotd represents a set of MOTD packaged lines.
|
||||
* @author MrTwiggy
|
||||
*
|
||||
*/
|
||||
public class GlobalMotd implements Data
|
||||
{
|
||||
// The unique name representing this MOTD set
|
||||
private String _name;
|
||||
|
||||
private String _headline;
|
||||
public String getHeadline() { return _headline; }
|
||||
|
||||
// List of lines describing the MOTD
|
||||
private List<String> _motd;
|
||||
public List<String> getMotd() { return _motd; }
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param name
|
||||
* @param motd
|
||||
*/
|
||||
public GlobalMotd(String name, String headline, List<String> motd)
|
||||
{
|
||||
_name = name;
|
||||
_headline = headline;
|
||||
_motd = motd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifying ID associated with this {@link GlobalMotd}.
|
||||
*/
|
||||
public String getDataId()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package mineplex.bungee.motd;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import mineplex.bungee.lobbyBalancer.LobbyType;
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.data.DataRepository;
|
||||
import mineplex.serverdata.redis.RedisDataRepository;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
|
||||
public class MotdManager implements Listener, Runnable
|
||||
{
|
||||
private static final String DEFAULT_HEADLINE = " §b§l§m §8§l§m[ §r §9§lThieunang.club§r §f§lGames§r §8§l§m ]§b§l§m §r";
|
||||
|
||||
private final DataRepository<GlobalMotd> _repository;
|
||||
private final Random _random = new Random();
|
||||
private final Map<LobbyType, GlobalMotd> motds = new EnumMap<>(LobbyType.class);
|
||||
|
||||
public MotdManager(Plugin plugin)
|
||||
{
|
||||
plugin.getProxy().getScheduler().schedule(plugin, this, 5L, 30L, TimeUnit.SECONDS);
|
||||
plugin.getProxy().getPluginManager().registerListener(plugin, this);
|
||||
|
||||
_repository = new RedisDataRepository<GlobalMotd>(ServerManager.getConnection(true, ServerManager.SERVER_STATUS_LABEL), ServerManager.getConnection(false, ServerManager.SERVER_STATUS_LABEL),
|
||||
Region.ALL, GlobalMotd.class, "globalMotd");
|
||||
run();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void serverPing(ProxyPingEvent event)
|
||||
{
|
||||
|
||||
net.md_5.bungee.api.ServerPing serverPing = event.getResponse();
|
||||
Optional<LobbyType> maybeType = Optional.empty();
|
||||
|
||||
if (event.getConnection().getListener() != null)
|
||||
{
|
||||
maybeType = Arrays.stream(LobbyType.values())
|
||||
.filter(type -> event.getConnection().getListener().getDefaultServer().equalsIgnoreCase(type.getConnectName()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
LobbyType lobbyType = maybeType.orElse(LobbyType.NORMAL);
|
||||
GlobalMotd globalMotd = motds.get(lobbyType);
|
||||
|
||||
String motd = DEFAULT_HEADLINE;
|
||||
if (globalMotd != null && globalMotd.getHeadline() != null)
|
||||
{
|
||||
motd = globalMotd.getHeadline() == null ? DEFAULT_HEADLINE : globalMotd.getHeadline();
|
||||
if (globalMotd.getMotd() != null)
|
||||
{
|
||||
motd += "\n" + globalMotd.getMotd().get(_random.nextInt(globalMotd.getMotd().size()));
|
||||
}
|
||||
}
|
||||
|
||||
event.setResponse(new net.md_5.bungee.api.ServerPing(serverPing.getVersion(), serverPing.getPlayers(), motd, serverPing.getFaviconObject()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
/*
|
||||
for (LobbyType type : LobbyType.values())
|
||||
{
|
||||
GlobalMotd motd = _repository.getElement(type.getRedisMotdKey());
|
||||
|
||||
if (motd != null)
|
||||
{
|
||||
motds.put(type, motd);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,118 @@
|
||||
package mineplex.bungee.playerCount;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mineplex.bungee.status.InternetStatus;
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.data.BungeeServer;
|
||||
import mineplex.serverdata.data.DataRepository;
|
||||
import mineplex.serverdata.redis.RedisDataRepository;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import net.md_5.bungee.api.ServerPing.Players;
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PlayerCount implements Listener, Runnable
|
||||
{
|
||||
private DataRepository<BungeeServer> _repository;
|
||||
private DataRepository<BungeeServer> _secondRepository;
|
||||
private Region _region;
|
||||
|
||||
private ListenerInfo _listenerInfo;
|
||||
private Plugin _plugin;
|
||||
|
||||
private int _totalPlayers = -1;
|
||||
|
||||
public PlayerCount(Plugin plugin)
|
||||
{
|
||||
_region = !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||
_plugin = plugin;
|
||||
|
||||
_plugin.getProxy().getScheduler().schedule(_plugin, this, 4L, 4L, TimeUnit.SECONDS);
|
||||
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||
|
||||
for (ListenerInfo info : _plugin.getProxy().getConfigurationAdapter().getListeners())
|
||||
{
|
||||
if (info.getDefaultServer().equalsIgnoreCase("Lobby"))
|
||||
{
|
||||
_listenerInfo = info;
|
||||
}
|
||||
}
|
||||
|
||||
_repository = new RedisDataRepository<BungeeServer>(ServerManager.getConnection(true, ServerManager.SERVER_STATUS_LABEL), ServerManager.getConnection(false, ServerManager.SERVER_STATUS_LABEL),
|
||||
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||
|
||||
if (_region == Region.US)
|
||||
_secondRepository = new RedisDataRepository<BungeeServer>(new ConnectionData("127.0.0.1", 6379, ConnectionType.MASTER, "ServerStatus"), new ConnectionData("127.0.0.1", 6379, ConnectionType.SLAVE, "ServerStatus"),
|
||||
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||
else
|
||||
_secondRepository = new RedisDataRepository<BungeeServer>(new ConnectionData("127.0.0.1", 6379, ConnectionType.MASTER, "ServerStatus"), new ConnectionData("127.0.0.1", 6379, ConnectionType.SLAVE, "ServerStatus"),
|
||||
Region.ALL, BungeeServer.class, "bungeeServers");
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
BungeeServer snapshot = generateSnapshot();
|
||||
if (snapshot != null)
|
||||
{
|
||||
_repository.addElement(snapshot, 15); // Update with a 15 second expiry on session
|
||||
}
|
||||
|
||||
_totalPlayers = fetchPlayerCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an up-to-date total player count across all active Bungee Servers.
|
||||
*/
|
||||
private int fetchPlayerCount()
|
||||
{
|
||||
int totalPlayers = 0;
|
||||
for (BungeeServer server : _repository.getElements())
|
||||
{
|
||||
totalPlayers += server.getPlayerCount();
|
||||
}
|
||||
|
||||
for (BungeeServer server : _secondRepository.getElements())
|
||||
{
|
||||
totalPlayers += server.getPlayerCount();
|
||||
}
|
||||
|
||||
return totalPlayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a newly instantiated {@link BungeeServer} snapshot of the current state of this server.
|
||||
*/
|
||||
private BungeeServer generateSnapshot()
|
||||
{
|
||||
if (_listenerInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String name = _listenerInfo.getHost().getAddress().getHostAddress();
|
||||
String host = _listenerInfo.getHost().getAddress().getHostAddress();
|
||||
int port = _listenerInfo.getHost().getPort();
|
||||
boolean connected = InternetStatus.isConnected();
|
||||
int playerCount = _plugin.getProxy().getOnlineCount();
|
||||
return new BungeeServer(name, _region, host, port, playerCount, connected);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void ServerPing(ProxyPingEvent event)
|
||||
{
|
||||
net.md_5.bungee.api.ServerPing serverPing = event.getResponse();
|
||||
|
||||
event.setResponse(new net.md_5.bungee.api.ServerPing(serverPing.getVersion(), new Players(_totalPlayers + 1, _totalPlayers, null), serverPing.getDescription(), serverPing.getFaviconObject()));
|
||||
}
|
||||
|
||||
public int getTotalPlayers()
|
||||
{
|
||||
return _totalPlayers;
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package mineplex.bungee.playerStats;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mineplex.bungee.playerStats.data.IpInfo;
|
||||
import mineplex.cache.player.PlayerCache;
|
||||
import mineplex.cache.player.PlayerInfo;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PlayerStats implements Listener, Runnable
|
||||
{
|
||||
private Plugin _plugin;
|
||||
private PlayerStatsRepository _repository;
|
||||
|
||||
private HashSet<UUID> _retrievingPlayerInfo = new HashSet<UUID>();
|
||||
|
||||
public PlayerStats(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
|
||||
_plugin.getProxy().getScheduler().schedule(_plugin, this, 5L, 5L, TimeUnit.MINUTES);
|
||||
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||
|
||||
_repository = new PlayerStatsRepository();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerConnect(final PostLoginEvent event)
|
||||
{
|
||||
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
String address = event.getPlayer().getPendingConnection().getAddress().getAddress().getHostAddress();
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
String name = event.getPlayer().getName();
|
||||
int version = event.getPlayer().getPendingConnection().getVersion();
|
||||
|
||||
try
|
||||
{
|
||||
PlayerInfo playerInfo = null;
|
||||
IpInfo ipInfo = _repository.getIp(address);
|
||||
|
||||
boolean addOrUpdatePlayer = false;
|
||||
|
||||
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||
|
||||
if (playerInfo == null)
|
||||
{
|
||||
addOrUpdatePlayer = true;
|
||||
_retrievingPlayerInfo.add(uuid);
|
||||
}
|
||||
|
||||
if (!addOrUpdatePlayer)
|
||||
{
|
||||
if (playerInfo.getVersion() != version)
|
||||
addOrUpdatePlayer = true;
|
||||
else if (!playerInfo.getName().equalsIgnoreCase(name))
|
||||
addOrUpdatePlayer = true;
|
||||
}
|
||||
|
||||
if (addOrUpdatePlayer)
|
||||
{
|
||||
// Just update? what about other properties?
|
||||
PlayerInfo updatedPlayerInfo = _repository.getPlayer(uuid, name, version);
|
||||
|
||||
if (playerInfo != null)
|
||||
{
|
||||
playerInfo.setName(updatedPlayerInfo.getName());
|
||||
playerInfo.setVersion(updatedPlayerInfo.getVersion());
|
||||
}
|
||||
else
|
||||
playerInfo = updatedPlayerInfo;
|
||||
}
|
||||
|
||||
playerInfo.setSessionId(_repository.updatePlayerStats(playerInfo.getId(), ipInfo.id));
|
||||
playerInfo.updateLoginTime();
|
||||
PlayerCache.getInstance().addPlayer(playerInfo);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_retrievingPlayerInfo.remove(uuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerDisconnect(final PlayerDisconnectEvent event)
|
||||
{
|
||||
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
UUID uuid = event.getPlayer().getUniqueId();
|
||||
|
||||
PlayerInfo playerInfo = null;
|
||||
|
||||
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||
|
||||
int timeout = 5;
|
||||
|
||||
while (playerInfo == null && _retrievingPlayerInfo.contains(uuid) && timeout <= 5)
|
||||
{
|
||||
playerInfo = PlayerCache.getInstance().getPlayer(uuid);
|
||||
|
||||
if (playerInfo != null)
|
||||
break;
|
||||
|
||||
System.out.println("ERROR - Player disconnecting and isn't in cache... sleeping");
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(500);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
timeout++;
|
||||
}
|
||||
|
||||
if(playerInfo == null){
|
||||
System.out.println("playerInfo is null, did the player even join the server before left?");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println(playerInfo.getName() + ":" + playerInfo.getSessionId());
|
||||
_repository.updatePlayerSession(playerInfo.getSessionId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
PlayerCache.getInstance().clean();
|
||||
}
|
||||
}
|
@ -0,0 +1,295 @@
|
||||
package mineplex.bungee.playerStats;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
|
||||
import mineplex.bungee.playerStats.data.IpInfo;
|
||||
import mineplex.cache.player.PlayerInfo;
|
||||
import mineplex.serverdata.database.DBPool;
|
||||
import mineplex.serverdata.database.RepositoryBase;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class PlayerStatsRepository extends RepositoryBase
|
||||
{
|
||||
private static String INSERT_PLAYERINFO = "INSERT INTO playerInfo (uuid, name, version) VALUES (?, ?, ?);";
|
||||
private static String SELECT_PLAYERINFO = "SELECT id, name, version FROM playerInfo WHERE uuid = ?;";
|
||||
private static String UPDATE_PLAYERINFO = "UPDATE playerInfo SET name = ?, version = ? WHERE id = ?;";
|
||||
|
||||
private static String INSERT_IPINFO = "INSERT INTO ipInfo (ipAddress) VALUES (?);";
|
||||
private static String SELECT_IPINFO = "SELECT id FROM ipInfo WHERE ipAddress = ?;";
|
||||
|
||||
private static String UPDATE_PLAYERSTATS = "INSERT IGNORE INTO playerIps (playerInfoId, ipInfoId, date) VALUES (?, ?, curdate());"
|
||||
+ "INSERT IGNORE INTO playerUniqueLogins (playerInfoId, day) values(?, curdate());"
|
||||
+ "INSERT IGNORE INTO playerLoginSessions (playerInfoId, loginTime) values(?, now());";
|
||||
|
||||
private static String UPDATE_LOGINSESSION = "UPDATE playerLoginSessions SET timeInGame = TIME_TO_SEC(TIMEDIFF(now(), loginTime)) / 60 WHERE id = ?;";
|
||||
|
||||
public PlayerStatsRepository()
|
||||
{
|
||||
super(DBPool.getPlayerStats());
|
||||
}
|
||||
|
||||
public PlayerInfo getPlayer(UUID uuid, String name, int version)
|
||||
{
|
||||
PlayerInfo playerInfo = null;
|
||||
PreparedStatement preparedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try(Connection connection = getConnection())
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(SELECT_PLAYERINFO);
|
||||
|
||||
preparedStatement.setString(1, uuid.toString());
|
||||
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
|
||||
while (resultSet.next())
|
||||
{
|
||||
playerInfo = new PlayerInfo(resultSet.getInt(1), uuid, resultSet.getString(2), resultSet.getInt(3));
|
||||
}
|
||||
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
|
||||
if (playerInfo == null)
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(INSERT_PLAYERINFO, Statement.RETURN_GENERATED_KEYS);
|
||||
preparedStatement.setString(1, uuid.toString());
|
||||
preparedStatement.setString(2, name);
|
||||
preparedStatement.setInt(3, version);
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
|
||||
int id = 0;
|
||||
|
||||
resultSet = preparedStatement.getGeneratedKeys();
|
||||
|
||||
while (resultSet.next())
|
||||
{
|
||||
id = resultSet.getInt(1);
|
||||
}
|
||||
|
||||
playerInfo = new PlayerInfo(id, uuid, name, version);
|
||||
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
}
|
||||
else if (!playerInfo.getName().equalsIgnoreCase(name) || playerInfo.getVersion() != version)
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(UPDATE_PLAYERINFO);
|
||||
preparedStatement.setString(1, name);
|
||||
preparedStatement.setInt(2, version);
|
||||
preparedStatement.setInt(3, playerInfo.getId());
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (preparedStatement != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
preparedStatement.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (resultSet != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
resultSet.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playerInfo;
|
||||
}
|
||||
|
||||
public IpInfo getIp(String ipAddress)
|
||||
{
|
||||
IpInfo ipInfo = null;
|
||||
PreparedStatement preparedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
|
||||
try(Connection connection = getConnection())
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(SELECT_IPINFO);
|
||||
preparedStatement.setString(1, ipAddress);
|
||||
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
|
||||
while (resultSet.next())
|
||||
{
|
||||
ipInfo = new IpInfo();
|
||||
ipInfo.id = resultSet.getInt(1);
|
||||
ipInfo.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
|
||||
if (ipInfo == null)
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(INSERT_IPINFO, Statement.RETURN_GENERATED_KEYS);
|
||||
preparedStatement.setString(1, ipAddress);
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
|
||||
int id = 0;
|
||||
|
||||
resultSet = preparedStatement.getGeneratedKeys();
|
||||
|
||||
while (resultSet.next())
|
||||
{
|
||||
id = resultSet.getInt(1);
|
||||
}
|
||||
|
||||
ipInfo = new IpInfo();
|
||||
ipInfo.id = id;
|
||||
ipInfo.ipAddress = ipAddress;
|
||||
|
||||
resultSet.close();
|
||||
preparedStatement.close();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (preparedStatement != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
preparedStatement.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (resultSet != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
resultSet.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ipInfo;
|
||||
}
|
||||
|
||||
public int updatePlayerStats(int playerId, int ipId)
|
||||
{
|
||||
Statement statement = null;
|
||||
ResultSet resultSet= null;
|
||||
|
||||
try(Connection connection = getConnection())
|
||||
{
|
||||
statement = connection.createStatement();
|
||||
|
||||
String queryString = UPDATE_PLAYERSTATS;
|
||||
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||
queryString = queryString.replaceFirst("\\?", ipId + "");
|
||||
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||
queryString = queryString.replaceFirst("\\?", playerId + "");
|
||||
|
||||
statement.executeUpdate(queryString, Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
statement.getMoreResults();
|
||||
statement.getMoreResults();
|
||||
resultSet = statement.getGeneratedKeys();
|
||||
|
||||
while (resultSet.next())
|
||||
{
|
||||
return resultSet.getInt(1);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (statement != null)
|
||||
statement.close();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (resultSet != null)
|
||||
resultSet.close();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void updatePlayerSession(int loginSessionId)
|
||||
{
|
||||
PreparedStatement preparedStatement = null;
|
||||
|
||||
try(Connection connection = getConnection())
|
||||
{
|
||||
preparedStatement = connection.prepareStatement(UPDATE_LOGINSESSION);
|
||||
preparedStatement.setInt(1, loginSessionId);
|
||||
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (preparedStatement != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
preparedStatement.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package mineplex.bungee.playerStats.data;
|
||||
|
||||
public class IpInfo
|
||||
{
|
||||
public int id;
|
||||
public String ipAddress;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package mineplex.bungee.playerTracker;
|
||||
|
||||
import java.util.UUID;
|
||||
import mineplex.serverdata.commands.CommandCallback;
|
||||
import mineplex.serverdata.commands.PlayerJoinCommand;
|
||||
import mineplex.serverdata.commands.ServerCommand;
|
||||
|
||||
public class PlayerJoinHandler implements CommandCallback
|
||||
{
|
||||
private PlayerTracker _playerTracker;
|
||||
|
||||
public PlayerJoinHandler(PlayerTracker playerTracker)
|
||||
{
|
||||
_playerTracker = playerTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ServerCommand command)
|
||||
{
|
||||
if (command instanceof PlayerJoinCommand)
|
||||
{
|
||||
PlayerJoinCommand joinCommand = (PlayerJoinCommand)command;
|
||||
_playerTracker.kickPlayerIfOnline(UUID.fromString(joinCommand.getUuid()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package mineplex.bungee.playerTracker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.commands.PlayerJoinCommand;
|
||||
import mineplex.serverdata.commands.ServerCommandManager;
|
||||
import mineplex.serverdata.data.DataRepository;
|
||||
import mineplex.serverdata.data.PlayerStatus;
|
||||
import mineplex.serverdata.redis.RedisDataRepository;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PlayerTracker implements Listener
|
||||
{
|
||||
private static final int DEFAULT_STATUS_TIMEOUT = 60 * 60 * 8;
|
||||
|
||||
private DataRepository<PlayerStatus> _repository;
|
||||
|
||||
private Plugin _plugin;
|
||||
|
||||
private final List<UUID> _ignoreKick = Lists.newArrayList();
|
||||
|
||||
public PlayerTracker(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
|
||||
_plugin.getProxy().getPluginManager().registerListener(_plugin, this);
|
||||
|
||||
_repository = new RedisDataRepository<PlayerStatus>(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(),
|
||||
Region.currentRegion(), PlayerStatus.class, "playerStatus");
|
||||
|
||||
ServerCommandManager.getInstance().initializeServer("BUNGEE ENABLE - " + System.currentTimeMillis(), new Gson());
|
||||
ServerCommandManager.getInstance().registerCommandType(mineplex.serverdata.commands.PlayerJoinCommand.class, new PlayerJoinHandler(this));
|
||||
|
||||
System.out.println("Initialized PlayerTracker.");
|
||||
}
|
||||
|
||||
public Plugin getPlugin()
|
||||
{
|
||||
return _plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerConnect(final ServerConnectedEvent event)
|
||||
{
|
||||
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
PlayerStatus snapshot = new PlayerStatus(event.getPlayer().getUniqueId(), event.getPlayer().getName(), event.getServer().getInfo().getName());
|
||||
_repository.addElement(snapshot, DEFAULT_STATUS_TIMEOUT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerDisconnect(final PlayerDisconnectEvent event)
|
||||
{
|
||||
_plugin.getProxy().getScheduler().runAsync(_plugin, new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
_repository.removeElement(event.getPlayer().getUniqueId().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void playerConnect(final PostLoginEvent event)
|
||||
{
|
||||
_ignoreKick.add(event.getPlayer().getUniqueId());
|
||||
PlayerJoinCommand command = new PlayerJoinCommand(event.getPlayer().getUniqueId(), event.getPlayer().getName());
|
||||
command.publish();
|
||||
}
|
||||
|
||||
public boolean isPlayerOnline(UUID uuid)
|
||||
{
|
||||
return _plugin.getProxy().getPlayer(uuid) != null;
|
||||
}
|
||||
|
||||
public void kickPlayerIfOnline(UUID uuid)
|
||||
{
|
||||
if (_ignoreKick.remove(uuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (isPlayerOnline(uuid))
|
||||
{
|
||||
ProxiedPlayer player = _plugin.getProxy().getPlayer(uuid);
|
||||
|
||||
player.disconnect(new TextComponent("You have logged in from another location."));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package mineplex.bungee.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
public class InternetStatus implements Runnable
|
||||
{
|
||||
// Current internet connectivity status
|
||||
private static boolean _connected = true;
|
||||
public static boolean isConnected() { return _connected; }
|
||||
|
||||
private Plugin _plugin;
|
||||
|
||||
public InternetStatus(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
_plugin.getProxy().getScheduler().schedule(_plugin, this, 1L, 1L, TimeUnit.MINUTES);
|
||||
|
||||
System.out.println("Initialized InternetStatus.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
_connected = isOnline(); // Update _connected flag.
|
||||
}
|
||||
|
||||
private boolean isOnline()
|
||||
{
|
||||
return testUrl("www.google.com")
|
||||
|| testUrl("www.espn.com")
|
||||
|| testUrl("www.bing.com");
|
||||
}
|
||||
|
||||
private boolean testUrl(String url)
|
||||
{
|
||||
boolean reachable = false;
|
||||
|
||||
try (Socket socket = new Socket(url, 80))
|
||||
{
|
||||
reachable = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Meh i don't care
|
||||
}
|
||||
|
||||
return reachable;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="Mineplex.ServerData" />
|
||||
</component>
|
||||
</module>
|
110
Bungee & Mineplexer/Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerCache.java
vendored
Normal file
110
Bungee & Mineplexer/Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerCache.java
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
package mineplex.cache.player;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.redis.RedisDataRepository;
|
||||
import mineplex.serverdata.redis.atomic.RedisStringRepository;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
|
||||
public enum PlayerCache
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
public static PlayerCache getInstance()
|
||||
{
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final RedisDataRepository<PlayerInfo> _playerInfoRepository;
|
||||
private final RedisStringRepository _accountIdRepository;
|
||||
|
||||
PlayerCache()
|
||||
{
|
||||
_playerInfoRepository = new RedisDataRepository<PlayerInfo>(
|
||||
ServerManager.getMasterConnection(),
|
||||
ServerManager.getSlaveConnection(),
|
||||
Region.ALL,
|
||||
PlayerInfo.class,
|
||||
"playercache");
|
||||
|
||||
_accountIdRepository = new RedisStringRepository(
|
||||
ServerManager.getMasterConnection(),
|
||||
ServerManager.getSlaveConnection(),
|
||||
Region.ALL,
|
||||
"accountid",
|
||||
(int) TimeUnit.HOURS.toSeconds(6)
|
||||
);
|
||||
}
|
||||
|
||||
public void addPlayer(PlayerInfo player)
|
||||
{
|
||||
try
|
||||
{
|
||||
_playerInfoRepository.addElement(player, 60 * 60 * 6); // 6 Hours
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
System.out.println("Error adding player info in PlayerCache : " + exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerInfo getPlayer(UUID uuid)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _playerInfoRepository.getElement(uuid.toString());
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
System.out.println("Error retrieving player info in PlayerCache : " + exception.getMessage());
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to grab a player's account ID from the cache
|
||||
*
|
||||
* @param uuid Minecraft Account UUID
|
||||
* @return The account id of the player, or -1 if the player is not in the cache
|
||||
*/
|
||||
public int getAccountId(UUID uuid)
|
||||
{
|
||||
String accountIdStr = _accountIdRepository.get(uuid.toString());
|
||||
|
||||
if (accountIdStr == null)
|
||||
return -1;
|
||||
|
||||
try
|
||||
{
|
||||
int accountId = Integer.parseInt(accountIdStr);
|
||||
if (accountId <= 0)
|
||||
{
|
||||
// remove invalid account id
|
||||
_accountIdRepository.del(uuid.toString());
|
||||
return -1;
|
||||
}
|
||||
return accountId;
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
// remove invalid account id
|
||||
_accountIdRepository.del(uuid.toString());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateAccountId(UUID uuid, int newId)
|
||||
{
|
||||
_accountIdRepository.set(uuid.toString(), String.valueOf(newId));
|
||||
}
|
||||
|
||||
public void clean()
|
||||
{
|
||||
_playerInfoRepository.clean();
|
||||
}
|
||||
}
|
93
Bungee & Mineplexer/Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerInfo.java
vendored
Normal file
93
Bungee & Mineplexer/Mineplexer/Mineplex.Cache/src/mineplex/cache/player/PlayerInfo.java
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
package mineplex.cache.player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.data.Data;
|
||||
|
||||
public class PlayerInfo implements Data
|
||||
{
|
||||
private int _id;
|
||||
private int _accountId;
|
||||
private UUID _uuid;
|
||||
private String _name;
|
||||
private boolean _online;
|
||||
private long _lastUniqueLogin;
|
||||
private long _loginTime;
|
||||
private int _sessionId;
|
||||
private int _version;
|
||||
|
||||
public PlayerInfo(int id, UUID uuid, String name, int version)
|
||||
{
|
||||
_id = id;
|
||||
_uuid = uuid;
|
||||
_name = name;
|
||||
_version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataId()
|
||||
{
|
||||
return _uuid.toString();
|
||||
}
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
public UUID getUUID()
|
||||
{
|
||||
return _uuid;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public boolean getOnline()
|
||||
{
|
||||
return _online;
|
||||
}
|
||||
|
||||
public long getLastUniqueLogin()
|
||||
{
|
||||
return _lastUniqueLogin;
|
||||
}
|
||||
|
||||
public long getLoginTime()
|
||||
{
|
||||
return _loginTime;
|
||||
}
|
||||
|
||||
public int getSessionId()
|
||||
{
|
||||
return _sessionId;
|
||||
}
|
||||
|
||||
public int getVersion()
|
||||
{
|
||||
return _version;
|
||||
}
|
||||
|
||||
public void setSessionId(int sessionId)
|
||||
{
|
||||
_sessionId = sessionId;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void setVersion(int version)
|
||||
{
|
||||
_version = version;
|
||||
}
|
||||
|
||||
public void updateLoginTime()
|
||||
{
|
||||
_loginTime = Utility.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/Bungeecord.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/jedis-2.8.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/commons-dbcp2-2.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/jooq-3.5.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library>
|
||||
<CLASSES>
|
||||
<root url="jar://$MODULE_DIR$/../Libraries/commons-pool2-2.9.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,25 @@
|
||||
package mineplex.serverdata;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Region enumerates the various geographical regions where Mineplex servers are
|
||||
* hosted.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public enum Region
|
||||
{
|
||||
US,
|
||||
EU,
|
||||
ALL;
|
||||
|
||||
|
||||
/**
|
||||
* @return the geographical {@link Region} of the current running process.
|
||||
*/
|
||||
public static Region currentRegion()
|
||||
{
|
||||
return !new File("eu.dat").exists() ? Region.US : Region.EU;
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
package mineplex.serverdata;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
/**
|
||||
* Utility offers various necessary utility-based methods for use in Mineplex.ServerData.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public class Utility
|
||||
{
|
||||
private static boolean _retrievedRedisTime = false;
|
||||
private static long _millisTimeDifference;
|
||||
|
||||
// The Gson instance used to serialize/deserialize objects in JSON form.
|
||||
private static Gson _gson = new Gson();
|
||||
public static Gson getGson() { return _gson; }
|
||||
|
||||
// map of all instantiated connection pools, distinguished by their ip:port combination
|
||||
private static final ConcurrentHashMap<String, JedisPool> _pools = new ConcurrentHashMap<String, JedisPool>();
|
||||
|
||||
// Public static jedis pool for interacting with central default jedis repo.
|
||||
private static JedisPool _masterPool;
|
||||
private static JedisPool _slavePool;
|
||||
private static final Object _poolLock = new Object();
|
||||
|
||||
/**
|
||||
* @param object - the (non-null) object to serialize
|
||||
* @return the serialized form of {@code object}.
|
||||
*/
|
||||
public static String serialize(Object object)
|
||||
{
|
||||
return _gson.toJson(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serializedData - the serialized data to be deserialized
|
||||
* @param type - the resulting class type of the object to be deserialized
|
||||
* @return the deserialized form of {@code serializedData} for class {@code type}.
|
||||
*/
|
||||
public static <T> T deserialize(String serializedData, Class<T> type)
|
||||
{
|
||||
if (serializedData == null) return null;
|
||||
|
||||
return _gson.fromJson(serializedData, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delimiter - the delimiter character used to separate the concatenated elements
|
||||
* @param elements - the set of string elements to be concatenated and returned.
|
||||
* @return the concatenated string of all {@code elements} separated by the {@code delimiter}.
|
||||
*/
|
||||
public static String concatenate(char delimiter, String... elements)
|
||||
{
|
||||
int length = elements.length;
|
||||
String result = length > 0 ? elements[0] : new String();
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
result += delimiter + elements[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current timestamp (in seconds) fetched from the central jedis repository
|
||||
* for synced timestamps.
|
||||
*/
|
||||
public static long currentTimeSeconds()
|
||||
{
|
||||
if (!_retrievedRedisTime)
|
||||
setTimeDifference();
|
||||
|
||||
return (System.currentTimeMillis() + _millisTimeDifference) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current timestamp (in milliseconds) fetched from the central jedis repository
|
||||
* for synced timestamps.
|
||||
*/
|
||||
public static long currentTimeMillis()
|
||||
{
|
||||
if (!_retrievedRedisTime)
|
||||
setTimeDifference();
|
||||
|
||||
return System.currentTimeMillis() + _millisTimeDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param connData - the connection data specifying the database to be connected to.
|
||||
* @return a newly instantiated {@link JedisPool} connected to the provided {@link ConnectionData} repository.
|
||||
*/
|
||||
public static JedisPool generatePool(ConnectionData connData)
|
||||
{
|
||||
synchronized(_poolLock)
|
||||
{
|
||||
String key = "127.0.0.1:6379";
|
||||
if(connData != null){
|
||||
key = getConnKey(connData);
|
||||
}
|
||||
JedisPool pool = _pools.get(key);
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
|
||||
|
||||
jedisPoolConfig.setMaxWaitMillis(1000);
|
||||
jedisPoolConfig.setMinIdle(5);
|
||||
jedisPoolConfig.setTestOnBorrow(true);
|
||||
|
||||
jedisPoolConfig.setMaxTotal(20);
|
||||
jedisPoolConfig.setBlockWhenExhausted(true);
|
||||
|
||||
if(connData != null){
|
||||
pool = new JedisPool(jedisPoolConfig, connData.getHost(), connData.getPort());
|
||||
}else{
|
||||
pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
|
||||
}
|
||||
_pools.put(key, pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeable - whether or not the Jedis connections returned should be writeable to.
|
||||
* @return a globally available {@link JedisPool}
|
||||
*/
|
||||
public static JedisPool getPool(boolean writeable)
|
||||
{
|
||||
if (writeable)
|
||||
{
|
||||
if (_masterPool == null)
|
||||
{
|
||||
_masterPool = generatePool(ServerManager.getMasterConnection());
|
||||
}
|
||||
|
||||
return _masterPool;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_slavePool == null)
|
||||
{
|
||||
ConnectionData slave = ServerManager.getSlaveConnection();
|
||||
|
||||
_slavePool = generatePool(slave);
|
||||
}
|
||||
|
||||
return _slavePool;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getConnKey(ConnectionData connData)
|
||||
{
|
||||
return connData.getHost() + ":" + connData.getPort();
|
||||
}
|
||||
|
||||
private static void setTimeDifference()
|
||||
{
|
||||
long currentTime = 0;
|
||||
JedisPool pool = getPool(false);
|
||||
|
||||
try (Jedis jedis = pool.getResource())
|
||||
{
|
||||
// Try multiple times in case one isn't valid
|
||||
// Addresses an error in sentry
|
||||
List<String> times = jedis.time();
|
||||
for (String time : times.subList(0, Math.min(5, times.size())))
|
||||
{
|
||||
try
|
||||
{
|
||||
currentTime = Long.parseLong(time);
|
||||
break;
|
||||
} catch (NumberFormatException ex) { }
|
||||
}
|
||||
}
|
||||
|
||||
_millisTimeDifference = (currentTime * 1000) - System.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class AddPunishCommand extends ServerCommand
|
||||
{
|
||||
private final String _target;
|
||||
private final String _category;
|
||||
private final String _sentence;
|
||||
private final String _reason;
|
||||
private final long _duration;
|
||||
private final String _admin;
|
||||
private final String _adminUUID;
|
||||
private final int _severity;
|
||||
|
||||
public AddPunishCommand(String finalPlayerName, int severity, String category, String sentence, String reason, long duration, String finalCallerName, String uuid)
|
||||
{
|
||||
this._target = finalPlayerName;
|
||||
this._severity = severity;
|
||||
this._category = category;
|
||||
this._sentence = sentence;
|
||||
this._reason = reason;
|
||||
this._duration = duration;
|
||||
this._admin = finalCallerName;
|
||||
this._adminUUID = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
|
||||
public class AnnouncementCommand extends ServerCommand
|
||||
{
|
||||
private boolean _displayTitle;
|
||||
private String _rank;
|
||||
private String _message;
|
||||
|
||||
public boolean getDisplayTitle() { return _displayTitle; }
|
||||
public String getRank() { return _rank; }
|
||||
public String getMessage() { return _message; }
|
||||
|
||||
public AnnouncementCommand(boolean displayTitle, String rank, String message)
|
||||
{
|
||||
_displayTitle = displayTitle;
|
||||
_rank = rank;
|
||||
_message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Utilitizes a callback functionality to seperate dependencies
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
public interface CommandCallback<T extends ServerCommand>
|
||||
{
|
||||
void run(T command);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
public class CommandType
|
||||
{
|
||||
private Class<? extends ServerCommand> _commandClazz;
|
||||
public Class<? extends ServerCommand> getCommandType() { return _commandClazz; }
|
||||
|
||||
private CommandCallback<? extends ServerCommand> _commandCallback;
|
||||
public CommandCallback<? extends ServerCommand> getCallback() { return _commandCallback; }
|
||||
|
||||
public CommandType(Class<? extends ServerCommand> commandClazz, CommandCallback<? extends ServerCommand> commandCallback)
|
||||
{
|
||||
_commandClazz = commandClazz;
|
||||
_commandCallback = commandCallback;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerJoinCommand extends ServerCommand
|
||||
{
|
||||
private String _uuid;
|
||||
private String _name;
|
||||
|
||||
public PlayerJoinCommand(UUID uuid, String name)
|
||||
{
|
||||
_uuid = uuid.toString();
|
||||
_name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Utilitizes a callback functionality to seperate dependencies
|
||||
}
|
||||
|
||||
public String getUuid()
|
||||
{
|
||||
return _uuid;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
|
||||
public class PunishCommand extends ServerCommand
|
||||
{
|
||||
private String _playerName;
|
||||
private boolean _ban;
|
||||
private boolean _mute;
|
||||
private String _message;
|
||||
|
||||
public String getPlayerName() { return _playerName; }
|
||||
public boolean getBan() { return _ban; }
|
||||
public boolean getMute() { return _mute; }
|
||||
public String getMessage() { return _message; }
|
||||
|
||||
public PunishCommand(String playerName, boolean ban, boolean mute, String message)
|
||||
{
|
||||
_playerName = playerName;
|
||||
_ban = ban;
|
||||
_mute = mute;
|
||||
_message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Utilitizes a callback functionality to seperate dependencies
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class RemovePunishCommand extends ServerCommand
|
||||
{
|
||||
private final JsonObject _punishment;
|
||||
private final String _target;
|
||||
private final String _admin;
|
||||
private final String _adminUUID;
|
||||
private final String _reason;
|
||||
|
||||
public RemovePunishCommand(JsonObject punishment, String target, String admin, UUID adminUUID, String reason)
|
||||
{
|
||||
_punishment = punishment;
|
||||
_target = target;
|
||||
_admin = admin;
|
||||
_adminUUID = adminUUID.toString();
|
||||
_reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
|
||||
public class RestartCommand extends ServerCommand
|
||||
{
|
||||
|
||||
private final String _server;
|
||||
private final Region _region;
|
||||
private final boolean _groupRestart;
|
||||
|
||||
public RestartCommand(String server, Region region, boolean groupRestart)
|
||||
{
|
||||
_server = server;
|
||||
_region = region;
|
||||
_groupRestart = groupRestart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
}
|
||||
|
||||
public String getServerName()
|
||||
{
|
||||
return _server;
|
||||
}
|
||||
|
||||
public Region getRegion()
|
||||
{
|
||||
return _region;
|
||||
}
|
||||
|
||||
public boolean isGroupRestart()
|
||||
{
|
||||
return _groupRestart;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class ServerCommand
|
||||
{
|
||||
private final UUID _commandId = UUID.randomUUID();
|
||||
private final String _fromServer = ServerCommandManager.getInstance().getServerName();
|
||||
|
||||
// The names of servers targetted to receive this ServerCommand.
|
||||
private String[] _targetServers;
|
||||
|
||||
public ServerCommand()
|
||||
{
|
||||
_targetServers = new String[0];
|
||||
}
|
||||
|
||||
public ServerCommand(String... targetServers)
|
||||
{
|
||||
_targetServers = targetServers;
|
||||
}
|
||||
|
||||
public void setTargetServers(String... targetServers)
|
||||
{
|
||||
_targetServers = targetServers;
|
||||
}
|
||||
|
||||
public String[] getTargetServers()
|
||||
{
|
||||
if (_targetServers == null)
|
||||
{
|
||||
_targetServers = new String[0];
|
||||
}
|
||||
|
||||
return _targetServers;
|
||||
}
|
||||
|
||||
public String getFromServer()
|
||||
{
|
||||
return this._fromServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command on it's destination target server.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
// Not yet implemented in base
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverName - the name of the server to be checked for whether they are a target
|
||||
* @return true, if {@code serverName} is one of the {@code targetServers} of this
|
||||
* {@link ServerCommand}, false otherwise.
|
||||
*/
|
||||
public boolean isTargetServer(String serverName)
|
||||
{
|
||||
if (getTargetServers().length == 0)
|
||||
return true;
|
||||
|
||||
for (String targetServer : getTargetServers())
|
||||
{
|
||||
if (targetServer.equalsIgnoreCase(serverName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the {@link ServerCommand} across the network to {@code targetServers}.
|
||||
*/
|
||||
public void publish()
|
||||
{
|
||||
ServerCommandManager.getInstance().publishCommand(this);
|
||||
}
|
||||
|
||||
public boolean wasSentFromThisServer()
|
||||
{
|
||||
return ServerCommandManager.getInstance().getServerName().equals(_fromServer);
|
||||
}
|
||||
|
||||
public boolean isSentToThisServer()
|
||||
{
|
||||
for (String targetServer : _targetServers)
|
||||
{
|
||||
if (ServerCommandManager.getInstance().getServerName().equals(targetServer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public UUID getCommandId()
|
||||
{
|
||||
return _commandId;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import redis.clients.jedis.JedisPubSub;
|
||||
|
||||
/**
|
||||
* The ServerCommandListener listens for published Redis network messages
|
||||
* and deserializes them into their {@link ServerCommand} form, then registers
|
||||
* it's arrival at the {@link ServerCommandManager}.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public class ServerCommandListener extends JedisPubSub
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onPMessage(String pattern, String channelName, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
String commandType = channelName.split(":")[1];
|
||||
ServerCommandManager.getInstance().handleCommand(commandType, message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onMessage(String channelName, String message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPSubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPUnsubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(String channelName, int subscribedChannels)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(String channelName, int subscribedChannels)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
public class ServerCommandManager
|
||||
{
|
||||
|
||||
// The singleton instance of ServerCommandManager
|
||||
private static ServerCommandManager _instance;
|
||||
|
||||
public final String SERVER_COMMANDS_CHANNEL = "commands.server";
|
||||
|
||||
private JedisPool _writePool;
|
||||
private JedisPool _readPool;
|
||||
private Map<String, CommandType> _commandTypes;
|
||||
|
||||
private String _localServerName;
|
||||
private Gson _gson;
|
||||
public void initializeServer(String serverName, Gson gson)
|
||||
{
|
||||
_localServerName = serverName;
|
||||
_gson = gson;
|
||||
}
|
||||
|
||||
public boolean isServerInitialized() { return _localServerName != null; }
|
||||
public String getServerName()
|
||||
{
|
||||
return _localServerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private class constructor to prevent non-singleton instances.
|
||||
*/
|
||||
private ServerCommandManager()
|
||||
{
|
||||
_writePool = Utility.generatePool(ServerManager.getMasterConnection()); // Publish to master instance
|
||||
_readPool = Utility.generatePool(ServerManager.getSlaveConnection()); // Read from slave instance
|
||||
|
||||
_commandTypes = new HashMap<>();
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the ServerCommandManager by subscribing to the
|
||||
* redis network.
|
||||
*/
|
||||
private void initialize()
|
||||
{
|
||||
// Spin up a new thread and subscribe to the Redis pubsub network
|
||||
Thread thread = new Thread("Redis Manager")
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try (Jedis jedis = _readPool.getResource())
|
||||
{
|
||||
jedis.psubscribe(new ServerCommandListener(), SERVER_COMMANDS_CHANNEL + ":*");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a {@link ServerCommand} across the network to all live servers.
|
||||
* @param serverCommand - the {@link ServerCommand} to issue to all servers.
|
||||
*/
|
||||
public void publishCommand(final ServerCommand serverCommand)
|
||||
{
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
String commandType = serverCommand.getClass().getSimpleName();
|
||||
String serializedCommand = _gson.toJson(serverCommand);
|
||||
|
||||
try(Jedis jedis = _writePool.getResource())
|
||||
{
|
||||
jedis.publish(SERVER_COMMANDS_CHANNEL + ":" + commandType, serializedCommand);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming (serialized) {@link ServerCommand}.
|
||||
* @param commandType - the type of command being received
|
||||
* @param serializedCommand - the serialized {@link ServerCommand} data.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void handleCommand(final String commandType, String serializedCommand)
|
||||
{
|
||||
if (!isServerInitialized())
|
||||
{
|
||||
// TODO: Log un-initialized server receiving command?
|
||||
return;
|
||||
}
|
||||
|
||||
if (_commandTypes.containsKey(commandType))
|
||||
{
|
||||
Class<? extends ServerCommand> commandClazz = _commandTypes.get(commandType).getCommandType();
|
||||
final ServerCommand serverCommand = _gson.fromJson(serializedCommand, commandClazz);
|
||||
|
||||
if (serverCommand.isTargetServer(_localServerName))
|
||||
{
|
||||
// TODO: Run synchronously?
|
||||
CommandCallback callback = _commandTypes.get(commandType).getCallback();
|
||||
serverCommand.run(); // Run server command without callback
|
||||
|
||||
if (callback != null)
|
||||
{
|
||||
callback.run(serverCommand); // Run callback
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new type of {@link ServerCommand}.
|
||||
* @param commandType - the {@link ServerCommand} type to register.
|
||||
*/
|
||||
public <T extends ServerCommand> void registerCommandType(String commandName, Class<T> commandType, CommandCallback<T> callback)
|
||||
{
|
||||
if (_commandTypes.containsKey(commandName))
|
||||
{
|
||||
// Log overwriting of command type?
|
||||
}
|
||||
|
||||
CommandType cmdType = new CommandType(commandType, callback);
|
||||
_commandTypes.put(commandName, cmdType);
|
||||
System.out.println("Registered : " + commandName);
|
||||
}
|
||||
|
||||
public <T extends ServerCommand> void registerCommandType(Class<T> commandType, CommandCallback<T> callback)
|
||||
{
|
||||
registerCommandType(commandType.getSimpleName(), commandType, callback);
|
||||
}
|
||||
|
||||
public void registerCommandType(String commandName, Class<? extends ServerCommand> commandType)
|
||||
{
|
||||
registerCommandType(commandName, commandType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the singleton instance of ServerCommandManager
|
||||
*/
|
||||
public static ServerCommandManager getInstance()
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new ServerCommandManager();
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
public class ServerTransfer
|
||||
{
|
||||
|
||||
// The name of the player who is being transferred.
|
||||
private String _playerName;
|
||||
public String getPlayerName() { return _playerName; }
|
||||
|
||||
// The name of the destination server in this ServerTransfer.
|
||||
private String _serverName;
|
||||
public String getServerName() { return _serverName; }
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param playerName - the name of the player being transferred.
|
||||
* @param serverName - the name of the server to transfer the player to (destination).
|
||||
*/
|
||||
public ServerTransfer(String playerName, String serverName)
|
||||
{
|
||||
_playerName = playerName;
|
||||
_serverName = serverName;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
|
||||
public class SuicideCommand extends ServerCommand
|
||||
{
|
||||
private String _server;
|
||||
public String getServerName() { return _server; }
|
||||
|
||||
private Region _region;
|
||||
public Region getRegion() { return _region; }
|
||||
|
||||
public SuicideCommand(String server, Region region)
|
||||
{
|
||||
_server = server;
|
||||
_region = region;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Utilitizes a callback functionality to seperate dependencies
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
|
||||
public class TransferCommand extends ServerCommand
|
||||
{
|
||||
private final String _playerName;
|
||||
private final String _targetServer;
|
||||
|
||||
public TransferCommand(String playerName, String targetServer)
|
||||
{
|
||||
_playerName = playerName;
|
||||
_targetServer = targetServer;
|
||||
}
|
||||
|
||||
public String getPlayerName()
|
||||
{
|
||||
return _playerName;
|
||||
}
|
||||
|
||||
public String getTargetServer()
|
||||
{
|
||||
return _targetServer;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class TransferUUIDCommand extends ServerCommand
|
||||
{
|
||||
private final UUID _playerUUID;
|
||||
private final String _targetServer;
|
||||
|
||||
public TransferUUIDCommand(UUID playerUUID, String targetServer)
|
||||
{
|
||||
_playerUUID = playerUUID;
|
||||
_targetServer = targetServer;
|
||||
}
|
||||
|
||||
public UUID getPlayerUUID()
|
||||
{
|
||||
return _playerUUID;
|
||||
}
|
||||
|
||||
public String getTargetServer()
|
||||
{
|
||||
return _targetServer;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package mineplex.serverdata.commands;
|
||||
|
||||
public class TwoFactorResetCommand extends ServerCommand
|
||||
{
|
||||
private String _adminName;
|
||||
private String _adminUUID;
|
||||
private String _targetName;
|
||||
private String _targetUUID;
|
||||
|
||||
public TwoFactorResetCommand(String adminName, String adminUUID, String targetName, String targetUUID)
|
||||
{
|
||||
_adminName = adminName;
|
||||
_adminUUID = adminUUID;
|
||||
_targetName = targetName;
|
||||
_targetUUID = targetUUID;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.data.Data;
|
||||
|
||||
public class BungeeServer implements Data
|
||||
{
|
||||
// The name of this server.
|
||||
private String _name;
|
||||
public String getName() { return _name; }
|
||||
|
||||
// The geographical region of this Bungee Server.
|
||||
private Region _region;
|
||||
public Region getRegion() { return _region; }
|
||||
|
||||
// The number of players currently online.
|
||||
private int _playerCount;
|
||||
public int getPlayerCount() { return _playerCount; }
|
||||
|
||||
// The public I.P address used by players to connect to the server.
|
||||
private String _publicAddress;
|
||||
public String getPublicAddress() { return _publicAddress; }
|
||||
|
||||
// The port the server is currently running/listening on.
|
||||
private int _port;
|
||||
public int getPort() { return _port; }
|
||||
|
||||
// Whether the Bungee server can connect to the internet.
|
||||
private boolean _connected;
|
||||
public boolean isConnected() { return _connected; }
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param name
|
||||
* @param publicAddress
|
||||
* @param port
|
||||
* @param playerCount
|
||||
* @param connected
|
||||
*/
|
||||
public BungeeServer(String name, Region region, String publicAddress, int port, int playerCount, boolean connected)
|
||||
{
|
||||
_name = name;
|
||||
_region = region;
|
||||
_playerCount = playerCount;
|
||||
_publicAddress = publicAddress;
|
||||
_port = port;
|
||||
_connected = connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifying ID for this Bungee Server.
|
||||
*/
|
||||
public String getDataId()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
public interface Data
|
||||
{
|
||||
/**
|
||||
* @return the unique id key representing this {@link Data} object in {@link DataRepository}s.
|
||||
*/
|
||||
public String getDataId();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* DataRepository is used to store {@link Data} objects in a central database
|
||||
* for real-time fetching/modification.
|
||||
* @author Ty
|
||||
*
|
||||
* @param <T> - the type of {@link Data} object stored in this repository.
|
||||
*/
|
||||
public interface DataRepository<T extends Data>
|
||||
{
|
||||
|
||||
public Collection<T> getElements();
|
||||
|
||||
public T getElement(String dataId);
|
||||
|
||||
public Collection<T> getElements(Collection<String> dataIds);
|
||||
|
||||
public Map<String,T> getElementsMap(List<String> dataIds);
|
||||
|
||||
public void addElement(T element, int timeout);
|
||||
|
||||
public void addElement(T element);
|
||||
|
||||
public void removeElement(T element);
|
||||
|
||||
public void removeElement(String dataId);
|
||||
|
||||
public boolean elementExists(String dataId);
|
||||
|
||||
public int clean();
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
|
||||
public class DedicatedServer
|
||||
{
|
||||
|
||||
// The default amount of available CPU usage.
|
||||
public static final int DEFAULT_CPU = 32;
|
||||
|
||||
// The default amount of available ram usage.
|
||||
public static final int DEFAULT_RAM = 14000;
|
||||
|
||||
// The unique name representing this server
|
||||
private String _name;
|
||||
public String getName() { return _name; }
|
||||
|
||||
// The public I.P address used to connect to this server
|
||||
private String _publicAddress;
|
||||
public String getPublicAddress() { return _publicAddress; }
|
||||
|
||||
// The private I.P address of this server
|
||||
private String _privateAddress;
|
||||
public String getPrivateAddress() { return _privateAddress; }
|
||||
|
||||
// The geographical region that this dedicated server is located in
|
||||
private Region _region;
|
||||
public Region getRegion() { return _region; }
|
||||
public boolean isUsRegion() { return _region == Region.US; }
|
||||
|
||||
// The amount of available CPU usage on this server box.
|
||||
private int _availableCpu;
|
||||
public int getAvailableCpu() { return _availableCpu; }
|
||||
|
||||
// The amount of available ram usage on this server box.
|
||||
private int _availableRam;
|
||||
public int getAvailableRam() { return _availableRam; }
|
||||
|
||||
// The amount of available CPU usage on this server box.
|
||||
private int _maxCpu;
|
||||
public int getMaxCpu() { return _maxCpu; }
|
||||
|
||||
// The amount of available ram usage on this server box.
|
||||
private int _maxRam;
|
||||
public int getMaxRam() { return _maxRam; }
|
||||
|
||||
// A mapping of server group names (Key) to the number of server instances (Value)
|
||||
private Map<String, Integer> _serverCounts;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param data - the set of serialized data values representing
|
||||
* the internal state of this DedicatedServer.
|
||||
*/
|
||||
public DedicatedServer(Map<String, String> data)
|
||||
{
|
||||
_name = data.get("name");
|
||||
_publicAddress = data.get("publicAddress");
|
||||
_privateAddress = data.get("privateAddress");
|
||||
_region = Region.valueOf(data.get("region").toUpperCase());
|
||||
_availableCpu = Integer.valueOf(data.get("cpu"));
|
||||
_availableRam = Integer.valueOf(data.get("ram"));
|
||||
_maxCpu = Integer.valueOf(data.get("cpu"));
|
||||
_maxRam = Integer.valueOf(data.get("ram"));
|
||||
_serverCounts = new HashMap<String, Integer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of {@link MinecraftServer} instances on this server
|
||||
* for a specific {@link ServerGroup} type.
|
||||
* @param serverGroup - the {@link ServerGroup} whose server instance count is being set.
|
||||
* @param serverCount - the number of {@link MinecraftServer} instances active on this server.
|
||||
*/
|
||||
public void setServerCount(ServerGroup serverGroup, int serverCount)
|
||||
{
|
||||
if (_serverCounts.containsKey(serverGroup.getName()))
|
||||
{
|
||||
int currentAmount = _serverCounts.get(serverGroup.getName());
|
||||
_availableCpu += serverGroup.getRequiredCpu() * currentAmount;
|
||||
_availableRam += serverGroup.getRequiredRam() * currentAmount;
|
||||
}
|
||||
|
||||
_serverCounts.put(serverGroup.getName(), serverCount);
|
||||
_availableCpu -= serverGroup.getRequiredCpu() * serverCount;
|
||||
_availableRam -= serverGroup.getRequiredRam() * serverCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serverGroup - the server group whose server count on this dedicated server is being fetched.
|
||||
* @return the number of active {@link MinecraftServer}s on this dedicated server
|
||||
* that belong to {@code serverGroup}.
|
||||
*/
|
||||
public int getServerCount(ServerGroup serverGroup)
|
||||
{
|
||||
String groupName = serverGroup.getName();
|
||||
return _serverCounts.containsKey(groupName) ? _serverCounts.get(groupName) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the number of {@link MinecraftServer} instances on this server
|
||||
* for a specific {@link ServerGroup} type by 1.
|
||||
* @param serverGroup - the {@link ServerGroup} whose server instance count is being incremented
|
||||
*/
|
||||
public void incrementServerCount(ServerGroup serverGroup)
|
||||
{
|
||||
setServerCount(serverGroup, getServerCount(serverGroup) + 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
public class MinecraftServer
|
||||
{
|
||||
// The name of this server.
|
||||
private String _name;
|
||||
public String getName() { return _name; }
|
||||
|
||||
// The ServerGroup that this MinecraftServer belongs to.
|
||||
private String _group;
|
||||
public String getGroup() { return _group; }
|
||||
|
||||
// The current message of the day (MOTD) of the server.
|
||||
private String _motd;
|
||||
public String getMotd() { return _motd; }
|
||||
|
||||
// The number of players currently online.
|
||||
private int _playerCount;
|
||||
public int getPlayerCount() { return _playerCount; }
|
||||
public void incrementPlayerCount(int amount) { this._playerCount += amount; }
|
||||
|
||||
// The maximum number of players allowed on the server.
|
||||
private int _maxPlayerCount;
|
||||
public int getMaxPlayerCount() { return _maxPlayerCount; }
|
||||
|
||||
// The ticks per second (TPS) of the server.
|
||||
private int _tps;
|
||||
public int getTps() { return _tps; }
|
||||
|
||||
// The current amount of RAM allocated to the server.
|
||||
private int _ram;
|
||||
public int getRam() { return _ram; }
|
||||
|
||||
// The maximum amount of available RAM that can be allocated to the server.
|
||||
private int _maxRam;
|
||||
public int getMaxRam() { return _maxRam; }
|
||||
|
||||
// The public I.P address used by players to connect to the server.
|
||||
private String _publicAddress;
|
||||
public String getPublicAddress() { return _publicAddress; }
|
||||
|
||||
// The port the server is currently running/listening on.
|
||||
private int _port;
|
||||
public int getPort() { return _port; }
|
||||
|
||||
private int _donorsOnline;
|
||||
public int getDonorsOnline() { return _donorsOnline; }
|
||||
|
||||
private long _startUpDate;
|
||||
|
||||
private long _currentTime;
|
||||
public long getCurrentTime()
|
||||
{
|
||||
return this._currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param name
|
||||
* @param group
|
||||
* @param motd
|
||||
* @param publicAddress
|
||||
* @param port
|
||||
* @param playerCount
|
||||
* @param maxPlayerCount
|
||||
* @param tps
|
||||
* @param ram
|
||||
* @param maxRam
|
||||
*/
|
||||
public MinecraftServer(String name, String group, String motd, String publicAddress, int port,
|
||||
int playerCount, int maxPlayerCount, int tps, int ram, int maxRam, long startUpDate, int donorsOnline)
|
||||
{
|
||||
_name = name;
|
||||
_group = group;
|
||||
_motd = motd;
|
||||
_playerCount = playerCount;
|
||||
_maxPlayerCount = maxPlayerCount;
|
||||
_tps = tps;
|
||||
_ram = ram;
|
||||
_maxRam = maxRam;
|
||||
_publicAddress = publicAddress;
|
||||
_port = port;
|
||||
_donorsOnline = donorsOnline;
|
||||
_startUpDate = startUpDate;
|
||||
_currentTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return _playerCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount of time (in seconds) that this {@link MinecraftServer} has been online for.
|
||||
*/
|
||||
public double getUptime()
|
||||
{
|
||||
return (System.currentTimeMillis() / 1000d - _startUpDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if this server is currently joinable by players, false otherwise.
|
||||
*/
|
||||
public boolean isJoinable()
|
||||
{
|
||||
if (_motd == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is super dodgy, this is the only way around monitor not killing game servers with the new MOTD system
|
||||
if (_motd.isEmpty() || _motd.contains("VOTING") || _motd.contains("STARTING") || _motd.contains("WAITING") || _motd.contains("ALWAYS_OPEN"))
|
||||
{
|
||||
if (_playerCount < _maxPlayerCount)
|
||||
{
|
||||
int availableSlots = _maxPlayerCount - _playerCount;
|
||||
|
||||
return !_motd.isEmpty() || (availableSlots > 20);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setGroup(String group)
|
||||
{
|
||||
_group = group;
|
||||
}
|
||||
public void setName(String name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
public class PlayerServerInfo implements Data
|
||||
{
|
||||
|
||||
private String _playerName;
|
||||
public String getPlayerName() { return _playerName; }
|
||||
|
||||
private String _lastServer;
|
||||
public String getLastServer() { return _lastServer; }
|
||||
public void setLastServer(String lastServer) { _lastServer = lastServer; }
|
||||
|
||||
public PlayerServerInfo(String playerName, String lastServer)
|
||||
{
|
||||
_playerName = playerName;
|
||||
_lastServer = lastServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDataId()
|
||||
{
|
||||
return _playerName;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerStatus implements Data
|
||||
{
|
||||
// The uuid of this player.
|
||||
private UUID _uuid;
|
||||
public UUID getUUID() { return _uuid; }
|
||||
|
||||
// The name of this player.
|
||||
private String _name;
|
||||
public String getName() { return _name; }
|
||||
|
||||
// The current server occupied by this player.
|
||||
private String _server;
|
||||
public String getServer() { return _server; }
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param name
|
||||
* @param server
|
||||
*/
|
||||
public PlayerStatus(UUID uuid, String name, String server)
|
||||
{
|
||||
_uuid = uuid;
|
||||
_name = name;
|
||||
_server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unique identifying String ID associated with this {@link PlayerStatus}.
|
||||
*/
|
||||
public String getDataId()
|
||||
{
|
||||
return _uuid.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,417 @@
|
||||
package mineplex.serverdata.data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
|
||||
public class ServerGroup
|
||||
{
|
||||
private HashMap<String, String> _dataMap = null;
|
||||
|
||||
private String _name;
|
||||
private String _host;
|
||||
private String _prefix;
|
||||
|
||||
private int _minPlayers;
|
||||
private int _maxPlayers;
|
||||
|
||||
private int _requiredRam;
|
||||
private int _requiredCpu;
|
||||
private int _requiredTotalServers;
|
||||
private int _requiredJoinableServers;
|
||||
|
||||
private String _uptimes = "";
|
||||
|
||||
private boolean _arcadeGroup;
|
||||
private String _worldZip;
|
||||
private String _plugin;
|
||||
private String _configPath;
|
||||
private int _portSection;
|
||||
|
||||
private boolean _pvp;
|
||||
private boolean _tournament;
|
||||
private boolean _tournamentPoints;
|
||||
private boolean _teamRejoin;
|
||||
private boolean _teamAutoJoin;
|
||||
private boolean _teamForceBalance;
|
||||
|
||||
private boolean _gameAutoStart;
|
||||
private boolean _gameTimeout;
|
||||
private boolean _gameVoting, _mapVoting;
|
||||
private boolean _rewardGems;
|
||||
private boolean _rewardItems;
|
||||
private boolean _rewardStats;
|
||||
private boolean _rewardAchievements;
|
||||
|
||||
private boolean _hotbarInventory;
|
||||
private boolean _hotbarHubClock;
|
||||
private boolean _playerKickIdle;
|
||||
private boolean _hardMaxPlayerCap;
|
||||
|
||||
private String _games;
|
||||
private String _modes;
|
||||
private String _boosterGroup;
|
||||
private String _serverType;
|
||||
private boolean _addNoCheat;
|
||||
private boolean _addWorldEdit;
|
||||
private boolean _whitelist;
|
||||
private boolean _staffOnly;
|
||||
private String _resourcePack = "";
|
||||
|
||||
private String _npcName = "";
|
||||
private String _portalBottomCornerLocation = "";
|
||||
private String _portalTopCornerLocation = "";
|
||||
|
||||
private String _teamServerKey = "";
|
||||
|
||||
private Region _region;
|
||||
|
||||
private Set<MinecraftServer> _servers;
|
||||
|
||||
public ServerGroup(Map<String, String> data, Collection<MinecraftServer> serverStatuses)
|
||||
{
|
||||
_name = data.get("name");
|
||||
_prefix = data.get("prefix");
|
||||
_requiredRam = Integer.valueOf(data.get("ram"));
|
||||
_requiredCpu = Integer.valueOf(data.get("cpu"));
|
||||
_requiredTotalServers = Integer.valueOf(data.get("totalServers"));
|
||||
_requiredJoinableServers = Integer.valueOf(data.get("joinableServers"));
|
||||
_portSection = Integer.valueOf(data.get("portSection"));
|
||||
_uptimes = data.getOrDefault("uptimes", "");
|
||||
_arcadeGroup = Boolean.valueOf(data.get("arcadeGroup"));
|
||||
_worldZip = data.get("worldZip");
|
||||
_plugin = data.get("plugin");
|
||||
_configPath = data.get("configPath");
|
||||
_minPlayers = Integer.valueOf(data.get("minPlayers"));
|
||||
_maxPlayers = Integer.valueOf(data.get("maxPlayers"));
|
||||
_pvp = Boolean.valueOf(data.get("pvp"));
|
||||
_tournament = Boolean.valueOf(data.get("tournament"));
|
||||
_tournamentPoints = Boolean.valueOf(data.get("tournamentPoints"));
|
||||
_hardMaxPlayerCap = Boolean.valueOf(data.get("hardMaxPlayerCap"));
|
||||
_games = data.get("games");
|
||||
_modes = data.get("modes");
|
||||
_boosterGroup = data.get("boosterGroup");
|
||||
_serverType = data.get("serverType");
|
||||
_addNoCheat = Boolean.valueOf(data.get("addNoCheat"));
|
||||
_addWorldEdit = Boolean.valueOf(data.get("addWorldEdit"));
|
||||
_teamRejoin = Boolean.valueOf(data.get("teamRejoin"));
|
||||
_teamAutoJoin = Boolean.valueOf(data.get("teamAutoJoin"));
|
||||
_teamForceBalance = Boolean.valueOf(data.get("teamForceBalance"));
|
||||
_gameAutoStart = Boolean.valueOf(data.get("gameAutoStart"));
|
||||
_gameTimeout = Boolean.valueOf(data.get("gameTimeout"));
|
||||
_gameVoting = Boolean.valueOf(data.get("gameVoting"));
|
||||
_mapVoting = Boolean.valueOf(data.get("mapVoting"));
|
||||
_rewardGems = Boolean.valueOf(data.get("rewardGems"));
|
||||
_rewardItems = Boolean.valueOf(data.get("rewardItems"));
|
||||
_rewardStats = Boolean.valueOf(data.get("rewardStats"));
|
||||
_rewardAchievements = Boolean.valueOf(data.get("rewardAchievements"));
|
||||
_hotbarInventory = Boolean.valueOf(data.get("hotbarInventory"));
|
||||
_hotbarHubClock = Boolean.valueOf(data.get("hotbarHubClock"));
|
||||
_playerKickIdle = Boolean.valueOf(data.get("playerKickIdle"));
|
||||
_staffOnly = Boolean.valueOf(data.get("staffOnly"));
|
||||
_whitelist = Boolean.valueOf(data.get("whitelist"));
|
||||
_resourcePack = data.getOrDefault("resourcePack", "");
|
||||
_host = data.get("host");
|
||||
_region = data.containsKey("region") ? Region.valueOf(data.get("region")) : Region.ALL;
|
||||
_teamServerKey = data.getOrDefault("teamServerKey", "");
|
||||
_portalBottomCornerLocation = data.getOrDefault("portalBottomCornerLocation", "");
|
||||
_portalTopCornerLocation = data.getOrDefault("portalTopCornerLocation", "");
|
||||
_npcName = data.getOrDefault("npcName", "");
|
||||
|
||||
if (serverStatuses != null)
|
||||
parseServers(serverStatuses);
|
||||
}
|
||||
|
||||
public ServerGroup(String name, String prefix, String host, int ram, int cpu, int totalServers, int joinable, int portSection, String uptimes, boolean arcade, String worldZip, String plugin, String configPath
|
||||
, int minPlayers, int maxPlayers, boolean pvp, boolean tournament, boolean tournamentPoints, String games, String modes, String boosterGroup, String serverType, boolean noCheat, boolean worldEdit, boolean teamRejoin
|
||||
, boolean teamAutoJoin, boolean teamForceBalance, boolean gameAutoStart, boolean gameTimeout, boolean gameVoting, boolean mapVoting, boolean rewardGems, boolean rewardItems, boolean rewardStats
|
||||
, boolean rewardAchievements, boolean hotbarInventory, boolean hotbarHubClock, boolean playerKickIdle, boolean hardMaxPlayerCap, boolean staffOnly, boolean whitelist, String resourcePack, Region region
|
||||
, String teamServerKey, String portalBottomCornerLocation, String portalTopCornerLocation, String npcName)
|
||||
{
|
||||
_name = name;
|
||||
_prefix = prefix;
|
||||
_host = host;
|
||||
_requiredRam = ram;
|
||||
_requiredCpu = cpu;
|
||||
_requiredTotalServers = totalServers;
|
||||
_requiredJoinableServers = joinable;
|
||||
_portSection = portSection;
|
||||
_uptimes = uptimes;
|
||||
_arcadeGroup = arcade;
|
||||
_worldZip = worldZip;
|
||||
_plugin = plugin;
|
||||
_configPath = configPath;
|
||||
_minPlayers = minPlayers;
|
||||
_maxPlayers = maxPlayers;
|
||||
_pvp = pvp;
|
||||
_tournament = tournament;
|
||||
_tournamentPoints = tournamentPoints;
|
||||
_games = games;
|
||||
_modes = modes;
|
||||
_boosterGroup = boosterGroup;
|
||||
_serverType = serverType;
|
||||
_addNoCheat = noCheat;
|
||||
_addWorldEdit = worldEdit;
|
||||
_teamRejoin = teamRejoin;
|
||||
_teamAutoJoin = teamAutoJoin;
|
||||
_teamForceBalance = teamForceBalance;
|
||||
_gameAutoStart = gameAutoStart;
|
||||
_gameTimeout = gameTimeout;
|
||||
_gameVoting = gameVoting;
|
||||
_mapVoting = mapVoting;
|
||||
_rewardGems = rewardGems;
|
||||
_rewardItems = rewardItems;
|
||||
_rewardStats = rewardStats;
|
||||
_rewardAchievements = rewardAchievements;
|
||||
_hotbarInventory = hotbarInventory;
|
||||
_hotbarHubClock = hotbarHubClock;
|
||||
_playerKickIdle = playerKickIdle;
|
||||
_hardMaxPlayerCap = hardMaxPlayerCap;
|
||||
_staffOnly = staffOnly;
|
||||
_whitelist = whitelist;
|
||||
_resourcePack = resourcePack;
|
||||
_region = region;
|
||||
_teamServerKey = teamServerKey;
|
||||
_portalBottomCornerLocation = portalBottomCornerLocation;
|
||||
_portalTopCornerLocation = portalTopCornerLocation;
|
||||
_npcName = npcName;
|
||||
}
|
||||
|
||||
public ServerGroup(String name, String npcName, String prefix)
|
||||
{
|
||||
_name = name;
|
||||
_npcName = npcName;
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public String getHost() { return _host; }
|
||||
public String getPrefix() { return _prefix; }
|
||||
|
||||
public int getMinPlayers() { return _minPlayers; }
|
||||
public int getMaxPlayers() { return _maxPlayers; }
|
||||
|
||||
public int getRequiredRam() { return _requiredRam; }
|
||||
public int getRequiredCpu() { return _requiredCpu; }
|
||||
public int getRequiredTotalServers() { return _requiredTotalServers; }
|
||||
public int getRequiredJoinableServers() { return _requiredJoinableServers; }
|
||||
public int getPortSection() { return _portSection; }
|
||||
|
||||
public boolean getArcadeGroup() { return _arcadeGroup; }
|
||||
public String getWorldZip() { return _worldZip; }
|
||||
public String getPlugin() { return _plugin; }
|
||||
public String getConfigPath() { return _configPath; }
|
||||
|
||||
public boolean getPvp() { return _pvp; }
|
||||
public boolean getTournament() { return _tournament; }
|
||||
public boolean getTournamentPoints() { return _tournamentPoints; }
|
||||
public boolean getTeamRejoin() { return _teamRejoin; }
|
||||
public boolean getTeamAutoJoin() { return _teamAutoJoin; }
|
||||
|
||||
public boolean getTeamForceBalance() { return _teamForceBalance; }
|
||||
public boolean getGameAutoStart() { return _gameAutoStart; }
|
||||
public boolean getGameTimeout() { return _gameTimeout; }
|
||||
public boolean getGameVoting() { return _gameVoting; }
|
||||
public boolean getMapVoting() { return _mapVoting; }
|
||||
public boolean getRewardGems() { return _rewardGems; }
|
||||
public boolean getRewardItems() { return _rewardItems; }
|
||||
public boolean getRewardStats() { return _rewardStats; }
|
||||
public boolean getRewardAchievements() { return _rewardAchievements; }
|
||||
|
||||
public boolean getHotbarInventory() { return _hotbarInventory; }
|
||||
public boolean getHotbarHubClock() { return _hotbarHubClock; }
|
||||
public boolean getPlayerKickIdle() { return _playerKickIdle; }
|
||||
public boolean getHardMaxPlayerCap() { return _hardMaxPlayerCap; }
|
||||
|
||||
public String getGames() { return _games; }
|
||||
public String getModes() { return _modes; }
|
||||
public String getBoosterGroup() { return _boosterGroup; }
|
||||
|
||||
public String getServerType() { return _serverType; }
|
||||
public boolean getAddNoCheat() { return _addNoCheat; }
|
||||
public boolean getAddWorldEdit() { return _addWorldEdit; }
|
||||
public boolean getWhitelist() { return _whitelist; }
|
||||
public boolean getStaffOnly() { return _staffOnly; }
|
||||
public String getResourcePack() { return _resourcePack; }
|
||||
public Region getRegion() { return _region; }
|
||||
|
||||
public String getTeamServerKey() { return _teamServerKey; }
|
||||
|
||||
public String getServerNpcName() { return _npcName; }
|
||||
public String getPortalBottomCornerLocation() { return _portalBottomCornerLocation; }
|
||||
public String getPortalTopCornerLocation() { return _portalTopCornerLocation; }
|
||||
public String getUptimes() { return _uptimes; }
|
||||
|
||||
public Set<MinecraftServer> getServers() { return _servers; }
|
||||
|
||||
public int getServerCount()
|
||||
{
|
||||
return _servers.size();
|
||||
}
|
||||
|
||||
public int getJoinableCount()
|
||||
{
|
||||
int joinable = 0;
|
||||
|
||||
for (MinecraftServer server : _servers)
|
||||
{
|
||||
if (server.isJoinable())
|
||||
{
|
||||
joinable++;
|
||||
}
|
||||
}
|
||||
|
||||
return joinable;
|
||||
}
|
||||
|
||||
public int getPlayerCount()
|
||||
{
|
||||
int playerCount = 0;
|
||||
|
||||
for (MinecraftServer server : _servers)
|
||||
{
|
||||
playerCount += server.getPlayerCount();
|
||||
}
|
||||
|
||||
return playerCount;
|
||||
}
|
||||
|
||||
public int getMaxPlayerCount()
|
||||
{
|
||||
int maxPlayerCount = 0;
|
||||
|
||||
for (MinecraftServer server : _servers)
|
||||
{
|
||||
maxPlayerCount += server.getMaxPlayerCount();
|
||||
}
|
||||
|
||||
return maxPlayerCount;
|
||||
}
|
||||
|
||||
public Collection<MinecraftServer> getEmptyServers()
|
||||
{
|
||||
Collection<MinecraftServer> emptyServers = new HashSet<MinecraftServer>();
|
||||
|
||||
for (MinecraftServer server : _servers)
|
||||
{
|
||||
if (server.isEmpty() && server.getUptime() >= 150) // Only return empty servers that have been online for >150 seconds
|
||||
{
|
||||
emptyServers.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
return emptyServers;
|
||||
}
|
||||
|
||||
private void parseServers(Collection<MinecraftServer> servers)
|
||||
{
|
||||
_servers = new HashSet<>();
|
||||
|
||||
for (MinecraftServer server : servers)
|
||||
{
|
||||
if (_name.equalsIgnoreCase(server.getGroup()))
|
||||
{
|
||||
_servers.add(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int generateUniqueId(int startId)
|
||||
{
|
||||
int id = startId;
|
||||
|
||||
while (true)
|
||||
{
|
||||
boolean uniqueId = true;
|
||||
|
||||
for (MinecraftServer server : _servers)
|
||||
{
|
||||
String serverName = server.getName();
|
||||
try
|
||||
{
|
||||
String[] nameArgs = serverName.split("-");
|
||||
int serverNum = Integer.parseInt(nameArgs[nameArgs.length - 1]);
|
||||
|
||||
if (serverNum == id)
|
||||
{
|
||||
uniqueId = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueId)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
else
|
||||
{
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, String> getDataMap()
|
||||
{
|
||||
if (_dataMap == null)
|
||||
{
|
||||
_dataMap = new HashMap<>();
|
||||
|
||||
_dataMap.put("name", _name);
|
||||
_dataMap.put("prefix", _prefix);
|
||||
_dataMap.put("ram", _requiredRam + "");
|
||||
_dataMap.put("cpu", _requiredCpu + "");
|
||||
_dataMap.put("totalServers", _requiredTotalServers + "");
|
||||
_dataMap.put("joinableServers", _requiredJoinableServers + "");
|
||||
_dataMap.put("portSection", _portSection + "");
|
||||
_dataMap.put("uptimes", _uptimes);
|
||||
_dataMap.put("arcadeGroup", _arcadeGroup + "");
|
||||
_dataMap.put("worldZip", _worldZip);
|
||||
_dataMap.put("plugin", _plugin);
|
||||
_dataMap.put("configPath", _configPath);
|
||||
_dataMap.put("minPlayers", _minPlayers + "");
|
||||
_dataMap.put("maxPlayers", _maxPlayers + "");
|
||||
_dataMap.put("pvp", _pvp + "");
|
||||
_dataMap.put("tournament", _tournament + "");
|
||||
_dataMap.put("tournamentPoints", _tournamentPoints + "");
|
||||
_dataMap.put("games", _games);
|
||||
_dataMap.put("modes", _modes);
|
||||
_dataMap.put("serverType", _serverType);
|
||||
_dataMap.put("addNoCheat", _addNoCheat + "");
|
||||
_dataMap.put("teamRejoin", _teamRejoin + "");
|
||||
_dataMap.put("teamAutoJoin", _teamAutoJoin + "");
|
||||
_dataMap.put("teamForceBalance", _teamForceBalance + "");
|
||||
_dataMap.put("gameAutoStart", _gameAutoStart + "");
|
||||
_dataMap.put("gameTimeout", _gameTimeout + "");
|
||||
_dataMap.put("gameVoting", String.valueOf(_gameVoting));
|
||||
_dataMap.put("mapVoting", String.valueOf(_mapVoting));
|
||||
_dataMap.put("rewardGems", _rewardGems + "");
|
||||
_dataMap.put("rewardItems", _rewardItems + "");
|
||||
_dataMap.put("rewardStats", _rewardStats + "");
|
||||
_dataMap.put("rewardAchievements", _rewardAchievements + "");
|
||||
_dataMap.put("hotbarInventory", _hotbarInventory + "");
|
||||
_dataMap.put("hotbarHubClock", _hotbarHubClock + "");
|
||||
_dataMap.put("playerKickIdle", _playerKickIdle + "");
|
||||
_dataMap.put("staffOnly", _staffOnly + "");
|
||||
_dataMap.put("whitelist", _whitelist + "");
|
||||
_dataMap.put("resourcePack", _resourcePack);
|
||||
_dataMap.put("host", _host);
|
||||
_dataMap.put("region", _region.name());
|
||||
_dataMap.put("teamServerKey", _teamServerKey);
|
||||
_dataMap.put("boosterGroup", _boosterGroup);
|
||||
_dataMap.put("portalBottomCornerLocation", _portalBottomCornerLocation);
|
||||
_dataMap.put("portalTopCornerLocation", _portalTopCornerLocation);
|
||||
_dataMap.put("npcName", _npcName);
|
||||
}
|
||||
|
||||
return _dataMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
package mineplex.serverdata.database;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.sql.Connection;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
|
||||
public final class DBPool
|
||||
{
|
||||
private static DataSource ACCOUNT;
|
||||
private static DataSource QUEUE;
|
||||
private static DataSource MINEPLEX;
|
||||
private static DataSource MINEPLEX_STATS;
|
||||
private static DataSource PLAYER_STATS;
|
||||
private static DataSource SERVER_STATS;
|
||||
private static DataSource MSSQL_MOCK;
|
||||
|
||||
private static DataSource openDataSource(String url, String username, String password)
|
||||
{
|
||||
BasicDataSource source = new BasicDataSource();
|
||||
source.addConnectionProperty("autoReconnect", "true");
|
||||
source.addConnectionProperty("allowMultiQueries", "true");
|
||||
source.addConnectionProperty("zeroDateTimeBehavior", "convertToNull");
|
||||
source.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
|
||||
source.setDriverClassName("com.mysql.jdbc.Driver");
|
||||
source.setUrl(url);
|
||||
source.setUsername(username);
|
||||
source.setPassword(password);
|
||||
source.setMaxTotal(5);
|
||||
source.setMaxIdle(5);
|
||||
source.setTimeBetweenEvictionRunsMillis(180 * 1000);
|
||||
source.setSoftMinEvictableIdleTimeMillis(180 * 1000);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
public static DataSource getMssqlMock()
|
||||
{
|
||||
if (MSSQL_MOCK == null)
|
||||
loadDataSources();
|
||||
|
||||
return MSSQL_MOCK;
|
||||
}
|
||||
|
||||
public static DataSource getAccount()
|
||||
{
|
||||
if (ACCOUNT == null)
|
||||
loadDataSources();
|
||||
|
||||
return ACCOUNT;
|
||||
}
|
||||
|
||||
public static DataSource getQueue()
|
||||
{
|
||||
if (QUEUE == null)
|
||||
loadDataSources();
|
||||
|
||||
return QUEUE;
|
||||
}
|
||||
|
||||
public static DataSource getMineplex()
|
||||
{
|
||||
if (MINEPLEX == null)
|
||||
loadDataSources();
|
||||
|
||||
return MINEPLEX;
|
||||
}
|
||||
|
||||
public static DataSource getMineplexStats()
|
||||
{
|
||||
if (MINEPLEX_STATS == null)
|
||||
loadDataSources();
|
||||
|
||||
return MINEPLEX_STATS;
|
||||
}
|
||||
|
||||
public static DataSource getPlayerStats()
|
||||
{
|
||||
if (PLAYER_STATS == null)
|
||||
loadDataSources();
|
||||
|
||||
return PLAYER_STATS;
|
||||
}
|
||||
|
||||
public static DataSource getServerStats()
|
||||
{
|
||||
if (SERVER_STATS == null)
|
||||
loadDataSources();
|
||||
|
||||
return SERVER_STATS;
|
||||
}
|
||||
|
||||
private static void loadDataSources()
|
||||
{
|
||||
try
|
||||
{
|
||||
File configFile = new File("database-config.dat");
|
||||
|
||||
if (configFile.exists())
|
||||
{
|
||||
List<String> lines = Files.readAllLines(configFile.toPath(), Charset.defaultCharset());
|
||||
|
||||
for (String line : lines)
|
||||
{
|
||||
deserializeConnection(line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("database-config.dat not found at " + configFile.toPath().toString());
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
System.out.println("---Unable To Parse DBPOOL Configuration File---");
|
||||
}
|
||||
}
|
||||
|
||||
private static void deserializeConnection(String line)
|
||||
{
|
||||
String[] args = line.split(" ");
|
||||
if (args.length == 3)
|
||||
{
|
||||
String dbHost = args[0];
|
||||
String userName = args[1];
|
||||
String password = args[2];
|
||||
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost + "/account", userName, password);
|
||||
QUEUE = openDataSource("jdbc:mysql://" + dbHost + "/queue", userName, password);
|
||||
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost + "/mineplex", userName, password);
|
||||
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost + "/mineplex_stats", userName, password);
|
||||
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost + "/player_stats", userName, password);
|
||||
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost + "/server_stats", userName, password);
|
||||
}
|
||||
/*
|
||||
String[] args = line.split(" ");
|
||||
|
||||
if (args.length == 4)
|
||||
{
|
||||
String dbHost = args[0];
|
||||
String userName = args[1];
|
||||
String password = args[2];
|
||||
|
||||
System.out.println(userName.toString());
|
||||
System.out.println(dbHost.toString());
|
||||
System.out.println(password.toString());
|
||||
|
||||
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
QUEUE = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
MSSQL_MOCK = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
|
||||
if (dbSource.toUpperCase().equalsIgnoreCase("ACCOUNT"))
|
||||
ACCOUNT = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("QUEUE"))
|
||||
QUEUE = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("MINEPLEX"))
|
||||
MINEPLEX = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("MINEPLEX_STATS"))
|
||||
MINEPLEX_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("PLAYER_STATS"))
|
||||
PLAYER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("SERVER_STATS"))
|
||||
SERVER_STATS = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
else if (dbSource.toUpperCase().equalsIgnoreCase("MSSQL_MOCK"))
|
||||
MSSQL_MOCK = openDataSource("jdbc:mysql://" + dbHost, userName, password);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package mineplex.serverdata.database;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.omg.CORBA.ACTIVITY_REQUIRED;
|
||||
|
||||
public class DatabaseRunnable
|
||||
{
|
||||
private Runnable _runnable;
|
||||
private String _errorMessage;
|
||||
private AtomicInteger _failedAttempts = new AtomicInteger(0);
|
||||
|
||||
public DatabaseRunnable(Runnable runnable, String error)
|
||||
{
|
||||
_runnable = runnable;
|
||||
_errorMessage = error;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
_runnable.run();
|
||||
}
|
||||
|
||||
public String getErrorMessage()
|
||||
{
|
||||
return _errorMessage;
|
||||
}
|
||||
|
||||
public void incrementFailCount()
|
||||
{
|
||||
_failedAttempts.getAndIncrement();
|
||||
}
|
||||
|
||||
public int getFailedCounts()
|
||||
{
|
||||
return _failedAttempts.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
package mineplex.serverdata.database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException;
|
||||
|
||||
import org.jooq.DSLContext;
|
||||
import org.jooq.SQLDialect;
|
||||
import org.jooq.impl.DSL;
|
||||
|
||||
import mineplex.serverdata.database.column.Column;
|
||||
|
||||
public abstract class RepositoryBase
|
||||
{
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
} catch (ClassNotFoundException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource _dataSource; // Connection pool
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param dataSource - the {@link DataSource} responsible for providing the connection pool to this repository.
|
||||
*/
|
||||
public RepositoryBase(DataSource dataSource)
|
||||
{
|
||||
_dataSource = dataSource;
|
||||
|
||||
new Thread(() -> {
|
||||
initialize();
|
||||
update();
|
||||
}).start();
|
||||
}
|
||||
|
||||
protected void initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link DataSource} used by the repository for connection pooling.
|
||||
*/
|
||||
protected DataSource getConnectionPool()
|
||||
{
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requirements: {@link Connection}s must be closed after usage so they may be returned to the pool!
|
||||
* @see Connection#close()
|
||||
* @return a newly fetched {@link Connection} from the connection pool, if a connection can be made, null otherwise.
|
||||
*/
|
||||
protected Connection getConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _dataSource.getConnection();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// TODO: Log connection failures?
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected int executeUpdate(Connection connection, String query, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
return executeInsert(connection, query, null, onSQLError, columns);
|
||||
}
|
||||
|
||||
protected int executeUpdate(String query, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
return executeInsert(query, null, onSQLError, columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query against the repository.
|
||||
* @param query - the concatenated query to execute in string form.
|
||||
* @param columns - the column data values used for insertion into the query.
|
||||
* @return the number of rows affected by this query in the repository.
|
||||
*/
|
||||
protected int executeUpdate(String query, Column<?>...columns)
|
||||
{
|
||||
return executeInsert(query, null, columns);
|
||||
}
|
||||
|
||||
protected int executeInsert(Connection connection, String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
int affectedRows = 0;
|
||||
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)
|
||||
)
|
||||
{
|
||||
for (int i=0; i < columns.length; i++)
|
||||
{
|
||||
columns[i].setValue(preparedStatement, i+1);
|
||||
}
|
||||
|
||||
affectedRows = preparedStatement.executeUpdate();
|
||||
|
||||
if (callable != null)
|
||||
{
|
||||
callable.processResultSet(preparedStatement.getGeneratedKeys());
|
||||
}
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
if (onSQLError != null)
|
||||
{
|
||||
onSQLError.run();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
return affectedRows;
|
||||
}
|
||||
|
||||
protected int executeInsert(String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
int affectedRows = 0;
|
||||
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
)
|
||||
{
|
||||
affectedRows = executeInsert(connection, query, callable, onSQLError, columns);
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
if (onSQLError != null)
|
||||
{
|
||||
onSQLError.run();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
return affectedRows;
|
||||
}
|
||||
|
||||
protected int executeInsert(String query, ResultSetCallable callable, Column<?>...columns)
|
||||
{
|
||||
int affectedRows = 0;
|
||||
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
)
|
||||
{
|
||||
affectedRows = executeInsert(connection, query, callable, null, columns);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
return affectedRows;
|
||||
}
|
||||
|
||||
protected void executeQuery(PreparedStatement statement, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i=0; i < columns.length; i++)
|
||||
{
|
||||
columns[i].setValue(statement, i+1);
|
||||
}
|
||||
|
||||
try (ResultSet resultSet = statement.executeQuery())
|
||||
{
|
||||
if (callable != null)
|
||||
{
|
||||
callable.processResultSet(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
if (onSQLError != null)
|
||||
{
|
||||
onSQLError.run();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeQuery(PreparedStatement statement, ResultSetCallable callable, Column<?>...columns)
|
||||
{
|
||||
executeQuery(statement, callable, null, columns);
|
||||
}
|
||||
|
||||
protected void executeQuery(Connection connection, String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(query)
|
||||
)
|
||||
{
|
||||
executeQuery(preparedStatement, callable, onSQLError, columns);
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
if (onSQLError != null)
|
||||
{
|
||||
onSQLError.run();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeQuery(Connection connection, String query, ResultSetCallable callable, Column<?>...columns)
|
||||
{
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
PreparedStatement preparedStatement = connection.prepareStatement(query)
|
||||
)
|
||||
{
|
||||
executeQuery(preparedStatement, callable, columns);
|
||||
}
|
||||
catch (MySQLSyntaxErrorException syntaxException)
|
||||
{
|
||||
System.err.println("Query \"" + query + "\" contained a syntax error:");
|
||||
syntaxException.printStackTrace();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeQuery(String query, ResultSetCallable callable, Runnable onSQLError, Column<?>...columns)
|
||||
{
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
)
|
||||
{
|
||||
executeQuery(connection, query, callable, onSQLError, columns);
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
if (onSQLError != null)
|
||||
{
|
||||
onSQLError.run();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeQuery(String query, ResultSetCallable callable, Column<?>...columns)
|
||||
{
|
||||
// Automatic resource management for handling/closing objects.
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
)
|
||||
{
|
||||
executeQuery(connection, query, callable, columns);
|
||||
}
|
||||
catch (SQLException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
protected DSLContext jooq()
|
||||
{
|
||||
return DSL.using(DBPool.getAccount(), SQLDialect.MYSQL);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package mineplex.serverdata.database;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public interface ResultSetCallable
|
||||
{
|
||||
public void processResultSet(ResultSet resultSet) throws SQLException;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public abstract class Column<Type>
|
||||
{
|
||||
public String Name;
|
||||
public Type Value;
|
||||
|
||||
public Column(String name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public Column(String name, Type value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public abstract String getCreateString();
|
||||
|
||||
public abstract Type getValue(ResultSet resultSet) throws SQLException;
|
||||
|
||||
public abstract void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException;
|
||||
|
||||
public abstract Column<Type> clone();
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnBoolean extends Column<Boolean>
|
||||
{
|
||||
public ColumnBoolean(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public ColumnBoolean(String name, boolean value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " BOOLEAN";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getBoolean(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setBoolean(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnBoolean clone()
|
||||
{
|
||||
return new ColumnBoolean(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnByte extends Column<Byte>
|
||||
{
|
||||
public ColumnByte(String name)
|
||||
{
|
||||
super(name);
|
||||
Value = (byte)0;
|
||||
}
|
||||
|
||||
public ColumnByte(String name, Byte value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " TINYINT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Byte getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getByte(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setLong(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnByte clone()
|
||||
{
|
||||
return new ColumnByte(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnDouble extends Column<Double>
|
||||
{
|
||||
public ColumnDouble(String name)
|
||||
{
|
||||
super(name);
|
||||
Value = 0.0;
|
||||
}
|
||||
|
||||
public ColumnDouble(String name, Double value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " DOUBLE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getDouble(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setDouble(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnDouble clone()
|
||||
{
|
||||
return new ColumnDouble(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnInt extends Column<Integer>
|
||||
{
|
||||
public ColumnInt(String name)
|
||||
{
|
||||
super(name);
|
||||
Value = 0;
|
||||
}
|
||||
|
||||
public ColumnInt(String name, int value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " INT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getInt(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setInt(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnInt clone()
|
||||
{
|
||||
return new ColumnInt(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnLong extends Column<Long>
|
||||
{
|
||||
public ColumnLong(String name)
|
||||
{
|
||||
super(name);
|
||||
Value = 0L;
|
||||
}
|
||||
|
||||
public ColumnLong(String name, Long value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " LONG";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getLong(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setLong(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnLong clone()
|
||||
{
|
||||
return new ColumnLong(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
public class ColumnTimestamp extends Column<Timestamp>
|
||||
{
|
||||
public ColumnTimestamp(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public ColumnTimestamp(String name, Timestamp value)
|
||||
{
|
||||
super(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " TIMESTAMP";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timestamp getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getTimestamp(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setTimestamp(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnTimestamp clone()
|
||||
{
|
||||
return new ColumnTimestamp(Name, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package mineplex.serverdata.database.column;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ColumnVarChar extends Column<String>
|
||||
{
|
||||
public int Length = 25;
|
||||
|
||||
public ColumnVarChar(String name, int length)
|
||||
{
|
||||
this(name, length, "");
|
||||
}
|
||||
|
||||
public ColumnVarChar(String name, int length, String value)
|
||||
{
|
||||
super(name);
|
||||
|
||||
Length = length;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public String getCreateString()
|
||||
{
|
||||
return Name + " VARCHAR(" + Length + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(ResultSet resultSet) throws SQLException
|
||||
{
|
||||
return resultSet.getString(Name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(PreparedStatement preparedStatement, int columnNumber) throws SQLException
|
||||
{
|
||||
preparedStatement.setString(columnNumber, Value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnVarChar clone()
|
||||
{
|
||||
return new ColumnVarChar(Name, Length, Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package mineplex.serverdata.redis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||
|
||||
public class RedisConfig
|
||||
{
|
||||
// Failsafe values in case configuration is not provided
|
||||
private static final String DEFAULT_IP = "127.0.0.1";
|
||||
private static final int DEFAULT_PORT = 6379;
|
||||
private static Random random = new Random(); // Utility random
|
||||
|
||||
// The connections managed by this configuration
|
||||
private List<ConnectionData> _connections;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param connections
|
||||
*/
|
||||
public RedisConfig(List<ConnectionData> connections)
|
||||
{
|
||||
_connections = connections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* Produces a default-value based RedisConfig.
|
||||
*/
|
||||
public RedisConfig()
|
||||
{
|
||||
_connections = new ArrayList<ConnectionData>();
|
||||
_connections.add(new ConnectionData(DEFAULT_IP, DEFAULT_PORT, ConnectionType.MASTER, "DefaultConnection"));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code writeable} defaults to {@literal true}.
|
||||
*/
|
||||
public ConnectionData getConnection()
|
||||
{
|
||||
return getConnection(true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeable - whether the returned connection reference can receive write-requests.
|
||||
* @return a {@link ConnectionData} referencing a valid redis-connection from this configuration.
|
||||
*/
|
||||
public ConnectionData getConnection(boolean writeable, String name)
|
||||
{
|
||||
List<ConnectionData> connections = getConnections(writeable, name);
|
||||
|
||||
if (connections.size() > 0)
|
||||
{
|
||||
int index = random.nextInt(connections.size());
|
||||
return connections.get(index);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ConnectionData> getConnections(boolean writeable, String name)
|
||||
{
|
||||
List<ConnectionData> connections = new ArrayList<ConnectionData>();
|
||||
ConnectionType type = (writeable) ? ConnectionType.MASTER : ConnectionType.SLAVE;
|
||||
|
||||
for (ConnectionData connection : _connections)
|
||||
{
|
||||
if (connection.getType() == type && connection.nameMatches(name))
|
||||
{
|
||||
connections.add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
package mineplex.serverdata.redis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.data.Data;
|
||||
import mineplex.serverdata.data.DataRepository;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Response;
|
||||
import redis.clients.jedis.Transaction;
|
||||
|
||||
public class RedisDataRepository<T extends Data> extends RedisRepository implements DataRepository<T>
|
||||
{
|
||||
// The class type of the elements stored in this repository
|
||||
private Class<T> _elementType;
|
||||
// A unique label designating the elements and this repository.
|
||||
private String _elementLabel;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
* @param writeConn
|
||||
* @param readConn
|
||||
* @param region
|
||||
*/
|
||||
public RedisDataRepository(ConnectionData writeConn, ConnectionData readConn, Region region,
|
||||
Class<T> elementType, String elementLabel)
|
||||
{
|
||||
super(writeConn, readConn, region);
|
||||
_elementType = elementType;
|
||||
_elementLabel = elementLabel;
|
||||
}
|
||||
|
||||
public RedisDataRepository(ConnectionData conn, Region region, Class<T> elementType, String elementLabel)
|
||||
{
|
||||
this(conn, conn, region, elementType, elementLabel);
|
||||
}
|
||||
|
||||
public RedisDataRepository(Region region, Class<T> elementType, String elementLabel)
|
||||
{
|
||||
this(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(), region,
|
||||
elementType, elementLabel);
|
||||
}
|
||||
|
||||
public String getElementSetKey()
|
||||
{
|
||||
return concatenate("data", _elementLabel, getRegion().toString());
|
||||
}
|
||||
|
||||
public String generateKey(T element)
|
||||
{
|
||||
return generateKey(element.getDataId());
|
||||
}
|
||||
|
||||
public String generateKey(String dataId)
|
||||
{
|
||||
return concatenate(getElementSetKey(), dataId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getElements()
|
||||
{
|
||||
return getElements(getActiveElements());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getElements(Collection<String> dataIds)
|
||||
{
|
||||
Collection<T> elements = new HashSet<T>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
|
||||
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||
for (String dataId : dataIds)
|
||||
{
|
||||
responses.add(pipeline.get(generateKey(dataId)));
|
||||
}
|
||||
|
||||
// Block until all requests have received pipelined responses
|
||||
pipeline.sync();
|
||||
|
||||
for (Response<String> response : responses)
|
||||
{
|
||||
String serializedData = response.get();
|
||||
T element = deserialize(serializedData);
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
elements.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String,T> getElementsMap(List<String> dataIds)
|
||||
{
|
||||
Map<String,T> elements = new HashMap<>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
|
||||
List<Response<String>> responses = new ArrayList<>();
|
||||
for (String dataId : dataIds)
|
||||
{
|
||||
responses.add(pipeline.get(generateKey(dataId)));
|
||||
}
|
||||
|
||||
// Block until all requests have received pipelined responses
|
||||
pipeline.sync();
|
||||
|
||||
for (int i = 0; i < responses.size(); i++)
|
||||
{
|
||||
String key = dataIds.get(i);
|
||||
|
||||
Response<String> response = responses.get(i);
|
||||
String serializedData = response.get();
|
||||
T element = deserialize(serializedData);
|
||||
|
||||
elements.put(key, element);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getElement(String dataId)
|
||||
{
|
||||
T element = null;
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String key = generateKey(dataId);
|
||||
String serializedData = jedis.get(key);
|
||||
element = deserialize(serializedData);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElement(T element, int timeout)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
String serializedData = serialize(element);
|
||||
String dataId = element.getDataId();
|
||||
String setKey = getElementSetKey();
|
||||
String dataKey = generateKey(element);
|
||||
long expiry = currentTime() + timeout;
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.set(dataKey, serializedData);
|
||||
transaction.zadd(setKey, expiry, dataId.toString());
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElement(T element)
|
||||
{
|
||||
addElement(element, 60 * 60 * 24 * 7 * 4 * 12 * 10); // Set the timeout to 10 years
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(T element)
|
||||
{
|
||||
removeElement(element.getDataId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(String dataId)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
String setKey = getElementSetKey();
|
||||
String dataKey = generateKey(dataId);
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.del(dataKey);
|
||||
transaction.zrem(setKey, dataId);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean elementExists(String dataId)
|
||||
{
|
||||
return getElement(dataId) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clean()
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
for (String dataId : getDeadElements())
|
||||
{
|
||||
String dataKey = generateKey(dataId);
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.del(dataKey);
|
||||
transaction.zrem(getElementSetKey(), dataId);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected Set<String> getActiveElements()
|
||||
{
|
||||
Set<String> dataIds = new HashSet<String>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String min = "(" + currentTime();
|
||||
String max = "+inf";
|
||||
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
|
||||
}
|
||||
|
||||
return dataIds;
|
||||
}
|
||||
|
||||
protected Set<String> getDeadElements()
|
||||
{
|
||||
Set<String> dataIds = new HashSet<String>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String min = "-inf";
|
||||
String max = currentTime() + "";
|
||||
dataIds = jedis.zrangeByScore(getElementSetKey(), min, max);
|
||||
}
|
||||
|
||||
return dataIds;
|
||||
}
|
||||
|
||||
protected T deserialize(String serializedData)
|
||||
{
|
||||
return Utility.deserialize(serializedData, _elementType);
|
||||
}
|
||||
|
||||
protected String serialize(T element)
|
||||
{
|
||||
return Utility.serialize(element);
|
||||
}
|
||||
|
||||
protected Long currentTime()
|
||||
{
|
||||
return Utility.currentTimeSeconds();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package mineplex.serverdata.redis;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ServerManager;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
/**
|
||||
* Repository for managing Redis Connections
|
||||
* @author Shaun Bennett
|
||||
*/
|
||||
public class RedisRepository
|
||||
{
|
||||
protected static final char KEY_DELIMITER = '.';
|
||||
|
||||
private JedisPool _writePool;
|
||||
private JedisPool _readPool;
|
||||
private Region _region;
|
||||
|
||||
public RedisRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||
{
|
||||
_writePool = Utility.generatePool(writeConn);
|
||||
_readPool = (writeConn == readConn) ? _writePool : Utility.generatePool(readConn);
|
||||
_region = region;
|
||||
}
|
||||
|
||||
public RedisRepository(Region region)
|
||||
{
|
||||
this(ServerManager.getMasterConnection(), ServerManager.getSlaveConnection(), region);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Jedis Resource from the pool. This Jedis instance needs to be closed when you are done with using it.
|
||||
* Call jedis.close() or use try with resources when using getResource()
|
||||
*
|
||||
* @param writeable If we need to be able to write to redis. Trying to write to a non writeable jedis instance will
|
||||
* throw an error.
|
||||
* @return {@link Jedis} instance from pool
|
||||
*/
|
||||
protected Jedis getResource(boolean writeable)
|
||||
{
|
||||
return (writeable ? _writePool : _readPool).getResource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server region that this redis repository is for. The region will affect the keys for redis
|
||||
* @return server region
|
||||
*/
|
||||
public Region getRegion()
|
||||
{
|
||||
return _region;
|
||||
}
|
||||
|
||||
protected String getKey(String dataKey)
|
||||
{
|
||||
return concatenate("minecraft", "data", _region.name(), dataKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param elements - the elements to concatenate together
|
||||
* @return the concatenated form of all {@code elements}
|
||||
* separated by the delimiter {@value KEY_DELIMITER}.
|
||||
*/
|
||||
protected String concatenate(String... elements)
|
||||
{
|
||||
return Utility.concatenate(KEY_DELIMITER, elements);
|
||||
}
|
||||
}
|
@ -0,0 +1,392 @@
|
||||
package mineplex.serverdata.redis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.data.DedicatedServer;
|
||||
import mineplex.serverdata.data.MinecraftServer;
|
||||
import mineplex.serverdata.data.ServerGroup;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import mineplex.serverdata.servers.ServerRepository;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Response;
|
||||
import redis.clients.jedis.Transaction;
|
||||
import redis.clients.jedis.Tuple;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
/**
|
||||
* RedisServerRepository offers a Redis-based implementation of {@link ServerRepository}
|
||||
* using a mixture of hash and JSON encoded storage.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public class RedisServerRepository extends RedisRepository implements ServerRepository
|
||||
{
|
||||
public RedisServerRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||
{
|
||||
super(writeConn, readConn, region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MinecraftServer> getServerStatuses()
|
||||
{
|
||||
return getServerStatusesByPrefix("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MinecraftServer> getServerStatusesByPrefix(String prefix)
|
||||
{
|
||||
Collection<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||
for (String serverName : getActiveNames(setKey))
|
||||
{
|
||||
if (prefix.isEmpty() || serverName.startsWith(prefix))
|
||||
{
|
||||
String dataKey = concatenate(setKey, serverName);
|
||||
responses.add(pipeline.get(dataKey));
|
||||
}
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
|
||||
for (Response<String> response : responses)
|
||||
{
|
||||
String serializedData = response.get();
|
||||
MinecraftServer server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
servers.add(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
return servers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MinecraftServer> getServersByGroup(String serverGroup)
|
||||
{
|
||||
Collection<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||
|
||||
for (MinecraftServer server : getServerStatuses())
|
||||
{
|
||||
if (server.getGroup().equalsIgnoreCase(serverGroup))
|
||||
{
|
||||
servers.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MinecraftServer getServerStatus(String serverName)
|
||||
{
|
||||
MinecraftServer server = null;
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||
String dataKey = concatenate(setKey, serverName);
|
||||
String serializedData = jedis.get(dataKey);
|
||||
server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updataServerStatus(MinecraftServer serverData, int timeout)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
String serializedData = Utility.serialize(serverData);
|
||||
String serverName = serverData.getName();
|
||||
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||
String dataKey = concatenate(setKey, serverName);
|
||||
long expiry = Utility.currentTimeSeconds() + timeout;
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.set(dataKey, serializedData);
|
||||
transaction.zadd(setKey, expiry, serverName);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeServerStatus(MinecraftServer serverData)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
String serverName = serverData.getName();
|
||||
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||
String dataKey = concatenate(setKey, serverName);
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.del(dataKey);
|
||||
transaction.zrem(setKey, serverName);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean serverExists(String serverName)
|
||||
{
|
||||
return getServerStatus(serverName) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<DedicatedServer> getDedicatedServers()
|
||||
{
|
||||
Collection<DedicatedServer> servers = new HashSet<DedicatedServer>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String key = concatenate("serverstatus", "dedicated");
|
||||
Set<String> serverNames = jedis.smembers(key);
|
||||
HashMap<String, Response<Map<String, String>>> serverDatas = new HashMap<String, Response<Map<String, String>>>();
|
||||
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
|
||||
for (String serverName : serverNames)
|
||||
{
|
||||
String dataKey = concatenate(key, serverName);
|
||||
serverDatas.put(serverName, pipeline.hgetAll(dataKey));
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
|
||||
for (Entry<String, Response<Map<String, String>>> responseEntry : serverDatas.entrySet())
|
||||
{
|
||||
Map<String, String> data = responseEntry.getValue().get();
|
||||
|
||||
try
|
||||
{
|
||||
DedicatedServer server = new DedicatedServer(data);
|
||||
|
||||
if (server.getRegion() == getRegion())
|
||||
servers.add(server);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.out.println(responseEntry.getKey() + " Errored");
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ServerGroup> getServerGroups(Collection<MinecraftServer> serverStatuses)
|
||||
{
|
||||
Collection<ServerGroup> servers = new HashSet<ServerGroup>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String key = "servergroups";
|
||||
Set<String> names = jedis.smembers(key);
|
||||
Set<Response<Map<String, String>>> serverDatas = new HashSet<Response<Map<String, String>>>();
|
||||
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
|
||||
for (String serverName : names)
|
||||
{
|
||||
String dataKey = concatenate(key, serverName);
|
||||
serverDatas.add(pipeline.hgetAll(dataKey));
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
|
||||
for (Response<Map<String, String>> response : serverDatas)
|
||||
{
|
||||
Map<String, String> data = response.get();
|
||||
|
||||
if (data.entrySet().size() == 0)
|
||||
{
|
||||
// please no
|
||||
// System.out.println("Encountered empty map! Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ServerGroup serverGroup = new ServerGroup(data, serverStatuses);
|
||||
|
||||
if (serverGroup.getRegion() == Region.ALL || serverGroup.getRegion() == getRegion())
|
||||
servers.add(serverGroup);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
System.out.println("Error parsing ServerGroup : " + data.get("name"));
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key - the key where the sorted set of server sessions is stored
|
||||
* @return the {@link Set} of active server names stored at {@code key} for non-expired
|
||||
* servers.
|
||||
*/
|
||||
protected Set<String> getActiveNames(String key)
|
||||
{
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String min = "(" + Utility.currentTimeSeconds();
|
||||
String max = "+inf";
|
||||
names = jedis.zrangeByScore(key, min, max);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key - the key where the sorted set of server sessions is stored
|
||||
* @return the {@link Set} of dead (expired) server names stored at {@code key}.
|
||||
*/
|
||||
protected Set<String> getDeadNames(String key)
|
||||
{
|
||||
Set<String> names = new HashSet<String>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String min = "-inf";
|
||||
String max = Utility.currentTimeSeconds() + "";
|
||||
names = jedis.zrangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MinecraftServer> getDeadServers()
|
||||
{
|
||||
Set<MinecraftServer> servers = new HashSet<MinecraftServer>();
|
||||
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
String setKey = concatenate("serverstatus", "minecraft", getRegion().toString());
|
||||
String min = "-inf";
|
||||
String max = Utility.currentTimeSeconds() + "";
|
||||
|
||||
List<Response<String>> responses = new ArrayList<Response<String>>();
|
||||
for (Tuple serverName : jedis.zrangeByScoreWithScores(setKey, min, max))
|
||||
{
|
||||
String dataKey = concatenate(setKey, serverName.getElement());
|
||||
responses.add(pipeline.get(dataKey));
|
||||
}
|
||||
|
||||
pipeline.sync();
|
||||
|
||||
for (Response<String> response : responses)
|
||||
{
|
||||
String serializedData = response.get();
|
||||
MinecraftServer server = Utility.deserialize(serializedData, MinecraftServer.class);
|
||||
|
||||
if (server != null)
|
||||
servers.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
return servers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateServerGroup(ServerGroup serverGroup)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
HashMap<String, String> serializedData = serverGroup.getDataMap();
|
||||
String serverGroupName = serverGroup.getName();
|
||||
String key = "servergroups";
|
||||
String dataKey = concatenate(key, serverGroupName);
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.hmset(dataKey, serializedData);
|
||||
transaction.sadd(key, serverGroupName);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeServerGroup(ServerGroup serverGroup)
|
||||
{
|
||||
try(Jedis jedis = getResource(true))
|
||||
{
|
||||
String serverName = serverGroup.getName();
|
||||
String setKey = "servergroups";
|
||||
String dataKey = concatenate(setKey, serverName);
|
||||
|
||||
Transaction transaction = jedis.multi();
|
||||
transaction.del(dataKey);
|
||||
transaction.srem(setKey, serverName);
|
||||
transaction.exec();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerGroup getServerGroup(String serverGroup)
|
||||
{
|
||||
ServerGroup server = null;
|
||||
try(Jedis jedis = getResource(false))
|
||||
{
|
||||
String key = concatenate("servergroups", serverGroup);
|
||||
Map<String, String> data = jedis.hgetAll(key);
|
||||
|
||||
server = new ServerGroup(data, null);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/*
|
||||
* <region> = "US" or "EU"
|
||||
* serverstatus.minecraft.<region>.<name> stores the JSON encoded information of an active MinecraftServer instance.
|
||||
* serverstatus.minecraft.<region> stores a sorted set with the set of name's for MinecraftServers
|
||||
* with a value of their expiry date (in ms)
|
||||
*
|
||||
* -----------------------
|
||||
*
|
||||
* serverstatus.dedicated.<name> stores the hash containing information of an active dedicated server instance
|
||||
* serverstatus.dedicated stores the set of active dedicated server names.
|
||||
* serverstatus.dedicated uses a hash with the following keys:
|
||||
* name, publicAddress, privateAddress, region, cpu, ram
|
||||
*
|
||||
* Example commands for adding/creating a new dedicated server:
|
||||
* 1. HMSET serverstatus.dedicated.<name> name <?> publicAddress <?> privateAddress <?> region <?> cpu <?> ram <?>
|
||||
* 2. SADD serverstatus.dedicated <name>
|
||||
*
|
||||
* ------------------------
|
||||
*
|
||||
* servergroups.<name> stores the hash-set containing information for the server group type.
|
||||
* servergroups stores the set of active server group names.
|
||||
* servergroups.<name> stores a hash of the following key name/values
|
||||
* name, prefix, scriptName, ram, cpu, totalServers, joinableServers
|
||||
*
|
||||
* Example commands for adding/creating a new server group:
|
||||
*
|
||||
* 1. HMSET servergroups.<name> name <?> prefix <?> scriptName <?> ram <?> cpu <?> totalServers <?> joinableServers <?>
|
||||
* 2. SADD servergroups <name>
|
||||
*/
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package mineplex.serverdata.redis.atomic;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.redis.RedisRepository;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
public class RedisStringRepository extends RedisRepository
|
||||
{
|
||||
private final String _dataKey;
|
||||
private final int _expiration;
|
||||
|
||||
public RedisStringRepository(ConnectionData writeConn, ConnectionData readConn, Region region, String dataKey, int expiryInSeconds)
|
||||
{
|
||||
super(writeConn, readConn, region);
|
||||
this._dataKey = dataKey;
|
||||
this._expiration = expiryInSeconds;
|
||||
}
|
||||
|
||||
public RedisStringRepository(ConnectionData writeConn, ConnectionData readConn, Region region, String dataKey)
|
||||
{
|
||||
this(writeConn, readConn, region, dataKey, -1);
|
||||
}
|
||||
|
||||
public void set(String key, String value)
|
||||
{
|
||||
try (Jedis jedis = getResource(true))
|
||||
{
|
||||
if (_expiration == -1)
|
||||
{
|
||||
jedis.set(generateKey(key), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
jedis.setex(generateKey(key), _expiration, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String get(String key)
|
||||
{
|
||||
String element;
|
||||
|
||||
try (Jedis jedis = getResource(false))
|
||||
{
|
||||
element = jedis.get(generateKey(key));
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public void del(String key)
|
||||
{
|
||||
try (Jedis jedis = getResource(true))
|
||||
{
|
||||
jedis.del(generateKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
private String getElementSetKey()
|
||||
{
|
||||
return concatenate("data", _dataKey, getRegion().toString());
|
||||
}
|
||||
|
||||
private String generateKey(String dataId)
|
||||
{
|
||||
return concatenate(getElementSetKey(), dataId);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package mineplex.serverdata.redis.counter;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* A counter represents an incrementing atomic number that is stored and updated through redis. This allows for
|
||||
* multiple servers to share and update the same counter
|
||||
*
|
||||
* @author Shaun Bennett
|
||||
*/
|
||||
public class Counter
|
||||
{
|
||||
// Cached count of the counter
|
||||
private final AtomicLong _count = new AtomicLong(0);
|
||||
// The System.currentTimeMillis() when cached count was last updated
|
||||
private volatile long _lastUpdated;
|
||||
// The unique key to reference this counter
|
||||
private final String _dataKey;
|
||||
|
||||
// Redis repository to store the count
|
||||
private final CounterRedisRepository _redisRepository;
|
||||
|
||||
public Counter(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey)
|
||||
{
|
||||
_dataKey = dataKey;
|
||||
_redisRepository = new CounterRedisRepository(writeConnection, readConnection, region, dataKey);
|
||||
}
|
||||
|
||||
public Counter(String dataKey)
|
||||
{
|
||||
_dataKey = dataKey;
|
||||
_redisRepository = new CounterRedisRepository(dataKey);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a value to the counter and return the new counter value. This method is thread-safe and interacts
|
||||
* directly with the atomic value stored in redis. The value returned from redis is then returned
|
||||
*
|
||||
* addAndGet will also update the cached counter value so we don't need to make extra trips to redis
|
||||
*
|
||||
* @param amount the amount to add to the counter
|
||||
* @return the updated value of the counter from redis repository
|
||||
*/
|
||||
public long addAndGet(long amount)
|
||||
{
|
||||
long newCount = _redisRepository.incrementCount(amount);
|
||||
updateCount(newCount);
|
||||
return newCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest cached count from the counter. This value will not be changed until {@link #addAndGet(long)}
|
||||
* or {@link #updateCount} is called.
|
||||
*
|
||||
* @return The counter count
|
||||
*/
|
||||
public long getCount()
|
||||
{
|
||||
return _count.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached count to reflect the count in redis. This should be called async
|
||||
*/
|
||||
public void updateCount()
|
||||
{
|
||||
updateCount(_redisRepository.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the counter back to 0. Immediately updates the redis repository.
|
||||
*
|
||||
* @return The value of the counter before it was reset
|
||||
*/
|
||||
public long reset()
|
||||
{
|
||||
updateCount(0);
|
||||
return _redisRepository.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data key for this counter. The data key is used as the redis repository key
|
||||
* @return The data key for this counter
|
||||
*/
|
||||
public String getDataKey()
|
||||
{
|
||||
return _dataKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached count with a new value
|
||||
* @param newCount updated count
|
||||
*/
|
||||
protected void updateCount(long newCount)
|
||||
{
|
||||
_count.set(newCount);
|
||||
_lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package mineplex.serverdata.redis.counter;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.redis.RedisRepository;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
/**
|
||||
* Redis repository to store the count for {@link Counter}
|
||||
* @author Shaun Bennett
|
||||
*/
|
||||
public class CounterRedisRepository extends RedisRepository
|
||||
{
|
||||
private String _dataKey;
|
||||
|
||||
public CounterRedisRepository(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey)
|
||||
{
|
||||
super(writeConnection, readConnection, region);
|
||||
|
||||
_dataKey = dataKey;
|
||||
}
|
||||
|
||||
public CounterRedisRepository(String dataKey)
|
||||
{
|
||||
super(Region.ALL);
|
||||
|
||||
_dataKey = dataKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current count inside the fountain
|
||||
* @return The current count for the fountain
|
||||
*/
|
||||
public long getCount()
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
try (Jedis jedis = getResource(false))
|
||||
{
|
||||
count = Long.parseLong(jedis.get(getKey()));
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the current count by {@code increment} and then return the latest
|
||||
* count from redis. This is handled in an atomic process
|
||||
* @param increment Amount to increment counter by
|
||||
* @return The updated count from redis
|
||||
*/
|
||||
public long incrementCount(long increment)
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
try (Jedis jedis = getResource(true))
|
||||
{
|
||||
count = jedis.incrBy(getKey(), increment);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the counter back to 0
|
||||
* @return the value of the counter before it was reset
|
||||
*/
|
||||
public long reset()
|
||||
{
|
||||
long count = -1;
|
||||
|
||||
try (Jedis jedis = getResource(true))
|
||||
{
|
||||
count = Long.parseLong(jedis.getSet(getKey(), "0"));
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key for this counter
|
||||
* @return The key is used to store the value in redis
|
||||
*/
|
||||
private String getKey()
|
||||
{
|
||||
return getKey(_dataKey);
|
||||
}
|
||||
|
||||
// private void setNX()
|
||||
// {
|
||||
// try (Jedis jedis = getResource(true))
|
||||
// {
|
||||
// jedis.setnx(getKey(), "0");
|
||||
// }
|
||||
// }
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package mineplex.serverdata.redis.counter;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A redis counter that is aiming to reach a goal
|
||||
*
|
||||
* @author Shaun Bennett
|
||||
*/
|
||||
public class GoalCounter extends Counter
|
||||
{
|
||||
private int _lastMilestone;
|
||||
// Has the goal been completed?
|
||||
private boolean _completed;
|
||||
// The goal we are trying to reach
|
||||
private long _goal;
|
||||
|
||||
private List<GoalCounterListener> _listeners;
|
||||
|
||||
public GoalCounter(ConnectionData writeConnection, ConnectionData readConnection, Region region, String dataKey, long goal)
|
||||
{
|
||||
super(writeConnection, readConnection, region, dataKey);
|
||||
|
||||
init(goal);
|
||||
}
|
||||
|
||||
public GoalCounter(String dataKey, long goal)
|
||||
{
|
||||
super(dataKey);
|
||||
|
||||
init(goal);
|
||||
}
|
||||
|
||||
private void init(long goal)
|
||||
{
|
||||
_completed = false;
|
||||
_goal = goal;
|
||||
_listeners = new ArrayList<>();
|
||||
|
||||
updateCount();
|
||||
|
||||
_lastMilestone = (int) getFillPercent();
|
||||
|
||||
updateMilestone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progress towards the goal as a percent ranging from 0 to 1.
|
||||
* @return the percent progress towards goal
|
||||
*/
|
||||
public double getFillPercent()
|
||||
{
|
||||
return (((double) getCount()) / _goal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the goal been completed?
|
||||
* @return
|
||||
*/
|
||||
public boolean isCompleted()
|
||||
{
|
||||
return _completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the goal for this GoalCounter
|
||||
* @return the goal of this counter
|
||||
*/
|
||||
public long getGoal()
|
||||
{
|
||||
return _goal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to this GoalCounter
|
||||
* @param listener the listener to be added
|
||||
*/
|
||||
public void addListener(GoalCounterListener listener)
|
||||
{
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the listeners
|
||||
*/
|
||||
public void clearListeners()
|
||||
{
|
||||
_listeners.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update {@link #_completed} and notify listeners if it has completed
|
||||
*/
|
||||
private void updateMilestone()
|
||||
{
|
||||
int currentMilestone = (int) getFillPercent();
|
||||
|
||||
if (currentMilestone != _lastMilestone && currentMilestone > _lastMilestone)
|
||||
{
|
||||
_listeners.forEach(listener -> listener.onMilestone(this, currentMilestone));
|
||||
}
|
||||
|
||||
_lastMilestone = currentMilestone;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateCount(long newCount)
|
||||
{
|
||||
super.updateCount(newCount);
|
||||
|
||||
updateMilestone();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package mineplex.serverdata.redis.counter;
|
||||
|
||||
/**
|
||||
* @author Shaun Bennett
|
||||
*/
|
||||
public interface GoalCounterListener
|
||||
{
|
||||
public void onMilestone(GoalCounter counter, int milestone);
|
||||
}
|
@ -0,0 +1,356 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import mineplex.serverdata.Utility;
|
||||
import mineplex.serverdata.servers.ConnectionData;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPubSub;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import redis.clients.jedis.exceptions.JedisDataException;
|
||||
|
||||
/**
|
||||
* A subscription to a Redis pub/sub system through a Jedis client. Includes a publishing mechanism
|
||||
* as well.
|
||||
* <p>
|
||||
* Subscribes to Jedis and offers a variety of methods to edit the channels that this listens to
|
||||
* over time. Does not support pattern-matching, even though Jedis can. Takes a single subscriber to
|
||||
* inform of incoming messages (on all channels this is subscribed to).
|
||||
* <p>
|
||||
* For the sake of internal efficiency, this does not protect the sanity or unchangeability of
|
||||
* arguments passed into its methods. Clients should not generally interact with this directly.
|
||||
* <p>
|
||||
* The Jedis pub/sub interface is a little confusing, especially when it comes to multithreading. At
|
||||
* any given time, if this class is subscribed to any channels at all, it will have a single thread
|
||||
* that is listening for incoming messages from redis with a blocking method. After that listening
|
||||
* thread is created, we can add and remove subscriptions as desired, but the initial subscription
|
||||
* and actual listening must be done on its own thread. When all channels are unsubscribed from, the
|
||||
* listening thread returns. Note that this is stated with about 70% certainty, as the internals of
|
||||
* the pub/sub mechanism are not entirely clear to me.
|
||||
* <p>
|
||||
* This class maintains a constant connection to its redis server by subscribing to a base channel.
|
||||
* This makes it much easier to protect its operation from potentially insane client commands.
|
||||
* <p>
|
||||
* If the connection to the given redis instance fails or is interrupted, will keep attempting to
|
||||
* reconnect periodically until destroyed. Publishers and subscribers are not informed of failure in
|
||||
* any way.
|
||||
* <p>
|
||||
* When {@link #unsubscribe()} or {@link #destroy()} is called, this class ceases operation.
|
||||
*/
|
||||
public class PubSubJedisClient extends JedisPubSub implements PubSubLibraryClient
|
||||
{
|
||||
|
||||
private static final long RECONNECT_PERIOD_MILLIS = 800;
|
||||
private static final String BASE_CHANNEL = "pG8n5jp#";
|
||||
|
||||
private static final String BOLD = "\u001B[1m";
|
||||
private static final String RESET = "\u001B[0m";
|
||||
|
||||
private final String _id;
|
||||
private JedisPool _writePool;
|
||||
private final ConnectionData _readConn;
|
||||
private JedisPool _readPool;
|
||||
private final ExecutorService _threadPool = Executors.newCachedThreadPool();
|
||||
private volatile Subscriber _sub;
|
||||
|
||||
private final Set<String> _channels = Collections
|
||||
.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||
private final Map<String, SettableFuture<Boolean>> _pendingFutures = new ConcurrentHashMap<String, SettableFuture<Boolean>>();
|
||||
|
||||
private volatile boolean _subscribed; // Is there a base subscription yet?
|
||||
private volatile boolean _destroyed; // has this been deliberately destroyed?
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param writeTo The connection info for the redis instance this client should publish to.
|
||||
* @param readFrom The connection info for the redis instance this client to subscribe to.
|
||||
*/
|
||||
public PubSubJedisClient(ConnectionData writeTo, ConnectionData readFrom)
|
||||
{
|
||||
if (writeTo == null)
|
||||
{
|
||||
throw new IllegalArgumentException("redis connection info cannot be null");
|
||||
}
|
||||
|
||||
_id = writeTo.getName();
|
||||
_writePool = Utility.generatePool(writeTo);
|
||||
|
||||
_readConn = readFrom;
|
||||
_readPool = Utility.generatePool(readFrom);
|
||||
|
||||
createSubscription(BASE_CHANNEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final synchronized void setSubscriber(Subscriber sub)
|
||||
{
|
||||
_sub = sub;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> addChannel(String channel)
|
||||
{
|
||||
SettableFuture<Boolean> ret = _pendingFutures.get(channel);
|
||||
if (ret == null)
|
||||
{
|
||||
ret = SettableFuture.create();
|
||||
_pendingFutures.put(channel, ret);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_channels.add(channel);
|
||||
|
||||
if (_subscribed)
|
||||
{ // Already has a subscription thread and can just add a new channel to it.
|
||||
subscribe(channel);
|
||||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
log("Encountered issue subscribing to a channel.");
|
||||
ex.printStackTrace();
|
||||
ret.setException(ex);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void removeChannel(String channel)
|
||||
{
|
||||
if (BASE_CHANNEL.equals(channel))
|
||||
{ // Protects the base subscription
|
||||
return;
|
||||
}
|
||||
|
||||
_channels.remove(channel);
|
||||
|
||||
if (_subscribed)
|
||||
{
|
||||
unsubscribe(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void unsubscribe()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void destroy()
|
||||
{
|
||||
_destroyed = true;
|
||||
try
|
||||
{
|
||||
super.unsubscribe();
|
||||
} catch (NullPointerException e)
|
||||
{
|
||||
}
|
||||
|
||||
for (SettableFuture<Boolean> fut : _pendingFutures.values())
|
||||
{
|
||||
fut.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMessage(String channel, String message)
|
||||
{
|
||||
if (_sub == null || BASE_CHANNEL.equals(channel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_sub.onMessage(channel, message);
|
||||
} catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> publish(final String channel, final String message)
|
||||
{
|
||||
final SettableFuture<Boolean> ret = SettableFuture.create();
|
||||
_threadPool.execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Jedis bJedis = null;
|
||||
try
|
||||
{
|
||||
bJedis = _writePool.getResource();
|
||||
bJedis.publish(channel, message);
|
||||
_writePool.returnResource((Jedis) bJedis);
|
||||
ret.set(true);
|
||||
|
||||
} catch (Exception e)
|
||||
{
|
||||
log("Encountered issue while publishing a message.");
|
||||
e.printStackTrace();
|
||||
if (bJedis != null)
|
||||
{
|
||||
_writePool.returnBrokenResource((Jedis) bJedis);
|
||||
}
|
||||
ret.set(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Confirms successful subscriptions/unsubscriptions.
|
||||
@Override
|
||||
public void onSubscribe(String channel, int subscribedChannels)
|
||||
{
|
||||
|
||||
// informs subscriber that this subscription completed successfully
|
||||
SettableFuture<Boolean> fut = _pendingFutures.remove(channel);
|
||||
if (fut != null)
|
||||
{
|
||||
fut.set(true);
|
||||
}
|
||||
|
||||
if (!_subscribed)
|
||||
{
|
||||
for (String subscribeTo : _channels)
|
||||
{
|
||||
subscribe(subscribeTo);
|
||||
}
|
||||
}
|
||||
_subscribed = true;
|
||||
|
||||
log("Subscribed to channel: " + channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnsubscribe(String channel, int subscribedChannels)
|
||||
{
|
||||
log("Unsubscribed from channel: " + channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the initial listening thread which blocks as it polls redis for new messages.
|
||||
* Subsequent subscriptions can simply be added using {@link #subscribe(String...)} after the
|
||||
* subscription thread has been created.
|
||||
*
|
||||
* @param firstChannel The first channel to initially subscribe to. If you do not have a first
|
||||
* channel, there is no reason to create a subscriber thread yet.
|
||||
*/
|
||||
private void createSubscription(final String firstChannel)
|
||||
{
|
||||
|
||||
final JedisPubSub pubsub = this;
|
||||
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
||||
boolean first = true;
|
||||
|
||||
while (!_destroyed)
|
||||
{
|
||||
|
||||
if (!first)
|
||||
{
|
||||
log("Jedis connection to " + _readConn.getHost() + ":"
|
||||
+ _readConn.getPort()
|
||||
+ " failed or was interrupted, attempting to reconnect");
|
||||
}
|
||||
first = false;
|
||||
|
||||
Jedis jedisInstance = null;
|
||||
|
||||
try
|
||||
{
|
||||
// gets a non-thread-safe jedis instance from the thread-safe pool.
|
||||
jedisInstance = _readPool.getResource();
|
||||
|
||||
log("Creating initial jedis subscription to channel " + firstChannel);
|
||||
// this will block as long as there are subscriptions
|
||||
jedisInstance.subscribe(pubsub, firstChannel);
|
||||
|
||||
log("jedisInstance.subscribe() returned, subscription over.");
|
||||
|
||||
// when the subscription ends (subscribe() returns), returns the instance to
|
||||
// the pool
|
||||
_readPool.returnResource(jedisInstance);
|
||||
|
||||
} catch (JedisConnectionException e)
|
||||
{
|
||||
log("Jedis connection encountered an issue.");
|
||||
e.printStackTrace();
|
||||
if (jedisInstance != null)
|
||||
{
|
||||
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||
}
|
||||
|
||||
} catch (JedisDataException e)
|
||||
{
|
||||
log("Jedis connection encountered an issue.");
|
||||
e.printStackTrace();
|
||||
if (jedisInstance != null)
|
||||
{
|
||||
_readPool.returnBrokenResource((Jedis) jedisInstance);
|
||||
}
|
||||
}
|
||||
_subscribed = false;
|
||||
|
||||
// sleeps for a short pause, rather than constantly retrying connection
|
||||
if (!_destroyed)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(RECONNECT_PERIOD_MILLIS);
|
||||
} catch (InterruptedException e)
|
||||
{
|
||||
_destroyed = true;
|
||||
log("Reconnection pause thread was interrupted.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// This implementation does not support pattern-matching subscriptions
|
||||
@Override
|
||||
public void onPMessage(String pattern, String channel, String message)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPSubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPUnsubscribe(String pattern, int subscribedChannels)
|
||||
{
|
||||
}
|
||||
|
||||
private void log(String msg)
|
||||
{
|
||||
System.out.println(BOLD + "[" + getClass().getSimpleName()
|
||||
+ (_id != null && !_id.isEmpty() ? " " + _id : "") + "] " + RESET + msg);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* A multi-channel subscription and publisher to a pub/sub messaging implementation. An interface to
|
||||
* the actual low-level pub/sub library, whatever it may be.
|
||||
*
|
||||
* For the sake of internal efficiency, this makes no guarantees for the sanity or unchangeability
|
||||
* of arguments passed into its methods. Clients should not generally interact with this directly.
|
||||
*/
|
||||
public interface PubSubLibraryClient
|
||||
{
|
||||
|
||||
/**
|
||||
* Publishes a message to all subscribers of a given channel.
|
||||
*
|
||||
* @param channel The channel to publish the message on.
|
||||
* @param message The message to send.
|
||||
* @return A future object that will complete after an unknown amount of time with
|
||||
* <code>false</code> if for some locally known reason the message definitely could not
|
||||
* be published, else completes with <code>true</code>.
|
||||
*/
|
||||
ListenableFuture<Boolean> publish(String channel, String message);
|
||||
|
||||
/**
|
||||
* Adds a channel to this subscription.
|
||||
*
|
||||
* @param channel The channel to add. Should not change after being passed in.
|
||||
* @return The asynchronous, future result of this attempt to add the channel. Will have
|
||||
* <code>true</code> when the subscription starts successfully.
|
||||
*/
|
||||
ListenableFuture<Boolean> addChannel(String channel);
|
||||
|
||||
/**
|
||||
* Removes a channel from this subscription.
|
||||
*
|
||||
* @param channel The channel to remove. Should not change after being passed in.
|
||||
*/
|
||||
void removeChannel(String channel);
|
||||
|
||||
/**
|
||||
* Removes all channels from this subscription, kills its connections, and relinquishes any
|
||||
* resources it was occupying.
|
||||
* <p>
|
||||
* Depending on the implementation, once a subscription has been destroyed, it may not be
|
||||
* reusable and it may be necessary to construct a new one in order to resume.
|
||||
* <p>
|
||||
* Call this when the subscription is no longer being used. Holding unnecessary connections can
|
||||
* cause serious performance and other issues on both ends.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Sets the subscriber to inform of messages received by this subscription.
|
||||
*
|
||||
* @param sub The listener for this subscription.
|
||||
*/
|
||||
void setSubscriber(Subscriber sub);
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* Messager for standard pub/sub model. Handles multiple publishers and subscribers.
|
||||
* <p>
|
||||
* All messaging is asynchronous and non-blocking, even to local subscribers.
|
||||
* <p>
|
||||
* For more about the pub/sub messaging paradigm, see <a
|
||||
* href="http://en.wikipedia.org/wiki/Publish%E2
|
||||
* %80%93subscribe_pattern">http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern</a>
|
||||
*/
|
||||
public interface PubSubMessager
|
||||
{
|
||||
|
||||
/**
|
||||
* Publishes a message to all subscribers of a given channel.
|
||||
* <p>
|
||||
* Publishes to all connected subscribers, including local ones.
|
||||
*
|
||||
* @param channel The channel to publish the message on.
|
||||
* @param message The message to send.
|
||||
* @return A future object that will complete after an unknown amount of time with
|
||||
* <code>false</code> if for some locally known reason the message definitely could not
|
||||
* be published, else completes with <code>true</code> (which does not guarantee it
|
||||
* succeeded 100%).
|
||||
*/
|
||||
ListenableFuture<Boolean> publish(String channel, String message);
|
||||
|
||||
/**
|
||||
* Subscribes to a messaging channel.
|
||||
* <p>
|
||||
* When incoming messages arrive, the subscriber is called from an arbitrary new thread.
|
||||
*
|
||||
* @param channel The channel to subscribe to.
|
||||
* @param sub The subscriber to inform of incoming messages.
|
||||
* @return The asynchronous, future result of this attempt to subscribe to the channel. Will
|
||||
* have <code>true</code> when the subscription starts successfully.
|
||||
*/
|
||||
ListenableFuture<Boolean> subscribe(String channel, Subscriber sub);
|
||||
|
||||
/**
|
||||
* Unsubscribes from a messaging channel.
|
||||
*
|
||||
* @param channel The channel to unsubscribe from.
|
||||
* @param sub The subscriber to stop informing of incoming messages.
|
||||
*/
|
||||
void unsubscribe(String channel, Subscriber sub);
|
||||
|
||||
/**
|
||||
* Attempts to gracefully shut down this messager. Generally irreversible.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
/**
|
||||
* A pub/sub messager that simply routes messages to some underlying pub/sub implementation, which
|
||||
* is in turn represented by a multi-channel subscription and a publishing mechanism.
|
||||
* <p>
|
||||
* This class handles:
|
||||
* <ol>
|
||||
* <li>Providing a modular messaging interface that is thread-safe.
|
||||
* <li>Protecting pub/sub implementations from some bad client behavior/data.
|
||||
* <li>Routing messages for multiple subscribers to the same channel(s).
|
||||
* </ol>
|
||||
*/
|
||||
public class PubSubRouter implements PubSubMessager, Subscriber
|
||||
{
|
||||
|
||||
private final PubSubLibraryClient _pubSubClient;
|
||||
private final Map<String, Set<Subscriber>> _subscribers;
|
||||
|
||||
private final ExecutorService _threadPool;
|
||||
|
||||
public PubSubRouter(PubSubLibraryClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new IllegalArgumentException("pubsub client cannot be null");
|
||||
}
|
||||
|
||||
this._pubSubClient = client;
|
||||
this._pubSubClient.setSubscriber(this);
|
||||
this._subscribers = new ConcurrentHashMap<String, Set<Subscriber>>();
|
||||
|
||||
this._threadPool = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> publish(String channel, String message)
|
||||
{
|
||||
if (channel == null || channel.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("channel cannot be null or empty");
|
||||
}
|
||||
|
||||
// messages of 0 length are allowed. Null messages are treated as messages of 0 length.
|
||||
if (message == null)
|
||||
{
|
||||
message = "";
|
||||
}
|
||||
|
||||
return _pubSubClient.publish(channel, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<Boolean> subscribe(String channel, Subscriber sub)
|
||||
{
|
||||
if (channel == null || channel.isEmpty() || sub == null)
|
||||
{
|
||||
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||
}
|
||||
|
||||
ListenableFuture<Boolean> ret = null;
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
if (channelSubs == null)
|
||||
{
|
||||
// uses CopyOnWriteArraySet for fast and consistent iteration (forwarding messages to
|
||||
// subscribers) but slow writes (adding/removing subscribers).
|
||||
// See a discussion of the issue here:
|
||||
// http://stackoverflow.com/questions/6720396/different-types-of-thread-safe-sets-in-java
|
||||
channelSubs = new CopyOnWriteArraySet<Subscriber>();
|
||||
_subscribers.put(channel, channelSubs);
|
||||
|
||||
// starts a jedis subscription to the channel if there were no subscribers before
|
||||
ret = _pubSubClient.addChannel(channel);
|
||||
|
||||
} else
|
||||
{
|
||||
ret = SettableFuture.create();
|
||||
((SettableFuture<Boolean>) ret).set(true); // already subscribed, calls back immediately
|
||||
}
|
||||
|
||||
channelSubs.add(sub);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void unsubscribe(String channel, Subscriber sub)
|
||||
{
|
||||
if (channel == null || channel.isEmpty() || sub == null)
|
||||
{
|
||||
throw new IllegalArgumentException("params cannot be null and channel cannot be empty");
|
||||
}
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
if (channelSubs == null)
|
||||
{ // no subscribers for the channel to begin with.
|
||||
return;
|
||||
}
|
||||
|
||||
channelSubs.remove(sub);
|
||||
|
||||
// stops the subscription to this channel if the unsubscribed was the last subscriber
|
||||
if (channelSubs.isEmpty())
|
||||
{
|
||||
_subscribers.remove(channel);
|
||||
_pubSubClient.removeChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMessage(final String channel, final String message)
|
||||
{
|
||||
if (channel == null || message == null || channel.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Subscriber> channelSubs = _subscribers.get(channel);
|
||||
|
||||
if (channelSubs == null)
|
||||
{ // We should not still be listening
|
||||
_pubSubClient.removeChannel(channel);
|
||||
return;
|
||||
} else if (channelSubs.isEmpty())
|
||||
{
|
||||
_subscribers.remove(channel);
|
||||
_pubSubClient.removeChannel(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Subscriber sub : channelSubs)
|
||||
{
|
||||
|
||||
// Gives subscribers their own thread from the thread pool in which to react to the
|
||||
// message.
|
||||
// Avoids interruptions and other problems while iterating over the subscriber set.
|
||||
_threadPool.execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
sub.onMessage(channel, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown()
|
||||
{
|
||||
_pubSubClient.destroy();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package mineplex.serverdata.redis.messaging;
|
||||
|
||||
/**
|
||||
* A subscriber to a pub/sub channel.
|
||||
*/
|
||||
public interface Subscriber
|
||||
{
|
||||
|
||||
/**
|
||||
* Called when a message is sent on a channel that this is subscribed to.
|
||||
* <p>
|
||||
* No guarantees are made about what thread this will be called from.
|
||||
*
|
||||
* @param channel The channel that the message was sent on.
|
||||
* @param message The message that was received.
|
||||
*/
|
||||
void onMessage(String channel, String message);
|
||||
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package mineplex.serverdata.serialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.Streams;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
|
||||
/**
|
||||
* Adapts values whose runtime type may differ from their declaration type. This
|
||||
* is necessary when a field's type is not the same type that GSON should create
|
||||
* when deserializing that field. For example, consider these types:
|
||||
* <pre> {@code
|
||||
* abstract class Shape {
|
||||
* int x;
|
||||
* int y;
|
||||
* }
|
||||
* class Circle extends Shape {
|
||||
* int radius;
|
||||
* }
|
||||
* class Rectangle extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Diamond extends Shape {
|
||||
* int width;
|
||||
* int height;
|
||||
* }
|
||||
* class Drawing {
|
||||
* Shape bottomShape;
|
||||
* Shape topShape;
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>Without additional type information, the serialized JSON is ambiguous. Is
|
||||
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}</pre>
|
||||
* This class addresses this problem by adding type information to the
|
||||
* serialized JSON and honoring that type information when the JSON is
|
||||
* deserialized: <pre> {@code
|
||||
* {
|
||||
* "bottomShape": {
|
||||
* "type": "Diamond",
|
||||
* "width": 10,
|
||||
* "height": 5,
|
||||
* "x": 0,
|
||||
* "y": 0
|
||||
* },
|
||||
* "topShape": {
|
||||
* "type": "Circle",
|
||||
* "radius": 2,
|
||||
* "x": 4,
|
||||
* "y": 1
|
||||
* }
|
||||
* }}</pre>
|
||||
* Both the type field name ({@code "type"}) and the type labels ({@code
|
||||
* "Rectangle"}) are configurable.
|
||||
*
|
||||
* <h3>Registering Types</h3>
|
||||
* Create a {@code RuntimeTypeAdapter} by passing the base type and type field
|
||||
* name to the {@link #of} factory method. If you don't supply an explicit type
|
||||
* field name, {@code "type"} will be used. <pre> {@code
|
||||
* RuntimeTypeAdapter<Shape> shapeAdapter
|
||||
* = RuntimeTypeAdapter.of(Shape.class, "type");
|
||||
* }</pre>
|
||||
* Next register all of your subtypes. Every subtype must be explicitly
|
||||
* registered. This protects your application from injection attacks. If you
|
||||
* don't supply an explicit type label, the type's simple name will be used.
|
||||
* <pre> {@code
|
||||
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
|
||||
* shapeAdapter.registerSubtype(Circle.class, "Circle");
|
||||
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
|
||||
* }</pre>
|
||||
* Finally, register the type adapter in your application's GSON builder:
|
||||
* <pre> {@code
|
||||
* Gson gson = new GsonBuilder()
|
||||
* .registerTypeAdapter(Shape.class, shapeAdapter)
|
||||
* .create();
|
||||
* }</pre>
|
||||
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
|
||||
* RuntimeTypeAdapter<Shape> shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class)
|
||||
* .registerSubtype(Rectangle.class)
|
||||
* .registerSubtype(Circle.class)
|
||||
* .registerSubtype(Diamond.class);
|
||||
* }</pre>
|
||||
*/
|
||||
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
private final Class<?> baseType;
|
||||
private final String typeFieldName;
|
||||
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
|
||||
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
|
||||
|
||||
private RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
|
||||
if (typeFieldName == null || baseType == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.baseType = baseType;
|
||||
this.typeFieldName = typeFieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter using for {@code baseType} using {@code
|
||||
* typeFieldName} as the type field name. Type field names are case sensitive.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
|
||||
* the type field name.
|
||||
*/
|
||||
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
|
||||
return new RuntimeTypeAdapterFactory<T>(baseType, "type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by {@code label}. Labels are case
|
||||
* sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or {@code label}
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
|
||||
if (type == null || label == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
|
||||
throw new IllegalArgumentException("types and labels must be unique");
|
||||
}
|
||||
labelToSubtype.put(label, type);
|
||||
subtypeToLabel.put(type, label);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers {@code type} identified by its {@link Class#getSimpleName simple
|
||||
* name}. Labels are case sensitive.
|
||||
*
|
||||
* @throws IllegalArgumentException if either {@code type} or its simple name
|
||||
* have already been registered on this type adapter.
|
||||
*/
|
||||
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
|
||||
return registerSubtype(type, type.getSimpleName());
|
||||
}
|
||||
|
||||
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
|
||||
if (type.getRawType() != baseType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Map<String, TypeAdapter<?>> labelToDelegate
|
||||
= new LinkedHashMap<String, TypeAdapter<?>>();
|
||||
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
|
||||
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
|
||||
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
|
||||
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
|
||||
labelToDelegate.put(entry.getKey(), delegate);
|
||||
subtypeToDelegate.put(entry.getValue(), delegate);
|
||||
}
|
||||
|
||||
return new TypeAdapter<R>() {
|
||||
@Override public R read(JsonReader in) throws IOException {
|
||||
JsonElement jsonElement = Streams.parse(in);
|
||||
JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName);
|
||||
if (labelJsonElement == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType
|
||||
+ " because it does not define a field named " + typeFieldName);
|
||||
}
|
||||
String label = labelJsonElement.getAsString();
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
|
||||
+ label + "; did you forget to register a subtype?");
|
||||
}
|
||||
return delegate.fromJsonTree(jsonElement);
|
||||
}
|
||||
|
||||
@Override public void write(JsonWriter out, R value) throws IOException {
|
||||
if(value!=null) {
|
||||
Class<?> srcType = value.getClass();
|
||||
String label = subtypeToLabel.get(srcType);
|
||||
@SuppressWarnings("unchecked") // registration requires that subtype extends T
|
||||
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
|
||||
if (delegate == null) {
|
||||
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||
+ "; did you forget to register a subtype?");
|
||||
}
|
||||
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
|
||||
if (jsonObject.has(typeFieldName)) {
|
||||
throw new JsonParseException("cannot serialize " + srcType.getName()
|
||||
+ " because it already defines a field named " + typeFieldName);
|
||||
}
|
||||
JsonObject clone = new JsonObject();
|
||||
clone.add(typeFieldName, new JsonPrimitive(label));
|
||||
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
|
||||
clone.add(e.getKey(), e.getValue());
|
||||
}
|
||||
Streams.write(clone, out);
|
||||
}else{
|
||||
out.nullValue();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package mineplex.serverdata.servers;
|
||||
|
||||
/**
|
||||
* ConnectionData stores information relevant for initiating a connection to a repository.
|
||||
* @author MrTwiggy
|
||||
*
|
||||
*/
|
||||
public class ConnectionData
|
||||
{
|
||||
|
||||
public enum ConnectionType
|
||||
{
|
||||
MASTER,
|
||||
SLAVE;
|
||||
}
|
||||
|
||||
private ConnectionType _type; // The type of connection available
|
||||
public ConnectionType getType() { return _type; }
|
||||
|
||||
private String _name; // The name associated with this connection
|
||||
public String getName() { return _name; }
|
||||
|
||||
private String _host; // The host URL to connect to repository
|
||||
public String getHost() { return _host; }
|
||||
|
||||
private int _port; // The port to connect to repository
|
||||
public int getPort() { return _port; }
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param host - the host URL defining the repository
|
||||
* @param port - the port used for connection to repository
|
||||
* @param type - the type of connection referenced by this ConnectionData
|
||||
* @param name - the name associated with ConnectionData
|
||||
*/
|
||||
public ConnectionData(String host, int port, ConnectionType type, String name)
|
||||
{
|
||||
_host = host;
|
||||
_port = port;
|
||||
_type = type;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return true, if {@code name} is null or it matches (case-insensitive) the {@code _name} associated
|
||||
* with this ConnectionData, false otherwise.
|
||||
*/
|
||||
public boolean nameMatches(String name)
|
||||
{
|
||||
return (name == null || name.equalsIgnoreCase(_name));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
package mineplex.serverdata.servers;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import mineplex.serverdata.data.DedicatedServer;
|
||||
|
||||
public class DedicatedServerSorter implements Comparator<DedicatedServer>
|
||||
{
|
||||
@Override
|
||||
public int compare(DedicatedServer first, DedicatedServer second)
|
||||
{
|
||||
if (second.getAvailableRam() <= 1024) return -1;
|
||||
else if (first.getAvailableRam() <= 1024) return 1;
|
||||
else if (first.getAvailableRam() > second.getAvailableRam()) return -1;
|
||||
else if (second.getAvailableRam() > first.getAvailableRam()) return 1;
|
||||
else if (first.getAvailableCpu() > second.getAvailableCpu()) return -1;
|
||||
else if (second.getAvailableCpu() > first.getAvailableCpu()) return 1;
|
||||
else return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
package mineplex.serverdata.servers;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import mineplex.serverdata.Region;
|
||||
import mineplex.serverdata.redis.RedisConfig;
|
||||
import mineplex.serverdata.redis.RedisServerRepository;
|
||||
import mineplex.serverdata.servers.ConnectionData.ConnectionType;
|
||||
|
||||
/**
|
||||
* ServerManager handles the creation/management of {@link ServerRepository}s for use.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public class ServerManager
|
||||
{
|
||||
public static final String SERVER_STATUS_LABEL = "ServerStatus"; // Label differentiating ServerStatus related servers
|
||||
private static final String DEFAULT_CONFIG = "redis-config.dat";
|
||||
|
||||
// Configuration determining connection information
|
||||
private static RedisConfig _config;
|
||||
|
||||
// The cached repository instances
|
||||
private static Map<Region, ServerRepository> repositories = new HashMap<Region, ServerRepository>();
|
||||
|
||||
/**
|
||||
* @param host - the host url used to connect to the database
|
||||
* @param port - the port to connect to the repository
|
||||
* @param region - the geographical region of the {@link ServerRepository}.
|
||||
* @return a newly instanced (or cached) {@link ServerRepository} for the specified {@code region}.
|
||||
*/
|
||||
private static ServerRepository getServerRepository(ConnectionData writeConn, ConnectionData readConn, Region region)
|
||||
{
|
||||
if (repositories.containsKey(region)) return repositories.get(region);
|
||||
|
||||
ServerRepository repository = new RedisServerRepository(writeConn, readConn, region);
|
||||
repositories.put(region, repository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code host} defaults to {@value DEFAULT_REDIS_HOST} and
|
||||
* {@code port} defaults to {@value DEFAULT_REDIS_PORT}.
|
||||
*
|
||||
* @see #getServerRepository(String, int, Region)
|
||||
*/
|
||||
public static ServerRepository getServerRepository(Region region)
|
||||
{
|
||||
return getServerRepository(getConnection(true, SERVER_STATUS_LABEL), getConnection(false, SERVER_STATUS_LABEL), region);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ConnectionData} associated with the master instance connection.
|
||||
*/
|
||||
public static ConnectionData getMasterConnection()
|
||||
{
|
||||
return getConnection(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-Deterministic: Generates random slave instance connection.
|
||||
* @return the {@link ConnectionData} associated with a random slave connection.
|
||||
*/
|
||||
public static ConnectionData getSlaveConnection()
|
||||
{
|
||||
return getConnection(false);
|
||||
}
|
||||
|
||||
public static ConnectionData getConnection(boolean writeable, String name)
|
||||
{
|
||||
return getDefaultConfig().getConnection(writeable, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeable - whether the connection referenced in return can receive write-requests
|
||||
* @return a newly generated {@code ConnectionData} pointing to a valid connection.
|
||||
*/
|
||||
public static ConnectionData getConnection(boolean writeable)
|
||||
{
|
||||
return getConnection(writeable, "DefaultConnection");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the default {@link RedisConfig} associated with this manager, providing appropriate connections.
|
||||
*/
|
||||
public static RedisConfig getDefaultConfig()
|
||||
{
|
||||
return getConfig(DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link RedisConfig} associated with this manager, providing appropriate connections.
|
||||
*/
|
||||
public static RedisConfig getConfig(String fileName)
|
||||
{
|
||||
if (_config == null)
|
||||
_config = loadConfig(fileName);
|
||||
|
||||
return _config;
|
||||
}
|
||||
|
||||
public static RedisConfig loadConfig(String fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
File configFile = new File(fileName);
|
||||
|
||||
if (configFile.exists())
|
||||
{
|
||||
List<ConnectionData> connections = new ArrayList<ConnectionData>();
|
||||
List<String> lines = Files.readAllLines(configFile.toPath(), Charset.defaultCharset());
|
||||
|
||||
for (String line : lines)
|
||||
{
|
||||
ConnectionData connection = deserializeConnection(line);
|
||||
connections.add(connection);
|
||||
|
||||
}
|
||||
|
||||
return new RedisConfig(connections);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(fileName + " not found at " + configFile.toPath().toString());
|
||||
return new RedisConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
log("---Unable To Parse Redis Configuration File---");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line - the serialized line representing a valid {@link ConnectionData} object.
|
||||
* @return a deserialized {@link ConnectionData} referenced by the {@code line} passed in.
|
||||
*/
|
||||
private static ConnectionData deserializeConnection(String line)
|
||||
{
|
||||
String[] args = line.split(" ");
|
||||
|
||||
if (args.length >= 2)
|
||||
{
|
||||
String ip = args[0];
|
||||
int port = Integer.parseInt(args[1]);
|
||||
String typeName = (args.length >= 3) ? args[2].toUpperCase() : "MASTER"; // Defaults to MASTER if omitted.
|
||||
ConnectionType type = ConnectionType.valueOf(typeName);
|
||||
String name = (args.length >= 4) ? args[3] : "DefaultConnection"; // Defaults to DefaultConnection if omitted.
|
||||
|
||||
return new ConnectionData(ip, port, type, name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void log(String message)
|
||||
{
|
||||
System.out.println(String.format("[ServerManager] %s", message));
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package mineplex.serverdata.servers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import mineplex.serverdata.data.DedicatedServer;
|
||||
import mineplex.serverdata.data.MinecraftServer;
|
||||
import mineplex.serverdata.data.ServerGroup;
|
||||
|
||||
/**
|
||||
* The ServerRepository is used for storing/retrieving active sessions
|
||||
* for {@link MinecraftServer}s, {@link DedicatedServer}s, and {@link ServerGroup}s
|
||||
* from a persistent database/repoistory.
|
||||
* @author Ty
|
||||
*
|
||||
*/
|
||||
public interface ServerRepository
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @return a newly instanced snapshot {@link Collection} of all currently active
|
||||
* {@link MinecraftServer}s in the repository.
|
||||
*/
|
||||
public Collection<MinecraftServer> getServerStatuses();
|
||||
|
||||
public Collection<MinecraftServer> getServerStatusesByPrefix(String prefix);
|
||||
|
||||
public Collection<MinecraftServer> getServersByGroup(String serverGroup);
|
||||
|
||||
/**
|
||||
* @param serverName - the name of the {@link MinecraftServer} to be fetched.
|
||||
* @return the currently active {@link MinecraftServer} with a matching {@code serverName},
|
||||
* if an active one exists, null otherwise.
|
||||
*/
|
||||
public MinecraftServer getServerStatus(String serverName);
|
||||
|
||||
/**
|
||||
* Update (or add, if it doesn't already exist) a {@link MinecraftServer}s data
|
||||
* in the repository.
|
||||
*
|
||||
* A {@link MinecraftServer} must be updated within {@code timeout} milliseconds before
|
||||
* it expires and is removed from the repository.
|
||||
* @param serverData - the {@link MinecraftServer} to add/update in the repository.
|
||||
* @param timeout - the timeout (in milliseconds) before the {@link MinecraftServer} session expires.
|
||||
*/
|
||||
public void updataServerStatus(MinecraftServer serverData, int timeout);
|
||||
|
||||
/**
|
||||
* Remove an active {@link MinecraftServer} from the repository.
|
||||
* @param serverData - the {@link MinecraftServer} to be removed.
|
||||
*/
|
||||
public void removeServerStatus(MinecraftServer serverData);
|
||||
|
||||
/**
|
||||
* @param serverName - the name of the server whose existence is being checked.
|
||||
* @return true, if there exists an active {@link MinecraftServer} session with a
|
||||
* matching {@code serverName}, false otherwise.
|
||||
*/
|
||||
public boolean serverExists(String serverName);
|
||||
|
||||
/**
|
||||
* @return a newly instanced snapshot {@link Collection} of all the
|
||||
* currently active {@link DedicatedServer}s in the repository.
|
||||
*/
|
||||
public Collection<DedicatedServer> getDedicatedServers();
|
||||
|
||||
/**
|
||||
* @return a newly instanced snapshot {@link Collection} of all the
|
||||
* currently active {@link ServerGroup}s in the repository.
|
||||
*/
|
||||
public Collection<ServerGroup> getServerGroups(Collection<MinecraftServer> servers);
|
||||
|
||||
public ServerGroup getServerGroup(String serverGroup);
|
||||
|
||||
public Collection<MinecraftServer> getDeadServers();
|
||||
|
||||
void updateServerGroup(ServerGroup serverGroup);
|
||||
|
||||
public void removeServerGroup(ServerGroup serverGroup);
|
||||
}
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user