1
0

final update

This commit is contained in:
Labality
2021-08-19 16:35:32 +07:00
parent c2e012237f
commit f4fa1d8085
11877 changed files with 1192560 additions and 0 deletions

Binary file not shown.

3
Bungee & Mineplexer/Mineplexer/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -0,0 +1,4 @@
name: Mineplexer
main: mineplex.bungee.Mineplexer
version: 1
author: defek7

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}
*/
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}
}
}

View File

@ -0,0 +1,7 @@
package mineplex.bungee.playerStats.data;
public class IpInfo
{
public int id;
public String ipAddress;
}

View File

@ -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()));
}
}
}

View File

@ -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."));
}
}
}

View File

@ -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;
}
}

View File

@ -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>

View 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();
}
}

View 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();
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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()
{
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
package mineplex.serverdata.commands;
public interface CommandCallback<T extends ServerCommand>
{
void run(T command);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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()
{
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)
{
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
*/
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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>
*/
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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");
// }
// }
}

View File

@ -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();
}
}

View File

@ -0,0 +1,9 @@
package mineplex.serverdata.redis.counter;
/**
* @author Shaun Bennett
*/
public interface GoalCounterListener
{
public void onMilestone(GoalCounter counter, int milestone);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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();
}
}
};
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More