players = new ArrayList<>();
+ String sql = "SELECT * FROM " + tableName + " ORDER BY " + col.LAST_LOGIN + " DESC LIMIT 10;";
+ try (Statement st = con.createStatement(); ResultSet rs = st.executeQuery(sql)) {
+ while (rs.next()) {
+ players.add(buildAuthFromResultSet(rs));
+ }
+ } catch (SQLException e) {
+ logSqlException(e);
+ }
+ return players;
+ }
+
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = !col.SALT.isEmpty() ? row.getString(col.SALT) : null;
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/Factory.java b/src/main/java/fr/xephi/authme/initialization/factory/Factory.java
deleted file mode 100644
index 0f4ae62a..00000000
--- a/src/main/java/fr/xephi/authme/initialization/factory/Factory.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package fr.xephi.authme.initialization.factory;
-
-/**
- * Injectable factory that creates new instances of a certain type.
- *
- * @param the parent type to which the factory is limited to
- */
-public interface Factory
{
-
- /**
- * Creates an instance of the given class.
- *
- * @param clazz the class to instantiate
- * @param the class type
- * @return new instance of the class
- */
- C newInstance(Class clazz);
-
-}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java
deleted file mode 100644
index e063d149..00000000
--- a/src/main/java/fr/xephi/authme/initialization/factory/FactoryDependencyHandler.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package fr.xephi.authme.initialization.factory;
-
-import ch.jalu.injector.Injector;
-import ch.jalu.injector.context.ResolvedInstantiationContext;
-import ch.jalu.injector.handlers.dependency.DependencyHandler;
-import ch.jalu.injector.handlers.instantiation.DependencyDescription;
-import ch.jalu.injector.utils.ReflectionUtils;
-
-/**
- * Dependency handler that builds {@link Factory} objects.
- */
-public class FactoryDependencyHandler implements DependencyHandler {
-
- @Override
- public Object resolveValue(ResolvedInstantiationContext> context, DependencyDescription dependencyDescription) {
- if (dependencyDescription.getType() == Factory.class) {
- Class> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
- if (genericType == null) {
- throw new IllegalStateException("Factory fields must have concrete generic type. "
- + "Cannot get generic type for field in '" + context.getMappedClass() + "'");
- }
-
- return new FactoryImpl<>(genericType, context.getInjector());
- }
- return null;
- }
-
- private static final class FactoryImpl implements Factory
{
-
- private final Injector injector;
- private final Class
parentClass;
-
- FactoryImpl(Class
parentClass, Injector injector) {
- this.parentClass = parentClass;
- this.injector = injector;
- }
-
- @Override
- public C newInstance(Class clazz) {
- if (parentClass.isAssignableFrom(clazz)) {
- return injector.newInstance(clazz);
- }
- throw new IllegalArgumentException(clazz + " not child of " + parentClass);
- }
- }
-}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java
deleted file mode 100644
index 13a5cbb5..00000000
--- a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStore.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package fr.xephi.authme.initialization.factory;
-
-import java.util.Collection;
-
-/**
- * Injectable object to retrieve and create singletons of a common parent.
- *
- * @param the parent class to which this store is limited to
- */
-public interface SingletonStore
{
-
- /**
- * Returns the singleton of the given type, creating it if it hasn't been yet created.
- *
- * @param clazz the class to get the singleton for
- * @param type of the singleton
- * @return the singleton of type {@code C}
- */
- C getSingleton(Class clazz);
-
- /**
- * Returns all existing singletons of this store's type.
- *
- * @return all registered singletons of type {@code P}
- */
- Collection retrieveAllOfType();
-
- /**
- * Returns all existing singletons of the given type.
- *
- * @param clazz the type to get singletons for
- * @param class type
- * @return all registered singletons of type {@code C}
- */
- Collection retrieveAllOfType(Class clazz);
-
-}
diff --git a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java b/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java
deleted file mode 100644
index 09a198c7..00000000
--- a/src/main/java/fr/xephi/authme/initialization/factory/SingletonStoreDependencyHandler.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package fr.xephi.authme.initialization.factory;
-
-import ch.jalu.injector.Injector;
-import ch.jalu.injector.context.ResolvedInstantiationContext;
-import ch.jalu.injector.handlers.dependency.DependencyHandler;
-import ch.jalu.injector.handlers.instantiation.DependencyDescription;
-import ch.jalu.injector.utils.ReflectionUtils;
-
-import java.util.Collection;
-
-/**
- * Dependency handler that builds {@link SingletonStore} objects.
- */
-public class SingletonStoreDependencyHandler implements DependencyHandler {
-
- @Override
- public Object resolveValue(ResolvedInstantiationContext> context, DependencyDescription dependencyDescription) {
- if (dependencyDescription.getType() == SingletonStore.class) {
- Class> genericType = ReflectionUtils.getGenericType(dependencyDescription.getGenericType());
- if (genericType == null) {
- throw new IllegalStateException("Singleton store fields must have concrete generic type. "
- + "Cannot get generic type for field in '" + context.getMappedClass() + "'");
- }
-
- return new SingletonStoreImpl<>(genericType, context.getInjector());
- }
- return null;
- }
-
- private static final class SingletonStoreImpl implements SingletonStore
{
-
- private final Injector injector;
- private final Class
parentClass;
-
- SingletonStoreImpl(Class
parentClass, Injector injector) {
- this.parentClass = parentClass;
- this.injector = injector;
- }
-
- @Override
- public C getSingleton(Class clazz) {
- if (parentClass.isAssignableFrom(clazz)) {
- return injector.getSingleton(clazz);
- }
- throw new IllegalArgumentException(clazz + " not child of " + parentClass);
- }
-
- @Override
- public Collection retrieveAllOfType() {
- return retrieveAllOfType(parentClass);
- }
-
- @Override
- public Collection retrieveAllOfType(Class clazz) {
- if (parentClass.isAssignableFrom(clazz)) {
- return injector.retrieveAllOfType(clazz);
- }
- throw new IllegalArgumentException(clazz + " not child of " + parentClass);
- }
- }
-}
diff --git a/src/main/java/fr/xephi/authme/listener/ServerListener.java b/src/main/java/fr/xephi/authme/listener/ServerListener.java
index 055e72af..4aceaf6f 100644
--- a/src/main/java/fr/xephi/authme/listener/ServerListener.java
+++ b/src/main/java/fr/xephi/authme/listener/ServerListener.java
@@ -40,6 +40,10 @@ public class ServerListener implements Listener {
if ("Essentials".equalsIgnoreCase(pluginName)) {
pluginHookService.unhookEssentials();
ConsoleLogger.info("Essentials has been disabled: unhooking");
+ } else if ("CMI".equalsIgnoreCase(pluginName)) {
+ pluginHookService.unhookCmi();
+ spawnLoader.unloadCmiSpawn();
+ ConsoleLogger.info("CMI has been disabled: unhooking");
} else if ("Multiverse-Core".equalsIgnoreCase(pluginName)) {
pluginHookService.unhookMultiverse();
ConsoleLogger.info("Multiverse-Core has been disabled: unhooking");
@@ -70,6 +74,9 @@ public class ServerListener implements Listener {
pluginHookService.tryHookToMultiverse();
} else if ("EssentialsSpawn".equalsIgnoreCase(pluginName)) {
spawnLoader.loadEssentialsSpawn();
+ } else if ("CMI".equalsIgnoreCase(pluginName)) {
+ pluginHookService.tryHookToCmi();
+ spawnLoader.loadCmiSpawn();
} else if ("ProtocolLib".equalsIgnoreCase(pluginName)) {
protocolLibService.setup();
}
diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
index 14baf3ae..7664e143 100644
--- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java
+++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
@@ -50,6 +50,11 @@ public enum AdminPermission implements PermissionNode {
*/
GET_IP("authme.admin.getip"),
+ /**
+ * Administrator command to see the last recently logged in players.
+ */
+ SEE_RECENT_PLAYERS("authme.admin.seerecent"),
+
/**
* Administrator command to teleport to the AuthMe spawn.
*/
diff --git a/src/main/java/fr/xephi/authme/process/SyncProcessManager.java b/src/main/java/fr/xephi/authme/process/SyncProcessManager.java
index e634302f..136c6b35 100644
--- a/src/main/java/fr/xephi/authme/process/SyncProcessManager.java
+++ b/src/main/java/fr/xephi/authme/process/SyncProcessManager.java
@@ -47,8 +47,8 @@ public class SyncProcessManager {
runTask(() -> processSyncPlayerLogout.processSyncLogout(player));
}
- public void processSyncPlayerLogin(Player player) {
- runTask(() -> processSyncPlayerLogin.processPlayerLogin(player));
+ public void processSyncPlayerLogin(Player player, boolean isFirstLogin) {
+ runTask(() -> processSyncPlayerLogin.processPlayerLogin(player, isFirstLogin));
}
public void processSyncPlayerQuit(Player player, boolean wasLoggedIn) {
diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
index e76fea78..75219b45 100644
--- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java
@@ -220,6 +220,8 @@ public class AsynchronousLogin implements AsynchronousProcess {
*/
private void performLogin(Player player, PlayerAuth auth) {
if (player.isOnline()) {
+ final boolean isFirstLogin = (auth.getLastLogin() == null);
+
// Update auth to reflect this new login
final String ip = PlayerUtils.getPlayerIp(player);
auth.setRealName(player.getName());
@@ -258,7 +260,7 @@ public class AsynchronousLogin implements AsynchronousProcess {
// task, we schedule it in the end
// so that we can be sure, and have not to care if it might be
// processed in other order.
- syncProcessManager.processSyncPlayerLogin(player);
+ syncProcessManager.processSyncPlayerLogin(player, isFirstLogin);
} else {
ConsoleLogger.warning("Player '" + player.getName() + "' wasn't online during login process, aborted...");
}
diff --git a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
index 359aca03..f47ccdbf 100644
--- a/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
+++ b/src/main/java/fr/xephi/authme/process/login/ProcessSyncPlayerLogin.java
@@ -62,7 +62,13 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
}
}
- public void processPlayerLogin(Player player) {
+ /**
+ * Performs operations in sync mode for a player that has just logged in.
+ *
+ * @param player the player that was logged in
+ * @param isFirstLogin true if this is the first time the player logged in
+ */
+ public void processPlayerLogin(Player player, boolean isFirstLogin) {
final String name = player.getName().toLowerCase();
final LimboPlayer limbo = limboService.getLimboPlayer(name);
@@ -93,6 +99,9 @@ public class ProcessSyncPlayerLogin implements SynchronousProcess {
welcomeMessageConfiguration.sendWelcomeMessage(player);
// Login is now finished; we can force all commands
+ if (isFirstLogin) {
+ commandManager.runCommandsOnFirstLogin(player);
+ }
commandManager.runCommandsOnLogin(player);
// Send Bungee stuff. The service will check if it is enabled or not.
diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
index 11f6354c..a370b1a9 100644
--- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
+++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java
@@ -1,9 +1,9 @@
package fr.xephi.authme.process.register;
+import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.AsynchronousProcess;
diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
index ebccb63d..d6cb6e71 100644
--- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
+++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
@@ -1,9 +1,9 @@
package fr.xephi.authme.security;
+import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
import fr.xephi.authme.initialization.Reloadable;
-import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
diff --git a/src/main/java/fr/xephi/authme/service/PluginHookService.java b/src/main/java/fr/xephi/authme/service/PluginHookService.java
index af86b8a3..0a204aa0 100644
--- a/src/main/java/fr/xephi/authme/service/PluginHookService.java
+++ b/src/main/java/fr/xephi/authme/service/PluginHookService.java
@@ -22,6 +22,7 @@ public class PluginHookService {
private final PluginManager pluginManager;
private Essentials essentials;
+ private Plugin cmi;
private MultiverseCore multiverse;
/**
@@ -33,6 +34,7 @@ public class PluginHookService {
public PluginHookService(PluginManager pluginManager) {
this.pluginManager = pluginManager;
tryHookToEssentials();
+ tryHookToCmi();
tryHookToMultiverse();
}
@@ -60,6 +62,19 @@ public class PluginHookService {
return null;
}
+ /**
+ * If CMI is hooked into, return CMI' data folder.
+ *
+ * @return The CMI data folder, or null if unavailable
+ */
+ public File getCmiDataFolder() {
+ Plugin plugin = pluginManager.getPlugin("CMI");
+ if(plugin == null) {
+ return null;
+ }
+ return plugin.getDataFolder();
+ }
+
/**
* Return the spawn of the given world as defined by Multiverse (if available).
*
@@ -76,10 +91,10 @@ public class PluginHookService {
return null;
}
-
// ------
// "Is plugin available" methods
// ------
+
/**
* @return true if we have a hook to Essentials, false otherwise
*/
@@ -87,6 +102,13 @@ public class PluginHookService {
return essentials != null;
}
+ /**
+ * @return true if we have a hook to CMI, false otherwise
+ */
+ public boolean isCmiAvailable() {
+ return cmi != null;
+ }
+
/**
* @return true if we have a hook to Multiverse, false otherwise
*/
@@ -109,6 +131,17 @@ public class PluginHookService {
}
}
+ /**
+ * Attempts to create a hook into CMI.
+ */
+ public void tryHookToCmi() {
+ try {
+ cmi = getPlugin(pluginManager, "CMI", Plugin.class);
+ } catch (Exception | NoClassDefFoundError ignored) {
+ cmi = null;
+ }
+ }
+
/**
* Attempts to create a hook into Multiverse.
*/
@@ -123,6 +156,7 @@ public class PluginHookService {
// ------
// Unhook methods
// ------
+
/**
* Unhooks from Essentials.
*/
@@ -130,6 +164,13 @@ public class PluginHookService {
essentials = null;
}
+ /**
+ * Unhooks from CMI.
+ */
+ public void unhookCmi() {
+ cmi = null;
+ }
+
/**
* Unhooks from Multiverse.
*/
@@ -140,6 +181,7 @@ public class PluginHookService {
// ------
// Helpers
// ------
+
private static T getPlugin(PluginManager pluginManager, String name, Class clazz)
throws Exception, NoClassDefFoundError {
if (pluginManager.isPluginEnabled(name)) {
@@ -150,5 +192,4 @@ public class PluginHookService {
return null;
}
-
}
diff --git a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
index 09442ed4..ea235b3c 100644
--- a/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
+++ b/src/main/java/fr/xephi/authme/settings/SpawnLoader.java
@@ -35,6 +35,7 @@ public class SpawnLoader implements Reloadable {
private FileConfiguration authMeConfiguration;
private String[] spawnPriority;
private Location essentialsSpawn;
+ private Location cmiSpawn;
/**
* Constructor.
@@ -130,6 +131,31 @@ public class SpawnLoader implements Reloadable {
essentialsSpawn = null;
}
+ /**
+ * Load the spawn point defined in CMI.
+ */
+ public void loadCmiSpawn() {
+ File cmiFolder = pluginHookService.getCmiDataFolder();
+ if (cmiFolder == null) {
+ return;
+ }
+
+ File cmiConfig = new File(cmiFolder, "config.yml");
+ if (cmiConfig.exists()) {
+ cmiSpawn = getLocationFromCmiConfiguration(YamlConfiguration.loadConfiguration(cmiConfig));
+ } else {
+ cmiSpawn = null;
+ ConsoleLogger.info("CMI config file not found: '" + cmiConfig.getAbsolutePath() + "'");
+ }
+ }
+
+ /**
+ * Unset the spawn point defined in CMI.
+ */
+ public void unloadCmiSpawn() {
+ cmiSpawn = null;
+ }
+
/**
* Return the spawn location for the given player. The source of the spawn location varies
* depending on the spawn priority setting.
@@ -162,6 +188,9 @@ public class SpawnLoader implements Reloadable {
case "essentials":
spawnLoc = essentialsSpawn;
break;
+ case "cmi":
+ spawnLoc = cmiSpawn;
+ break;
case "authme":
spawnLoc = getSpawn();
break;
@@ -242,6 +271,28 @@ public class SpawnLoader implements Reloadable {
return null;
}
+ /**
+ * Build a {@link Location} object based on the CMI configuration.
+ *
+ * @param configuration The CMI file configuration to read from
+ *
+ * @return Location corresponding to the values in the path
+ */
+ private static Location getLocationFromCmiConfiguration(FileConfiguration configuration) {
+ final String pathPrefix = "Spawn.Main";
+ if (isLocationCompleteInCmiConfig(configuration, pathPrefix)) {
+ String prefix = pathPrefix + ".";
+ String worldName = configuration.getString(prefix + "World");
+ World world = Bukkit.getWorld(worldName);
+ if (!StringUtils.isEmpty(worldName) && world != null) {
+ return new Location(world, configuration.getDouble(prefix + "X"),
+ configuration.getDouble(prefix + "Y"), configuration.getDouble(prefix + "Z"),
+ getFloat(configuration, prefix + "Yaw"), getFloat(configuration, prefix + "Pitch"));
+ }
+ }
+ return null;
+ }
+
/**
* Return whether the file configuration contains all fields necessary to define a spawn
* under the given path.
@@ -261,6 +312,24 @@ public class SpawnLoader implements Reloadable {
return true;
}
+ /**
+ * Return whether the CMI file configuration contains all spawn fields under the given path.
+ *
+ * @param cmiConfiguration The file configuration from CMI
+ * @param pathPrefix The path to verify
+ *
+ * @return True if all spawn fields are present, false otherwise
+ */
+ private static boolean isLocationCompleteInCmiConfig(FileConfiguration cmiConfiguration, String pathPrefix) {
+ String[] fields = {"World", "X", "Y", "Z", "Yaw", "Pitch"};
+ for (String field : fields) {
+ if (!cmiConfiguration.contains(pathPrefix + "." + field)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Retrieve a property as a float from the given file configuration.
*
@@ -274,4 +343,5 @@ public class SpawnLoader implements Reloadable {
// This behavior is consistent with FileConfiguration#getDouble
return (value instanceof Number) ? ((Number) value).floatValue() : 0;
}
+
}
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java
index f7023020..e210f597 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandConfig.java
@@ -13,6 +13,7 @@ public class CommandConfig {
private Map onJoin = new LinkedHashMap<>();
private Map onLogin = new LinkedHashMap<>();
private Map onSessionLogin = new LinkedHashMap<>();
+ private Map onFirstLogin = new LinkedHashMap<>();
private Map onRegister = new LinkedHashMap<>();
private Map onUnregister = new LinkedHashMap<>();
private Map onLogout = new LinkedHashMap<>();
@@ -41,6 +42,14 @@ public class CommandConfig {
this.onSessionLogin = onSessionLogin;
}
+ public Map getOnFirstLogin() {
+ return onFirstLogin;
+ }
+
+ public void setOnFirstLogin(Map onFirstLogin) {
+ this.onFirstLogin = onFirstLogin;
+ }
+
public Map getOnRegister() {
return onRegister;
}
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
index f6fbd4c7..6fdb5fde 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandManager.java
@@ -34,6 +34,7 @@ public class CommandManager implements Reloadable {
private WrappedTagReplacer onJoinCommands;
private WrappedTagReplacer onLoginCommands;
private WrappedTagReplacer onSessionLoginCommands;
+ private WrappedTagReplacer onFirstLoginCommands;
private WrappedTagReplacer onRegisterCommands;
private WrappedTagReplacer onUnregisterCommands;
private WrappedTagReplacer onLogoutCommands;
@@ -75,7 +76,6 @@ public class CommandManager implements Reloadable {
executeCommands(player, onLoginCommands.getAdaptedItems(player));
}
-
/**
* Runs the configured commands for when a player has logged in successfully due to session.
*
@@ -85,6 +85,15 @@ public class CommandManager implements Reloadable {
executeCommands(player, onSessionLoginCommands.getAdaptedItems(player));
}
+ /**
+ * Runs the configured commands for when a player logs in the first time.
+ *
+ * @param player the player that has logged in for the first time
+ */
+ public void runCommandsOnFirstLogin(Player player) {
+ executeCommands(player, onFirstLoginCommands.getAdaptedItems(player));
+ }
+
/**
* Runs the configured commands for when a player has been unregistered.
*
@@ -124,6 +133,7 @@ public class CommandManager implements Reloadable {
CommandConfig commandConfig = settingsManager.getProperty(CommandSettingsHolder.COMMANDS);
onJoinCommands = newReplacer(commandConfig.getOnJoin());
onLoginCommands = newReplacer(commandConfig.getOnLogin());
+ onFirstLoginCommands = newReplacer(commandConfig.getOnFirstLogin());
onSessionLoginCommands = newReplacer(commandConfig.getOnSessionLogin());
onRegisterCommands = newReplacer(commandConfig.getOnRegister());
onUnregisterCommands = newReplacer(commandConfig.getOnUnregister());
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java
index e1d1085a..acd39cc6 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandMigrationService.java
@@ -23,7 +23,7 @@ class CommandMigrationService implements MigrationService {
/** List of all properties in {@link CommandConfig}. */
@VisibleForTesting
static final List COMMAND_CONFIG_PROPERTIES = ImmutableList.of(
- "onJoin", "onLogin", "onSessionLogin", "onRegister", "onUnregister", "onLogout");
+ "onJoin", "onLogin", "onSessionLogin", "onFirstLogin", "onRegister", "onUnregister", "onLogout");
@Inject
private SettingsMigrationService settingsMigrationService;
diff --git a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
index 29e9b0c9..87530d22 100644
--- a/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
+++ b/src/main/java/fr/xephi/authme/settings/commandconfig/CommandSettingsHolder.java
@@ -22,7 +22,7 @@ public final class CommandSettingsHolder implements SettingsHolder {
@SectionComments
public static Map sectionComments() {
- String[] comments = {
+ String[] rootComments = {
"This configuration file allows you to execute commands on various events.",
"Supported placeholders in commands:",
" %p is replaced with the player name.",
@@ -48,10 +48,14 @@ public final class CommandSettingsHolder implements SettingsHolder {
" command: 'broadcast %p has joined, welcome back!'",
" executor: CONSOLE",
"",
- "Supported command events: onLogin, onSessionLogin, onJoin, onLogout, onRegister, onUnregister"
+ "Supported command events: onLogin, onSessionLogin, onFirstLogin, onJoin, onLogout, onRegister, "
+ + "onUnregister"
};
Map commentMap = new HashMap<>();
- commentMap.put("", comments);
+ commentMap.put("", rootComments);
+ commentMap.put("onFirstLogin", new String[]{
+ "Commands to run for players logging in whose 'last login date' was empty"
+ });
commentMap.put("onUnregister", new String[]{
"Commands to run whenever a player is unregistered (by himself, or by an admin)"
});
diff --git a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
index ab202d2f..a53eaa76 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/RestrictionSettings.java
@@ -140,9 +140,9 @@ public final class RestrictionSettings implements SettingsHolder {
public static final Property DISPLAY_OTHER_ACCOUNTS =
newProperty("settings.restrictions.displayOtherAccounts", true);
- @Comment("Spawn priority; values: authme, essentials, multiverse, default")
+ @Comment("Spawn priority; values: authme, essentials, cmi, multiverse, default")
public static final Property SPAWN_PRIORITY =
- newProperty("settings.restrictions.spawnPriority", "authme,essentials,multiverse,default");
+ newProperty("settings.restrictions.spawnPriority", "authme,essentials,cmi,multiverse,default");
@Comment("Maximum Login authorized by IP")
public static final Property MAX_LOGIN_PER_IP =
diff --git a/src/main/java/fr/xephi/authme/task/CleanupTask.java b/src/main/java/fr/xephi/authme/task/CleanupTask.java
index f373c3e5..48d989f1 100644
--- a/src/main/java/fr/xephi/authme/task/CleanupTask.java
+++ b/src/main/java/fr/xephi/authme/task/CleanupTask.java
@@ -1,7 +1,7 @@
package fr.xephi.authme.task;
+import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.initialization.HasCleanup;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import org.bukkit.scheduler.BukkitRunnable;
import javax.inject.Inject;
diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml
index fecccd59..5e7717ac 100644
--- a/src/main/resources/commands.yml
+++ b/src/main/resources/commands.yml
@@ -24,7 +24,9 @@
# command: 'broadcast %p has joined, welcome back!'
# executor: CONSOLE
#
-# Supported command events: onLogin, onSessionLogin, onJoin, onLogout, onRegister, onUnregister
+# Supported command events: onLogin, onSessionLogin, onFirstLogin, onJoin, onLogout, onRegister, onUnregister
+# Commands to run for players logging in whose 'last login date' was empty
+onFirstLogin: {}
onJoin: {}
onLogin: {}
# These commands are called whenever a logged in player uses /logout or quits.
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 9014476f..0f74a3ac 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -17,7 +17,7 @@ softdepend:
commands:
authme:
description: AuthMe op commands
- usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
+ usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|purgeplayer|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|recent|debug
email:
description: Add email or recover password
usage: /email show|add|change|recover|code|setpassword
@@ -74,6 +74,7 @@ permissions:
authme.admin.register: true
authme.admin.reload: true
authme.admin.seeotheraccounts: true
+ authme.admin.seerecent: true
authme.admin.setfirstspawn: true
authme.admin.setspawn: true
authme.admin.spawn: true
@@ -134,6 +135,9 @@ permissions:
authme.admin.seeotheraccounts:
description: Permission to see the other accounts of the players that log in.
default: op
+ authme.admin.seerecent:
+ description: Administrator command to see the last recently logged in players.
+ default: op
authme.admin.setfirstspawn:
description: Administrator command to set the first AuthMe spawn.
default: op
diff --git a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java
index d0f3c1b3..e64d0f7e 100644
--- a/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java
+++ b/src/test/java/fr/xephi/authme/AuthMeInitializationTest.java
@@ -8,8 +8,6 @@ import fr.xephi.authme.api.v3.AuthMeApi;
import fr.xephi.authme.command.CommandHandler;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.DataFolder;
-import fr.xephi.authme.initialization.factory.FactoryDependencyHandler;
-import fr.xephi.authme.initialization.factory.SingletonStoreDependencyHandler;
import fr.xephi.authme.listener.BlockListener;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.Management;
@@ -96,7 +94,6 @@ public class AuthMeInitializationTest {
new Settings(dataFolder, mock(PropertyResource.class), null, buildConfigurationData());
Injector injector = new InjectorBuilder()
- .addHandlers(new FactoryDependencyHandler(), new SingletonStoreDependencyHandler())
.addDefaultHandlers("fr.xephi.authme")
.create();
injector.provide(DataFolder.class, dataFolder);
diff --git a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java
index e343199f..d80d8f8a 100644
--- a/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java
+++ b/src/test/java/fr/xephi/authme/command/CommandHandlerTest.java
@@ -1,12 +1,12 @@
package fr.xephi.authme.command;
import ch.jalu.injector.Injector;
+import ch.jalu.injector.factory.Factory;
import com.google.common.collect.Sets;
import fr.xephi.authme.command.TestCommandsUtil.TestLoginCommand;
import fr.xephi.authme.command.TestCommandsUtil.TestRegisterCommand;
import fr.xephi.authme.command.TestCommandsUtil.TestUnregisterCommand;
import fr.xephi.authme.command.help.HelpProvider;
-import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.message.Messages;
import fr.xephi.authme.permission.PermissionsManager;
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java
index 02ecd7e2..626e9c90 100644
--- a/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/ConverterCommandTest.java
@@ -1,8 +1,8 @@
package fr.xephi.authme.command.executable.authme;
+import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.converter.Converter;
-import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.service.BukkitService;
import fr.xephi.authme.service.CommonService;
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/RecentPlayersCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/RecentPlayersCommandTest.java
new file mode 100644
index 00000000..eac5f8fb
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/RecentPlayersCommandTest.java
@@ -0,0 +1,61 @@
+package fr.xephi.authme.command.executable.authme;
+
+import fr.xephi.authme.data.auth.PlayerAuth;
+import fr.xephi.authme.datasource.DataSource;
+import org.bukkit.command.CommandSender;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+/**
+ * Test for {@link RecentPlayersCommand}.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class RecentPlayersCommandTest {
+
+ @InjectMocks
+ @Spy
+ private RecentPlayersCommand command;
+
+ @Mock
+ private DataSource dataSource;
+
+ @Test
+ public void shouldShowRecentPlayers() {
+ // given
+ PlayerAuth auth1 = PlayerAuth.builder()
+ .name("hannah").realName("Hannah").lastIp("11.11.11.11")
+ .lastLogin(1510387755000L) // 11/11/2017 @ 8:09am
+ .build();
+ PlayerAuth auth2 = PlayerAuth.builder()
+ .name("matt").realName("MATT").lastIp("22.11.22.33")
+ .lastLogin(1510269301000L) // 11/09/2017 @ 11:15pm
+ .build();
+ doReturn(ZoneId.of("UTC")).when(command).getZoneId();
+ given(dataSource.getRecentlyLoggedInPlayers()).willReturn(Arrays.asList(auth1, auth2));
+
+ CommandSender sender = mock(CommandSender.class);
+
+ // when
+ command.executeCommand(sender, Collections.emptyList());
+
+ // then
+ verify(sender).sendMessage(argThat(containsString("Recently logged in players")));
+ verify(sender).sendMessage("- Hannah (08:09 AM, 11 Nov with IP 11.11.11.11)");
+ verify(sender).sendMessage("- MATT (11:15 PM, 09 Nov with IP 22.11.22.33)");
+ }
+}
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java
index 52e342db..8b10de64 100644
--- a/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/ReloadCommandTest.java
@@ -1,12 +1,12 @@
package fr.xephi.authme.command.executable.authme;
+import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.service.CommonService;
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java
index e28c110f..52b13b0e 100644
--- a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DataStatisticsTest.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.command.executable.authme.debug;
+import ch.jalu.injector.factory.SingletonStore;
import com.google.common.cache.LoadingCache;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.data.auth.PlayerAuth;
@@ -11,7 +12,6 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.initialization.HasCleanup;
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.initialization.SettingsDependent;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.junit.Before;
diff --git a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java
index 3174926a..e2c63a60 100644
--- a/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java
+++ b/src/test/java/fr/xephi/authme/command/executable/authme/debug/DebugCommandTest.java
@@ -1,6 +1,6 @@
package fr.xephi.authme.command.executable.authme.debug;
-import fr.xephi.authme.initialization.factory.Factory;
+import ch.jalu.injector.factory.Factory;
import fr.xephi.authme.permission.DebugSectionPermissions;
import fr.xephi.authme.permission.PermissionNode;
import fr.xephi.authme.permission.PermissionsManager;
diff --git a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java
index d253869f..2b1e311c 100644
--- a/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java
+++ b/src/test/java/fr/xephi/authme/data/limbo/persistence/LimboPersistenceTest.java
@@ -1,12 +1,12 @@
package fr.xephi.authme.data.limbo.persistence;
+import ch.jalu.injector.factory.Factory;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.limbo.LimboPlayer;
-import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.LimboSettings;
import org.bukkit.entity.Player;
diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
index f72c92ae..530ab56b 100644
--- a/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
+++ b/src/test/java/fr/xephi/authme/datasource/AbstractDataSourceIntegrationTest.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.datasource;
+import com.google.common.collect.Lists;
import fr.xephi.authme.data.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
import org.junit.Test;
@@ -467,4 +468,30 @@ public abstract class AbstractDataSourceIntegrationTest {
assertThat(dataSource.hasSession("user"), equalTo(true));
assertThat(dataSource.hasSession("nonExistentName"), equalTo(false));
}
+
+ @Test
+ public void shouldGetRecentlyLoggedInPlayers() {
+ // given
+ DataSource dataSource = getDataSource();
+ String[] names = {"user3", "user8", "user2", "user4", "user7",
+ "user11", "user14", "user12", "user18", "user16",
+ "user28", "user29", "user22", "user20", "user24"};
+ long timestamp = 1461024000; // 2016-04-19 00:00:00
+ for (int i = 0; i < names.length; ++i) {
+ PlayerAuth auth = PlayerAuth.builder().name(names[i])
+ .registrationDate(1234567)
+ .lastLogin(timestamp + i * 3600)
+ .build();
+ dataSource.saveAuth(auth);
+ dataSource.updateSession(auth);
+ }
+
+ // when
+ List recentPlayers = dataSource.getRecentlyLoggedInPlayers();
+
+ // then
+ assertThat(Lists.transform(recentPlayers, PlayerAuth::getNickname),
+ contains("user24", "user20", "user22", "user29", "user28",
+ "user16", "user18", "user12", "user14", "user11"));
+ }
}
diff --git a/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java b/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java
index 2ffe57f5..b015c2eb 100644
--- a/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java
+++ b/src/test/java/fr/xephi/authme/listener/ServerListenerTest.java
@@ -31,6 +31,7 @@ public class ServerListenerTest {
private static final String ESSENTIALS = "Essentials";
private static final String ESSENTIALS_SPAWN = "EssentialsSpawn";
+ private static final String CMI = "CMI";
private static final String MULTIVERSE = "Multiverse-Core";
private static final String PROTOCOL_LIB = "ProtocolLib";
@@ -58,6 +59,10 @@ public class ServerListenerTest {
public void shouldForwardPluginNameOnEnable() {
checkEnableHandling(ESSENTIALS, () -> verify(pluginHookService).tryHookToEssentials());
checkEnableHandling(ESSENTIALS_SPAWN, () -> verify(spawnLoader).loadEssentialsSpawn());
+ checkEnableHandling(CMI, () -> {
+ verify(pluginHookService).tryHookToCmi();
+ verify(spawnLoader).loadCmiSpawn();
+ });
checkEnableHandling(MULTIVERSE, () -> verify(pluginHookService).tryHookToMultiverse());
checkEnableHandling(PROTOCOL_LIB, () -> verify(protocolLibService).setup());
checkEnableHandling("UnknownPlugin", () -> verifyZeroInteractions(pluginHookService, spawnLoader));
@@ -67,6 +72,10 @@ public class ServerListenerTest {
public void shouldForwardPluginNameOnDisable() {
checkDisableHandling(ESSENTIALS, () -> verify(pluginHookService).unhookEssentials());
checkDisableHandling(ESSENTIALS_SPAWN, () -> verify(spawnLoader).unloadEssentialsSpawn());
+ checkDisableHandling(CMI, () -> {
+ verify(pluginHookService).unhookCmi();
+ verify(spawnLoader).unloadCmiSpawn();
+ });
checkDisableHandling(MULTIVERSE, () -> verify(pluginHookService).unhookMultiverse());
checkDisableHandling(PROTOCOL_LIB, () -> verify(protocolLibService).disable());
checkDisableHandling("UnknownPlugin", () -> verifyZeroInteractions(pluginHookService, spawnLoader));
diff --git a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java
index dee3dc64..e044a754 100644
--- a/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java
+++ b/src/test/java/fr/xephi/authme/process/register/AsyncRegisterTest.java
@@ -1,9 +1,9 @@
package fr.xephi.authme.process.register;
+import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.DataSource;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.process.register.executors.PasswordRegisterParams;
diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java
index 44118076..520ae8dd 100644
--- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java
+++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java
@@ -2,6 +2,7 @@ package fr.xephi.authme.security;
import ch.jalu.injector.Injector;
import ch.jalu.injector.InjectorBuilder;
+import ch.jalu.injector.factory.Factory;
import ch.jalu.injector.testing.BeforeInjecting;
import ch.jalu.injector.testing.DelayedInjectionRunner;
import ch.jalu.injector.testing.InjectDelayed;
@@ -9,7 +10,6 @@ import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.TestHelper;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.PasswordEncryptionEvent;
-import fr.xephi.authme.initialization.factory.Factory;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.security.crypts.Joomla;
diff --git a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java
index 763e6b31..d949db7d 100644
--- a/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java
+++ b/src/test/java/fr/xephi/authme/service/PluginHookServiceTest.java
@@ -35,6 +35,8 @@ public class PluginHookServiceTest {
/** The plugin name of Essentials. */
private static final String ESSENTIALS = "Essentials";
+ /** The plugin name of CMI. */
+ private static final String CMI = "CMI";
/** The plugin name of Multiverse-Core. */
private static final String MULTIVERSE = "Multiverse-Core";
@@ -71,6 +73,19 @@ public class PluginHookServiceTest {
assertThat(pluginHookService.isEssentialsAvailable(), equalTo(true));
}
+ @Test
+ public void shouldHookIntoCmiAtInitialization() {
+ // given
+ PluginManager pluginManager = mock(PluginManager.class);
+ setPluginAvailable(pluginManager, CMI, Plugin.class);
+
+ // when
+ PluginHookService pluginHookService = new PluginHookService(pluginManager);
+
+ // then
+ assertThat(pluginHookService.isCmiAvailable(), equalTo(true));
+ }
+
@Test
public void shouldHookIntoMultiverseAtInitialization() {
// given
@@ -175,6 +190,7 @@ public class PluginHookServiceTest {
// then
assertThat(pluginHookService.isEssentialsAvailable(), equalTo(false));
+ assertThat(pluginHookService.isCmiAvailable(), equalTo(false));
assertThat(pluginHookService.isMultiverseAvailable(), equalTo(false));
}
diff --git a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java
index ebf92726..b4183d70 100644
--- a/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java
+++ b/src/test/java/fr/xephi/authme/settings/commandconfig/CommandManagerTest.java
@@ -107,6 +107,21 @@ public class CommandManagerTest {
verifyZeroInteractions(geoIpService);
}
+ @Test
+ public void shouldExecuteCommandsOnFirstLogin() {
+ // given
+ copyJarFileAsCommandsYml(TEST_FILES_FOLDER + "commands.complete.yml");
+ initManager();
+
+ // when
+ manager.runCommandsOnFirstLogin(player);
+
+ // then
+ verify(bukkitService).dispatchConsoleCommand("pay Bobby 30");
+ verifyNoMoreInteractions(bukkitService);
+ verifyZeroInteractions(geoIpService);
+ }
+
@Test
public void shouldExecuteCommandsOnJoin() {
// given
diff --git a/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java b/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java
index 33cd360b..4269f9bd 100644
--- a/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java
+++ b/src/test/java/fr/xephi/authme/task/CleanupTaskTest.java
@@ -1,7 +1,7 @@
package fr.xephi.authme.task;
+import ch.jalu.injector.factory.SingletonStore;
import fr.xephi.authme.initialization.HasCleanup;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
diff --git a/src/test/java/tools/dependencygraph/DrawDependency.java b/src/test/java/tools/dependencygraph/DrawDependency.java
index 98de28be..3f34197c 100644
--- a/src/test/java/tools/dependencygraph/DrawDependency.java
+++ b/src/test/java/tools/dependencygraph/DrawDependency.java
@@ -1,7 +1,9 @@
package tools.dependencygraph;
-import ch.jalu.injector.handlers.instantiation.DependencyDescription;
-import ch.jalu.injector.handlers.instantiation.Instantiation;
+import ch.jalu.injector.context.ObjectIdentifier;
+import ch.jalu.injector.factory.Factory;
+import ch.jalu.injector.factory.SingletonStore;
+import ch.jalu.injector.handlers.instantiation.Resolution;
import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider;
import ch.jalu.injector.utils.ReflectionUtils;
import com.google.common.collect.HashMultimap;
@@ -14,13 +16,12 @@ import fr.xephi.authme.command.executable.authme.debug.DebugCommand;
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
import fr.xephi.authme.datasource.converter.Converter;
import fr.xephi.authme.initialization.DataFolder;
-import fr.xephi.authme.initialization.factory.Factory;
-import fr.xephi.authme.initialization.factory.SingletonStore;
import fr.xephi.authme.process.AsynchronousProcess;
import fr.xephi.authme.process.SynchronousProcess;
import fr.xephi.authme.process.register.executors.RegistrationExecutor;
import fr.xephi.authme.security.crypts.EncryptionMethod;
import org.bukkit.event.Listener;
+import tools.utils.InjectorUtils;
import tools.utils.ToolTask;
import tools.utils.ToolsConstants;
@@ -124,21 +125,20 @@ public class DrawDependency implements ToolTask {
* This is interesting so that a dependency in a class to {@code Factory} is
* rendered as a dependency to {@code Foo}, not to {@code Factory}.
*
- * @param clazz class of the dependency
* @param genericType generic type of the dependency
* @return the class to use to render the dependency
*/
- private Class> unwrapGenericClass(Class> clazz, Type genericType) {
- if (clazz == Factory.class || clazz == SingletonStore.class) {
+ private Class> unwrapGenericClass(Type genericType) {
+ if (genericType == Factory.class || genericType == SingletonStore.class) {
Class> parameterType = ReflectionUtils.getGenericType(genericType);
- Objects.requireNonNull(parameterType, "Parameter type for '" + clazz + "' should be a concrete class");
+ Objects.requireNonNull(parameterType, "Parameter type for '" + genericType + "' should be a concrete class");
return parameterType;
}
- return clazz;
+ return InjectorUtils.convertToClass(genericType);
}
private List getDependencies(Class> clazz) {
- Instantiation> instantiation = new StandardInjectionProvider().safeGet(clazz);
+ Resolution> instantiation = new StandardInjectionProvider().safeGet(clazz);
return instantiation == null ? null : formatInjectionDependencies(instantiation);
}
@@ -150,22 +150,22 @@ public class DrawDependency implements ToolTask {
* @param injection the injection whose dependencies should be formatted
* @return list of dependencies in a friendly format
*/
- private List formatInjectionDependencies(Instantiation> injection) {
- List descriptions = injection.getDependencies();
- List result = new ArrayList<>(descriptions.size());
- for (DependencyDescription dependency : descriptions) {
+ private List formatInjectionDependencies(Resolution> injection) {
+ List dependencies = injection.getDependencies();
+ List result = new ArrayList<>(dependencies.size());
+ for (ObjectIdentifier dependency : dependencies) {
Class> annotation = getRelevantAnnotationClass(dependency.getAnnotations());
if (annotation != null) {
result.add("@" + annotation.getSimpleName());
} else {
- Class> clazz = unwrapGenericClass(dependency.getType(), dependency.getGenericType());
+ Class> clazz = unwrapGenericClass(dependency.getType());
result.add(mapToSuper(clazz).getSimpleName());
}
}
return result;
}
- private static Class extends Annotation> getRelevantAnnotationClass(Annotation[] annotations) {
+ private static Class extends Annotation> getRelevantAnnotationClass(List annotations) {
for (Annotation annotation : annotations) {
if (ANNOTATION_TYPES.contains(annotation.annotationType())) {
return annotation.annotationType();
diff --git a/src/test/java/tools/utils/InjectorUtils.java b/src/test/java/tools/utils/InjectorUtils.java
index 6b5d510e..27150125 100644
--- a/src/test/java/tools/utils/InjectorUtils.java
+++ b/src/test/java/tools/utils/InjectorUtils.java
@@ -1,9 +1,11 @@
package tools.utils;
-import ch.jalu.injector.handlers.instantiation.DependencyDescription;
-import ch.jalu.injector.handlers.instantiation.Instantiation;
+import ch.jalu.injector.context.ObjectIdentifier;
+import ch.jalu.injector.handlers.instantiation.Resolution;
import ch.jalu.injector.handlers.instantiation.StandardInjectionProvider;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
@@ -22,15 +24,37 @@ public final class InjectorUtils {
* @return the class' dependencies, or null if no instantiation method found
*/
public static Set> getDependencies(Class> clazz) {
- Instantiation> instantiation = new StandardInjectionProvider().safeGet(clazz);
+ Resolution> instantiation = new StandardInjectionProvider().safeGet(clazz);
if (instantiation == null) {
return null;
}
Set> dependencies = new HashSet<>();
- for (DependencyDescription description : instantiation.getDependencies()) {
- dependencies.add(description.getType());
+ for (ObjectIdentifier description : instantiation.getDependencies()) {
+ dependencies.add(convertToClass(description.getType()));
}
return dependencies;
}
+ /**
+ * Returns the given type as a {@link Class}.
+ *
+ * @param type the type to convert
+ * @return class corresponding to the provided type
+ */
+ public static Class> convertToClass(Type type) {
+ if (type instanceof Class>) {
+ return (Class>) type;
+ } else if (type instanceof ParameterizedType) {
+ Type rawType = ((ParameterizedType) type).getRawType();
+ if (rawType instanceof Class>) {
+ return (Class>) rawType;
+ } else {
+ throw new IllegalStateException("Got raw type '" + rawType + "' of type '"
+ + rawType.getClass() + "' for genericType '" + type + "'");
+ }
+ }
+ Class> typeClass = type == null ? null : type.getClass();
+ throw new IllegalStateException("Unknown type implementation '" + typeClass + "' for '" + type + "'");
+ }
+
}
diff --git a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml
index 57154c7f..7bcf0b77 100644
--- a/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml
+++ b/src/test/resources/fr/xephi/authme/settings/commandconfig/commands.complete.yml
@@ -25,6 +25,10 @@ onSessionLogin:
welcome:
command: 'msg %p Session login!'
executor: CONSOLE
+onFirstLogin:
+ give_money:
+ command: 'pay %p 30'
+ executor: CONSOLE
onUnregister: {}
onLogout:
announce: