# TemporaryAccountPool.java

    /*
    ** Module   : TemporaryAccountPool.java
    ** Abstract : temporary account pool implementation.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -------------------------------Description------------------------------------
    ** 001 MAG 20131212 Created initial version.
    ** 002 CA  20140206 Read the temporary client pool size and timeout here, as these are specific
    **                  to this class.
    ** 003 MAG 20140217 Fixed shut down code.
    ** 004 IAS 20160403 Replaced ConcurrentLinkedQueue with LinkedBlockingQueue, removed unnecessary
    **                  synchronization
    ** 005 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 006 DDF 20230620 Replaced static initialization of values from the directory configuration with
    **                  a method called at server bootstrap.
    ** 007 GBB 20250404 Fixed the order or reading configs and initializing.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.main;

    import java.util.concurrent.*;
    import java.util.logging.*;

    import com.goldencode.p2j.admin.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.logging.CentralLogger;
    import com.goldencode.p2j.util.*;
    import com.goldencode.util.*;

    /**
     * A temporary account pool implementation.
     * On server start-up all temporary accounts which belongs to "temps" group are deleted.
     * New accounts are created and added to pool. The new accounts are created disabled.
     * Because the password is stored in directory as a hash string, the clear text password
     * is kept in memory (pool) inside a <code>TemporaryAccount</code> structure.
     * When an item is requested from pool the item is removed form pool and the associated
     * account is enabled before returned to caller.
     * If the pool is empty the current thread will wait for an specified amount of time to
     * get an item returned to pool by other threads.
     * When an item is returned to pool first the associated account is disabled than the item
     * is enqueued at the tail of the pool and other threads waiting for an item are notified.
     * In order to manipulate user accounts special permissions are mandatory.
     * A dedicated worker thread is spawned on start-up and is used to manage user accounts at
     * runtime. This daemon thread is running on server security context having admin permissions.
     *
     * @author mag
     *
     */
    public class TemporaryAccountPool
    {
       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(TemporaryAccountWorker.class.getName());

       /** Temporary accounts group name */
       private static final String TEMP_GROUP = "temps";

       /** Random generator length */
       public static final int GEN_LENGTH = 16;

       /** Random generator digits */
       public static final int GEN_DIGITS = 8;

       /** Random generator symbols */
       public static final int GEN_SYMBOLS = 4;

       /** FIFO Pool */
       private static final BlockingQueue<TemporaryAccount> pool =
                new LinkedBlockingQueue<>();

       /** Pool Manager instance */
       private static volatile TemporaryAccountPool poolManager;

       /** Task used to change the user account status */
       private static TemporaryAccountWorker poolTask;

       /** poolTask guard */
       private static final Object taskLock = new Object();

       /** A daemon worker thread having a security context which allow to change user account. */
       private static AssociatedThread worker;

       /** Timeout in milliseconds. */
       private static int timeout = 30000;

       /** Pool size */
       private static int poolSize = 8;

       /**
        * Initialize pool. This happens on server start-up.
        * All temporary accounts are deleted a new accounts are created.
        */
       private TemporaryAccountPool()
       {
          // Pool size
          poolSize = ConfigItem.TEMP_CLIENT_POOL_SIZE.read(poolSize, Utils.DirScope.SERVER);

          // Pool timeout
          timeout = ConfigItem.TEMP_CLIENT_TIMEOUT.read(timeout, Utils.DirScope.SERVER);

          AdminServerImpl.setTargetLive(true);
          // Create TEMP_GROUP group if not exists
          createGroupIfNotExists();
          // Delete temporary accounts
          deleteTemporaryAccounts();
          // Create new temporary accounts
          createTemporaryAccounts();
          // Test if pool has been created
          if (pool.isEmpty())
          {
             String message = "Admin privileges are mandatory to create temporary account pool.";
             LOG.logp(Level.WARNING,
                      "TemporaryAccountPool",
                      "static initializer",
                      message);
          }
          else
          {
             // Refresh target
             AdminServerImpl.targetRefresh();
          }
          // Start worker thread
          startUp();
       }

       /**
        * Create new temporary accounts
        */
       private static void createTemporaryAccounts()
       {
          for (int i = 0;i < poolSize;i++)
          {
             TemporaryAccount account = createUser(false);
             if (account != null)
             {
                pool.add(account);
             }
          }
       }

       /**
        * Delete all temporary accounts.
        * Temporary accounts belong to TEMP_GROUP group.
        */
       private static void deleteTemporaryAccounts()
       {
          TaggedName[] users = AdminServerImpl.listGroupUsers(TEMP_GROUP);
          if (users != null)
          {
             for (TaggedName name : users)
             {
                UserDef user = AdminServerImpl.getUser(name.getName());
                if (user != null)
                {
                   AdminServerImpl.deleteUser(user.subjectId);
                }
             }
          }
       }

       /**
        * Check if temporary group exists. If not create group.
        */
       private static void createGroupIfNotExists()
       {
          GroupDef groupDef = AdminServerImpl.getGroup(TEMP_GROUP);
          if (groupDef == null)
          {
             groupDef = new GroupDef();
             groupDef.subjectId = TEMP_GROUP;
             groupDef.description = "all temps";
             AdminServerImpl.addGroup(groupDef);
          }
       }

       /**
        * Create a temporary user.
        *
        * @param   enabled
        *          Initial account status.
        *
        * @return  Created user account.
        */
       static TemporaryAccount createUser(boolean enabled)
       {
          TemporaryAccount account = null;
          // Generate random values for account
          String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
          String password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
          // Fill account structure
          UserDef user = new UserDef();
          user.enabled = enabled;
          user.protect = true;
          user.person = "chui";
          user.subjectId = subject;
          user.password = HashPassword.hashPassword(password);
          user.mode = 1;
          user.groups = new String[] { TEMP_GROUP };
          // Create temporary account
          if (AdminServerImpl.addUser(user, null))
          {
             account = new TemporaryAccount(user.subjectId, password);
          }
          // Return created account.
          return account;
       }

       /**
        * Create and return the pool manager instance.
        * This is a singleton design pattern.
        *
        * @return  An instance of TemporaryAccountPool
        */
       public static TemporaryAccountPool getInstance()
       {
          return ManagerHolder.INSTANCE;
       }

       /**
        * Startup worker thread.
        */
       private static void startUp()
       {
          synchronized (taskLock)
          {
             poolTask = new TemporaryAccountWorker();
             worker = new AssociatedThread(poolTask);
             worker.start();
          }
       }

       /**
        * Shut down worker thread.
        */
       public static void shutDown()
       {
          synchronized (taskLock)
          {
             poolTask.terminate();
             // wait for thread to die
             try
             {
                worker.join(5000);
             }
             catch (InterruptedException e)
             {
                LOG.logp(Level.WARNING,
                         "TemporaryAccountPool",
                         "shutDown",
                         "InterruptedException!",
                         e);
             }
          }
       }

       /**
        * Get and remove an item from pool.
        * Enable the temporary account before return to caller.
        * The item should be returned back to pool when no longer used.
        * On empty pool wait for an amount of time to get an item from pool.
        * If no items are available after waiting a null value is returned.
        *
        * @return  The item at the head of pool or null if pool is empty and
        *          no item is available after waiting an amount of time.
        */
       public TemporaryAccount poll()
       {
          long timeout = TemporaryAccountPool.timeout;
          TemporaryAccount item = null;
          try {
             item = TemporaryAccountPool.pool.poll(timeout, TimeUnit.MILLISECONDS);
             if (item != null)
             {
                // Enable account
                enableAccount(item, true);
             }
          }
          catch (InterruptedException e)
          {
             LOG.logp(Level.WARNING,
                      "TemporaryAccountPool.poll()",
                      "",
                      "InterruptedException!",
                      e);
          }
          return item;
       }

       /**
        * Disable temporary account and return the item to pool.
        * The item is enqueued at the tail of the pool.
        * Notify other threads waiting for a pool item.
        *
        * @param   item
        *          A TemporaryAccount item returned to pool.
        */
       public void tail(TemporaryAccount item)
       {
             // Change user account status
             enableAccount(item, false);
             TemporaryAccountPool.pool.add(item);
       }

       /**
        * Enable / disable temporary account.
        *
        * @param   item
        *          Account pool item.
        * @param   enabled
        *          <code>true</code> enable account.
        *          <code>false</code> disable account.
        */
       private void enableAccount(TemporaryAccount item, boolean enabled)
       {
          // Change user account status
          Runnable core = new UpdateAccountTask(item, enabled);
          synchronized (taskLock)
          {
             TemporaryAccountPool.poolTask.execute(core);
          }
       }

       /**
        * Helper clasee for the lazt initialization of the TemporaryAccountPool instance
        */
       private static class ManagerHolder
       {
          /** TemporaryAccountPool instance will be initialized on the first access */
          public static final TemporaryAccountPool INSTANCE = new TemporaryAccountPool();
       }
    }