Plan/Plan/src/main/java/com/djrapitops/plan/data/cache/DataCacheHandler.java

491 lines
16 KiB
Java

package main.java.com.djrapitops.plan.data.cache;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import main.java.com.djrapitops.plan.Log;
import main.java.com.djrapitops.plan.Phrase;
import main.java.com.djrapitops.plan.Plan;
import main.java.com.djrapitops.plan.Settings;
import main.java.com.djrapitops.plan.data.*;
import main.java.com.djrapitops.plan.data.cache.queue.DataCacheClearQueue;
import main.java.com.djrapitops.plan.data.cache.queue.DataCacheGetQueue;
import main.java.com.djrapitops.plan.data.cache.queue.DataCacheProcessQueue;
import main.java.com.djrapitops.plan.data.cache.queue.DataCacheSaveQueue;
import main.java.com.djrapitops.plan.data.handling.info.HandlingInfo;
import main.java.com.djrapitops.plan.data.handling.info.LogoutInfo;
import main.java.com.djrapitops.plan.data.handling.info.ReloadInfo;
import main.java.com.djrapitops.plan.database.Database;
import main.java.com.djrapitops.plan.utilities.MiscUtils;
import main.java.com.djrapitops.plan.utilities.NewPlayerCreator;
import main.java.com.djrapitops.plan.utilities.comparators.HandlingInfoTimeComparator;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import static org.bukkit.Bukkit.getOfflinePlayer;
/**
* This Class contains the Cache.
*
* This class is the main processing class that initializes Save, Clear, Process
* and Get queue and Starts the asyncronous save task.
*
* It is used to store commanduse, locations, active sessions and UserData objects
* in memory.
*
* It's methods can be used to access all the data it stores and to clear them.
*
* @author Rsl1122
* @since 2.0.0
*/
public class DataCacheHandler extends LocationCache {
// Cache
private final HashMap<UUID, UserData> dataCache;
private HashMap<String, Integer> commandUse;
// Plan
private final Plan plugin;
private final Database db;
// Queues
private DataCacheSaveQueue saveTask;
private DataCacheClearQueue clearTask;
private DataCacheProcessQueue processTask;
private DataCacheGetQueue getTask;
// Variables
private int timesSaved;
private int maxPlayers;
/**
* Class Constructor.
*
* Gets the Database from the plugin. Starts the queues. Registers
* Asyncronous Periodic Save Task
*
* @param plugin Current instance of Plan
*/
public DataCacheHandler(Plan plugin) {
super();
this.plugin = plugin;
db = plugin.getDB();
dataCache = new HashMap<>();
startQueues();
timesSaved = 0;
maxPlayers = plugin.getServer().getMaxPlayers();
commandUse = new HashMap<>();
if (!getCommandUseFromDb()) {
Log.error(Phrase.DB_FAILURE_DISABLE + "");
plugin.getServer().getPluginManager().disablePlugin(plugin);
return;
}
startAsyncPeriodicSaveTask();
}
/**
* Used to get the initial commandUse Map from the database.
*
* @return Was the fetch successful?
*/
public boolean getCommandUseFromDb() {
try {
commandUse = db.getCommandUse();
return true;
} catch (Exception e) {
Log.toLog(this.getClass().getName(), e);
}
return false;
}
/**
* Used to start all processing Queue Threads.
*/
public void startQueues() {
clearTask = new DataCacheClearQueue(this);
saveTask = new DataCacheSaveQueue(plugin, clearTask);
getTask = new DataCacheGetQueue(plugin);
processTask = new DataCacheProcessQueue(this);
}
/**
* Used to start the Asyncronous Save Task.
*
* @throws IllegalArgumentException BukkitRunnable was given wrong
* parameters.
* @throws IllegalStateException BukkitScheduler is in a wrong state.
*/
public void startAsyncPeriodicSaveTask() throws IllegalArgumentException, IllegalStateException {
int minutes = Settings.SAVE_CACHE_MIN.getNumber();
if (minutes <= 0) {
minutes = 5;
}
final int clearAfterXsaves;
int configValue = Settings.CLEAR_CACHE_X_SAVES.getNumber();
if (configValue <= 1) {
clearAfterXsaves = 2;
} else {
clearAfterXsaves = configValue;
}
BukkitTask asyncPeriodicCacheSaveTask = new BukkitRunnable() {
@Override
public void run() {
DataCacheHandler handler = Plan.getInstance().getHandler();
handler.saveHandlerDataToCache();
handler.saveCachedUserData();
if (timesSaved % clearAfterXsaves == 0) {
handler.clearCache();
}
saveCommandUse();
timesSaved++;
}
}.runTaskTimerAsynchronously(plugin, 60 * 20 * minutes, 60 * 20 * minutes);
}
/**
* Uses Database or Cache to retrieve the UserData of a matching player.
*
* Caches the data to the Cache if cache-parameter is true.
*
* @param processor DBCallableProcessor Object used to process the data
* after it was retrieved
* @param uuid Player's UUID
* @param cache Whether or not the UserData will be Cached in this instance
* of DataCacheHandler after it has been fetched (if not already fetched)
*/
public void getUserDataForProcessing(DBCallableProcessor processor, UUID uuid, boolean cache) {
Log.debug(uuid + ": HANDLER getForProcess," + " Cache:" + cache);
UserData uData = dataCache.get(uuid);
if (uData == null) {
if (cache) {
DBCallableProcessor cacher = new DBCallableProcessor() {
@Override
public void process(UserData data) {
cache(data);
}
};
getTask.scheduleForGet(uuid, cacher, processor);
} else {
getTask.scheduleForGet(uuid, processor);
}
} else {
processor.process(uData);
}
}
/**
* Used to Cache a UserData object to the Cache.
*
* If a object already exists it will be replaced.
*
* @param data UserData object with the UUID inside used as key.
*/
public void cache(UserData data) {
dataCache.put(data.getUuid(), data);
Log.info(Phrase.CACHE_ADD.parse(data.getUuid().toString()));
}
/**
* Uses Database or Cache to retrieve the UserData of a matching player.
*
* Always Caches the data after retrieval (unless already cached)
*
* @param processor DBCallableProcessor Object used to process the data
* after it was retrieved
* @param uuid Player's UUID
*/
public void getUserDataForProcessing(DBCallableProcessor processor, UUID uuid) {
getUserDataForProcessing(processor, uuid, true);
}
/**
* Saves all UserData in the cache to Database.
*
* ATTENTION: TODO - Doesn't save the Locations in the locationCache.
*
* Should only be called from Async thread
*/
public void saveCachedUserData() {
List<UserData> data = new ArrayList<>();
data.addAll(dataCache.values());
try {
db.saveMultipleUserData(data);
} catch (SQLException ex) {
Log.toLog(this.getClass().getName(), ex);
}
}
/**
* Used to add event HandlingInfo to the processTask's pool.
*
* Given HandlingInfo object's process method will be called.
*
* @param i Object that extends HandlingInfo.
*/
public void addToPool(HandlingInfo i) {
Log.debug(i.getUuid() + ": Adding to pool, type:" + i.getType().name());
processTask.addToPool(i);
}
/**
* Saves all data in the cache to Database and closes the database down.
*
* Stops all tasks.
*
* If processTask has unprocessed information, it will be processed.
*/
public void saveCacheOnDisable() {
long time = MiscUtils.getTime();
Log.debug("SaveCacheOnDisable! " + time);
saveTask.stop();
getTask.stop();
clearTask.stop();
List<HandlingInfo> toProcess = processTask.stop();
Log.debug("ToProcess size: " + toProcess.size() + " DataCache size: " + dataCache.keySet().size());
Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
Log.debug("Online: " + onlinePlayers.size());
for (Player p : onlinePlayers) {
UUID uuid = p.getUniqueId();
endSession(uuid);
if (dataCache.containsKey(uuid)) {
dataCache.get(uuid).addLocations(getLocationsForSaving(uuid));
}
toProcess.add(new LogoutInfo(uuid, time, p.isBanned(), p.getGameMode(), getSession(uuid)));
}
Log.debug("ToProcess size_AFTER: " + toProcess.size() + " DataCache size: " + dataCache.keySet().size());
Collections.sort(toProcess, new HandlingInfoTimeComparator());
processUnprocessedHandlingInfo(toProcess);
List<UserData> data = new ArrayList<>();
data.addAll(dataCache.values());
Log.debug("SAVING, DataCache size: " + dataCache.keySet().size());
try {
db.saveMultipleUserData(data);
db.saveCommandUse(commandUse);
db.close();
} catch (SQLException e) {
Log.toLog(this.getClass().getName(), e);
}
Log.debug("SaveCacheOnDisable_END");
}
private void processUnprocessedHandlingInfo(List<HandlingInfo> toProcess) {
Log.debug("PROCESS: " + toProcess.size());
for (HandlingInfo i : toProcess) {
UserData uData = dataCache.get(i.getUuid());
if (uData == null) {
DBCallableProcessor p = new DBCallableProcessor() {
@Override
public void process(UserData data) {
i.process(data);
}
};
getUserDataForProcessing(p, i.getUuid());
} else {
i.process(uData);
}
}
}
/**
* Saves the cached data of matching Player if it is in the cache.
*
* @param uuid Player's UUID
*/
public void saveCachedData(UUID uuid) {
Log.debug(uuid + ": SaveCachedData");
DBCallableProcessor saveProcessor = new DBCallableProcessor() {
@Override
public void process(UserData data) {
data.addLocations(getLocationsForSaving(uuid));
clearLocations(uuid);
data.access();
data.setClearAfterSave(true);
saveTask.scheduleForSave(data);
}
};
getUserDataForProcessing(saveProcessor, uuid);
}
/**
* Saves the cached CommandUse.
*
* Should be only called from an Asyncronous Thread.
*/
public void saveCommandUse() {
try {
db.saveCommandUse(commandUse);
} catch (SQLException | NullPointerException e) {
Log.toLog(this.getClass().getName(), e);
}
}
/**
* Refreshes the calculations for all online players with ReloadInfo.
*/
public void saveHandlerDataToCache() {
Bukkit.getServer().getOnlinePlayers().parallelStream().forEach((p) -> {
saveHandlerDataToCache(p);
});
}
private void saveHandlerDataToCache(Player player) {
long time = MiscUtils.getTime();
UUID uuid = player.getUniqueId();
addToPool(new ReloadInfo(uuid, time, player.getAddress().getAddress(), player.isBanned(), player.getDisplayName(), player.getGameMode()));
}
/**
* Schedules all UserData from the Cache to be cleared.
*/
public void clearCache() {
clearTask.scheduleForClear(dataCache.keySet());
}
/**
* Clears the matching UserData from the Cache if they're not online.
*
* @param uuid Player's UUID
*/
public void clearFromCache(UUID uuid) {
Log.debug(uuid + ": Clear");
if (getOfflinePlayer(uuid).isOnline()) {
Log.debug(uuid + ": Online, did not clear");
UserData data = dataCache.get(uuid);
if (data != null) {
data.setClearAfterSave(false);
}
} else {
dataCache.remove(uuid);
Log.info(Phrase.CACHE_REMOVE.parse(uuid.toString()));
}
}
/**
* Schedules a matching UserData object to be cleared from the cache.
*
* @param uuid Player's UUID.
*/
public void scheludeForClear(UUID uuid) {
clearTask.scheduleForClear(uuid);
}
/**
* Check whether or not the UserData object is being accessed by save or
* process tasks.
*
* @param uuid Player's UUID
* @return true/false
*/
public boolean isDataAccessed(UUID uuid) {
UserData userData = dataCache.get(uuid);
if (userData == null) {
return false;
}
boolean isAccessed = (userData.isAccessed()) || saveTask.containsUUID(uuid) || processTask.containsUUID(uuid);
if (isAccessed) {
userData.setClearAfterSave(false);
}
return isAccessed;
}
/**
* Creates a new UserData instance and saves it to the Database.
*
* @param player Player the new UserData is created for
*/
public void newPlayer(Player player) {
newPlayer(NewPlayerCreator.createNewPlayer(player));
}
/**
* Creates a new UserData instance and saves it to the Database.
*
* @param player Player the new UserData is created for
*/
public void newPlayer(OfflinePlayer player) {
newPlayer(NewPlayerCreator.createNewPlayer(player));
}
/**
* Schedules a new player's data to be saved to the Database.
*
* @param data UserData object to schedule for save.
*/
public void newPlayer(UserData data) {
saveTask.scheduleNewPlayer(data);
cache(data);
}
/**
* Used to get the contents of the cache.
*
* @return The HashMap containing all Cached UserData
*/
public HashMap<UUID, UserData> getDataCache() {
return dataCache;
}
/**
* Used to get the cached commandUse.
*
* @return Map with key:value - "/command":4
*/
public Map<String, Integer> getCommandUse() {
return commandUse;
}
/**
* If /reload is run this treats every online player as a new login.
*
* Calls all the methods that are ran when PlayerJoinEvent is fired
*/
public void handleReload() {
BukkitTask asyncReloadCacheUpdateTask = (new BukkitRunnable() {
@Override
public void run() {
for (Player player : Bukkit.getOnlinePlayers()) {
UUID uuid = player.getUniqueId();
boolean isNewPlayer = !db.wasSeenBefore(uuid);
if (isNewPlayer) {
newPlayer(player);
}
startSession(uuid);
saveHandlerDataToCache(player);
}
this.cancel();
}
}).runTaskAsynchronously(plugin);
}
/**
* Used by Analysis for Player activity graphs.
*
* @return Maximum number of players defined in server.properties.
*/
public int getMaxPlayers() {
return maxPlayers;
}
/**
* Used to handle a command's execution.
*
* @param command "/command"
*/
public void handleCommand(String command) {
if (!commandUse.containsKey(command)) {
commandUse.put(command, 0);
}
commandUse.put(command, commandUse.get(command) + 1);
}
}