docs: @Nonnull
    public ELoginResult loginUser (@Nullable final IUser aUser,
                                   @Nullable final String sPlainTextPassword,
                                   @Nullable final Iterable <String> aRequiredRoleIDs)
    {
      if (aUser == null)
        return ELoginResult.USER_NOT_EXISTING;
  
      final String sUserID = aUser.getID ();
  
      // Deleted user?
      if (aUser.isDeleted ())
      {
        AuditHelper.onAuditExecuteFailure ("login", sUserID, "user-is-deleted");
        return _onLoginError (sUserID, ELoginResult.USER_IS_DELETED);
      }
  
      // Disabled user?
      if (aUser.isDisabled ())
      {
        AuditHelper.onAuditExecuteFailure ("login", sUserID, "user-is-disabled");
        return _onLoginError (sUserID, ELoginResult.USER_IS_DISABLED);
      }
  
      // Check the password
      final UserManager aUserMgr = PhotonSecurityManager.getUserMgr ();
      if (!aUserMgr.areUserIDAndPasswordValid (sUserID, sPlainTextPassword))
      {
        AuditHelper.onAuditExecuteFailure ("login", sUserID, "invalid-password");
        return _onLoginError (sUserID, ELoginResult.INVALID_PASSWORD);
      }
  
      // Are all roles present?
      if (!SecurityHelper.hasUserAllRoles (sUserID, aRequiredRoleIDs))
      {
        AuditHelper.onAuditExecuteFailure ("login", sUserID, "user-is-missing-required-roles", aRequiredRoleIDs);
        return _onLoginError (sUserID, ELoginResult.USER_IS_MISSING_ROLE);
      }
  
      // Check if the password hash needs to be updated
      final String sExistingPasswordHashAlgorithmName = aUser.getPasswordHash ().getAlgorithmName ();
      final String sDefaultPasswordHashAlgorithmName = GlobalPasswordSettings.getPasswordHashCreatorManager ()
                                                                             .getDefaultPasswordHashCreatorAlgorithmName ();
      if (!sExistingPasswordHashAlgorithmName.equals (sDefaultPasswordHashAlgorithmName))
      {
        // This implicitly implies using the default hash creator algorithm
        // This automatically saves the file
        aUserMgr.setUserPassword (sUserID, sPlainTextPassword);
        if (LOGGER.isInfoEnabled ())
          LOGGER.info ("Updated password hash of " +
                       _getUserIDLogText (sUserID) +
                       " from algorithm '" +
                       sExistingPasswordHashAlgorithmName +
                       "' to '" +
                       sDefaultPasswordHashAlgorithmName +
                       "'");
      }
  
      boolean bLoggedOutUser = false;
      LoginInfo aInfo;
      m_aRWLock.writeLock ().lock ();
      try
      {
        if (m_aLoggedInUsers.containsKey (sUserID))
        {
          // The user is already logged in
          if (isLogoutAlreadyLoggedInUser ())
          {
            // Explicitly log out
            logoutUser (sUserID);
  
            // Just a short check
            if (m_aLoggedInUsers.containsKey (sUserID))
              throw new IllegalStateException ("Failed to logout '" + sUserID + "'");
  
            AuditHelper.onAuditExecuteSuccess ("logout-in-login", sUserID);
            bLoggedOutUser = true;
          }
          else
          {
            // Error: user already logged in
            AuditHelper.onAuditExecuteFailure ("login", sUserID, "user-already-logged-in");
            return _onLoginError (sUserID, ELoginResult.USER_ALREADY_LOGGED_IN);
          }
        }
  
        // Update user in session
        final InternalSessionUserHolder aSUH = InternalSessionUserHolder._getInstance ();
        if (aSUH._hasUser ())
        {
          // This session already has a user
          if (LOGGER.isWarnEnabled ())
            LOGGER.warn ("The session user holder already has the user ID '" +
                         aSUH._getUserID () +
                         "' so the new ID '" +
                         sUserID +
                         "' will not be set!");
          AuditHelper.onAuditExecuteFailure ("login", sUserID, "session-already-has-user");
          return _onLoginError (sUserID, ELoginResult.SESSION_ALREADY_HAS_USER);
        }
  
        aInfo = new LoginInfo (aUser, ScopeManager.getSessionScope ());
        m_aLoggedInUsers.put (sUserID, aInfo);
        aSUH._setUser (this, aUser);
      }
      finally
      {
        m_aRWLock.writeLock ().unlock ();
      }
  
      if (LOGGER.isInfoEnabled ())
        LOGGER.info ("Logged in " +
                     _getUserIDLogText (sUserID) +
                     (isAnonymousLogging () ? "" : " with login name '" + aUser.getLoginName () + "'"));
      AuditHelper.onAuditExecuteSuccess ("login-user", sUserID, aUser.getLoginName ());
  
      // Execute callback as the very last action
      m_aUserLoginCallbacks.forEach (aCB -> aCB.onUserLogin (aInfo));
  
      return bLoggedOutUser ? ELoginResult.SUCCESS_WITH_LOGOUT : ELoginResult.SUCCESS;
    }