# AdminServerImpl.java

    /*
    ** Module   : AdminServerImpl.java
    ** Abstract : Implementation of the Administration Server APIs.
    **
    ** Copyright (c) 2007-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- --------------------------Description--------------------------------
    ** 001 NVS 20070312   @32375 Created the first version of the file.
    ** 002 GES 20080318   @37532 Disabled troublesome code that needs to be
    **                           modified to avoid translating chars when
    **                           writing XML.
    ** 003 NVS 20090304   @41404 Minor change to logging.
    ** 004 NVS 20090311   @41521 Reworked to implement AdminExports interface,
    **                           initial implementation.
    ** 005 NVS 20090318   @41635 Added targetRefresh(). This method and the
    **                           existing setTargetLive() return the current
    **                           cache serial number.
    ** 006 NVS 20090326   @41693 Added getErrors() method and fixed groups
    **                           retrieval.
    ** 007 NVS 20090428   @42032 Added peekMessage() method.
    ** 008 NVS 20090429   @42075 Added isMaster() method.
    **                           Implemented listProcesses(), checkProcess(),
    **                           getProcess(), setProcess(), addProcess(),
    **                           deleteProcess(), cloneProcess(),
    ** 009 NVS 20090505   @42099 Implemented certificate related APIs.
    ** 010 NVS 20090506   @42136 Implemented the user account related APIs.
    ** 011 NVS 20090518   @42242 Implemented some more certificate related APIs.
    ** 012 NVS 20090520   @42417 Added listUserGroups() API implementation.
    ** 013 NVS 20090525   @42447 Added APIs for custom account extensions support.
    ** 014 NVS 20090602   @42545 Partially implemented getAdminDef() method.
    ** 015 CA  20090603   @42555 Set methods addUser and addProcess as static.
    ** 016 NVS 20090604   @42604 Added a form of cloneUser() that assigns new
    **                           password.
    ** 017 NVS 20090609   @42621 Started Console group of APIs with
    **                           consReloadDirectory() method.
    ** 018 CA  20090615   @42691 Modified getAdminDef to call its SecurityAdmin
    **                           counterpart (existing code was moved here).
    ** 019 GES 20090617   @42753 Added getSessionList().
    ** 020 GES 20090619   @42779 Added backing support for terminateSession().
    ** 021 NVS 20090626   @42966 Implemented the first group of ACL related APIs -
    **                           listAclInstances() and the like
    ** 022 NVS 20090701   @42997 Implemented the ACL getters group of ACL related
    **                           APIs.
    ** 023 NVS 20090703   @43040 Implemented the addAcl() method.
    ** 024 NVS 20090703   @43049 Implemented the setAcl() method.
    ** 025 NVS 20090706   @43066 Implemented APIs moveAcl(), deleteAcl(),
    **                           cloneAcl() and resequenceAcl().
    ** 026 GES 20090721   @43300 Added getRecordLockList().
    ** 027 ECF 20090802   @43502 Completed implementation of getRecordLockList().
    ** 028 NVS 20090807   @43559 Added two forms of listServers() method.
    ** 029 NVS 20090812   @43608 Fixed a NPE in listServers() method.
    ** 030 NVS 20090816   @43647 Added shutdown() method implementation.
    ** 031 NVS 20091007   @44120 Implemented two flavors of getProfile() APIs.
    ** 032 NVS 20091116   @44343 Cleanup.
    ** 033 NVS 20091117   @44414 Safe directory reload support APIs added.
    ** 034 NVS 20091118   @44426 Safe directory backup support API added.
    ** 035 SVL 20091126   @44447 Implemented APIs for locking database records.
    ** 036 SIY 20091216   @44515 Added support for pending refresh handling.
    ** 037 SIY 20100419   @44826 Added DirectoryService accessor with admin rights
    **                           check. Added method which allows refreshing of
    **                           plugins with specified name.
    ** 038 SIY 20100422   @44832 Fixed Javadocs.
    ** 039 SVL 20100916          Added listAuthPlugins function.
    ** 040 SVL 20100920          Added bulkAuthModeUpdate function.
    ** 041 SVL 20110619          Password is preserved during user cloning if
    **                           newPassword is null into cloneUser(String,
    **                           String, boolean, byte[]).
    ** 042 SVL 20111128          Implemented customer libs management.
    ** 043 ECF 20131028          Import change.
    ** 044 EVL 20160222          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 045 SBI 20170225          Moved administration profile type, access control list type and
    **                           record lock type to their own modules respectively.
    **     SBI 20170920          Fixed getDatabases() to get a list of primary databases, added
    **                           getReportParametersMap.
    ** 046 CA  20180605          Added program traces in the session and record locks views.
    ** 047 ECF 20200906          New ORM implementation.
    ** 048 IAS 20210505          Changed exception thrown on failed shutdown.
    ** 049 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 050 CA  20230803          Invoked DatabaseManager.autoConnect() on setTargetLive(boolean).
    ** 051 GBB 20230825          Adding new methods to support OS user screens and dialogs.
    ** 052 TJD 20240123          Java 17 compatibility updates
    ** 053 ICP 20240705          Fixed method of obtaining the class loader.
    ** 054 RAA 20240424          Added tenant managing methods.
    **     RAA 20240513          Made updateTenant capable of receiving an array of fields and values.
    **     RAA 20240517          Fixed list display in updateTenant javadoc.
    **     RAA 20240604          Changed return type of addTenant and changeTenantStatus. Changed to a Map
    **                           parameter in updateTenant function.
    **     RAA 20240610          Added listTenants function.
    **     RAA 20240627          Added functions for adding/deleting a single database of a tenant.
    **     RAA 20240702          Refactored parameters of the addTenant function.
    **     RAA 20240715          Clarified the type of database name occurrences.
    **     RAA 20240726          Simplified the number of parameters from addDatabaseToTenant.
    ** 055 GBB 20241010          Adding getAppserverList & getAppserverAgents methods.
    ** 056 RAA 20241011          Added tenant description logic.
    **     RAA 20241014          Expanded setTargetLive() with databaseAutoConnect and restCall parameters.
    ** 057 RAA 20241106          Added tenant info parameter to addTenant().
    ** 058 RAA 20241118          Added listTenant() method for listing a single tenant.
    ** 059 OM  20241128          Multi-tenant runtime support: selected the proper persistence context, eventually
    **                           based on [sharedDb] parameter.
    ** 060 RNC 20250109          Added convertProgramTrace() and getSessionById() methods.
    ** 061 OM  20250128          Added admin REST API for sec_auth domains. Added sink for collecting and
    **                           reporting errors occurred during REST invocations.
    ** 062 GBB 20250403          getAppserverList to support new multi-session appserver.
    */

    /*
    ** 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.admin;

    import java.io.*;
    import java.security.*;
    import java.security.cert.*;
    import java.text.*;
    import java.util.*;
    import java.util.regex.*;
    import java.util.stream.Stream;

    import com.fasterxml.jackson.databind.*;
    import com.fasterxml.jackson.databind.node.*;
    import com.goldencode.p2j.main.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.lock.*;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.orm.TenantManager.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.Utils.DirScope;
    import com.goldencode.p2j.admin.shared.ReportParameters;
    import com.goldencode.p2j.classloader.*;
    import com.goldencode.p2j.directory.*;
    import com.goldencode.p2j.util.appserver.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Implements AdminExports interface.
     * Methods of this interface are exported through the NET package and
     * are used by administration clients.
     *
     * @author  NVS
     * @version 2.0
     */
    public class AdminServerImpl
    {
       /** Context-local global data area for admin session state */
       private static final ContextLocal<WorkArea> workArea = new ContextLocal<WorkArea>()
       {
          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          protected synchronized WorkArea initialValue()
          {
             return new WorkArea();
          }
       };

       /** Logger */
       private static CentralLogger LOG = CentralLogger.get(AdminServerImpl.class);

       /** instance of the security manager */
       private static com.goldencode.p2j.security.SecurityManager sm = null;

       /** Maximum timeout (in milliseconds) before give up on acquiring a lock.*/
       private static final int LOCK_ACQUIRE_MAXIMUM_WAIT_TIME = 30000;

       /** Interval (in milliseconds) between lock acquisition attempts. */
       private static final int LOCK_ACQUIRE_INTERVAL = 1000;

       /** Defines the empty locks array. */
       private static final RecordInfo[] EMPTY_LOCK_LIST = new RecordInfo[0];

       /** Global refresh counter. */
       private static Integer refreshGlobal = Integer.valueOf(0);

       /** Synchronization object for refresh counters. */
       private static final Object refreshMutex = new Object();

       /** The report settings template to build the node id of the given report parameter. */
       private static final MessageFormat REPORT_SETTINGS = new MessageFormat("report-settings/{0}");

       /**
        * Initializes this component.
        */
       public static void initialize()
       {
          sm = com.goldencode.p2j.security.SecurityManager.getInstance();
       }

       //***********************************************************************
       // Admin Session Control API
       //***********************************************************************

       /**
        * Returns the server's name.
        *
        * @return   This server's name or {@code null}
        */
       public static String getServerName()
       {
          WorkArea wa = workArea.get();
          return sm.adminGetServerName(wa.stayCurrent);
       }

       /**
        * Tells whether the server can modify the directory. Only the master
        * server can. The others have read-only access to the directory.
        *
        * @return   {@code true} if it can
        */
       public static boolean isMaster()
       {
          WorkArea wa = workArea.get();
          return wa.sa.isMaster();
       }

       /**
        * Sets the live server's directory as the target.
        *
        * @param    stayCurrent
        *           if {@code true}, all operations affect the most current
        *           generation of the security cache, otherwise they affect the
        *           generation attached to the admin session
        *
        * @return   security cache generation serial number
        */
       public static int setTargetLive(boolean stayCurrent)
       {
          return setTargetLive(stayCurrent, true, false);
       }

       /**
        * Sets the live server's directory as the target.
        *
        * @param    stayCurrent
        *           if {@code true}, all operations affect the most current
        *           generation of the security cache, otherwise they affect the
        *           generation attached to the admin session
        * @param    databaseAutoConnect
        *           {@code true} if database auto connecting should be included in this function execution,
        *           {@code false} otherwise.
        * @param    restCall
        *           {@code true} if this function was called from a REST environment (e.g. the tenant API),
        *           {@code false} otherwise.
        *
        * @return   security cache generation serial number
        */
       public static int setTargetLive(boolean stayCurrent, boolean databaseAutoConnect, boolean restCall)
       {
          WorkArea wa = workArea.get();
          if (restCall && wa.initialized)
          {
             return 0;
          }

          if (databaseAutoConnect)
          {
             DatabaseManager.autoConnect();
          }

          // mark session as targeted
          wa.targeted = true;
          wa.stayCurrent = stayCurrent;

          // bind session to the directory service
          wa.ds = DirectoryService.getInstance();
          if (wa.ds.bind())
          {
             wa.bound = true;
          }

          wa.sa = new SecurityAdmin(wa.ds);
          wa.initialized = true;

          return sm.adminGetCacheSerial(wa.stayCurrent);
       }

       /**
        * Sets the specified directory file as the offline target. The file can't
        * be the live directory file.
        *
        * @param    name
        *           name of a directory file to use as the target
        *
        * @param    create
        *           {@code true} means a new directory file should be
        *           created, otherwise the named directory file should exist.
        *
        * @return   {@code true} if the new target was set successfully
        */
       public static boolean setTargetFile(String name, boolean create)
       {
          // TO-DO: implement later
          return false;
       }

       /**
        * Refreshes the current security cache if the target is the live directory
        * and only if there were changes performed since the last call to the
        * targetRefresh().
        *
        * @return   Security cache generation number. The same number as before
        *           means the cache was not refreshed.
        */
       public static int targetRefresh()
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.canRefresh())
          {
             return -1;
          }

          // refresh the cache
          int newSerial = sm.adminRefresh(wa.freshMessages);
          if (newSerial == -1)
          {
             return -1;
          }

          wa.resetCounters();

          message(new String[] {"Security Cache generation " + newSerial +
                                " created."});
          return newSerial;
       }

       /**
        * Returns an array of messages accumulated since the previous call or all
        * messages since the last message cleanup. Messages can be retrieved
        * multiple times. New messages are moved to the saved messages category
        * after the first retrieval.
        *
        * @param    all
        *           if {@code true}, then old and new messages are retrieved,
        *           otherwise new messages only
        *
        * @return   array of messages, that can be empty
        */
       public static String[][] getMessages(boolean all)
       {
          List<String[]> msgs = new LinkedList<String[]>();
          WorkArea wa = workArea.get();

          // fetch old messages first, if requested
          if (all)
          {
             msgs.addAll(wa.savedMessages);
          }

          // fetch new messages unconditionally
          msgs.addAll(wa.freshMessages);

          // age fresh messages
          wa.savedMessages.addAll(wa.freshMessages);
          wa.freshMessages.clear();

          // return messages
          return msgs.toArray(new String[][] {});
       }

       /**
        * Returns the most recent server message without changing its state on the
        * server.
        *
        * @return   message or {@code null} if nothing is generated
        */
       public static String peekMessage()
       {
          WorkArea wa = workArea.get();

          // check the fresh messages first
          if (!wa.freshMessages.isEmpty())
          {
             String[] last = wa.freshMessages.getLast();
             return last[last.length - 1];
          }
          // check the old messages next
          else if (!wa.savedMessages.isEmpty())
          {
             String[] last = wa.savedMessages.getLast();
             return last[last.length - 1];
          }

          return null;
       }

       /**
        * Clears the old messages saved on the server, if any. New messages are
        * not affected and will be returned with the next call to getMessages().
        */
       public static void clearMessages()
       {
          WorkArea wa = workArea.get();
          wa.savedMessages.clear();
       }

       /**
        * Returns the configuration information about the administration interface
        * itself. This information should be used to get access to the custom
        * admin extensions
        *
        * @return   container with admin related configuration information
        */
       public static AdminDef getAdminDef()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.getAdminDef();
       }

       /**
        * Check if there is pending security cache refresh.
        *
        * @return   {@code true} if refresh is pending and {@code false}
        *           is there is no pending refresh or current user has not enough
        *           rights to perform it.
        */
       public static boolean isRefreshPending()
       {
          return workArea.get().isRefreshPending();
       }

       /**
        * Check if current user can perform a refresh.
        *
        * @return   {@code true} user has enough rights to perform refresh.
        */
       public static boolean canRefresh()
       {
          return workArea.get().sa.canRefresh();
       }

       /**
        * Returns the admin permissions profile for an arbitrary set of resource
        * instance names.
        *
        * @param    name
        *           array of admin resource instance names to query
        *
        * @return   permissions for the given names or {@code null}
        */
       public static AdminProfile[] getProfile(String[] name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.getProfile(name);
       }

       /**
        * Returns the admin permissions profile for an arbitrary set of
        * permissions for a resource instance name.
        *
        * @param    name
        *           admin leaf resource instance name to query
        *
        * @param    perm
        *           array of permissions to query
        *
        * @return   permissions for the given name or {@code null}
        */
       public static AdminProfile[] getProfile(String name, int[] perm)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.getProfile(name, perm);
       }

       /**
        * Backup the directory into a new file.
        *
        * @param    newfile
        *           name of a backup file to create
        *
        * @return   {@code true} if backup was successful.
        */
       public static boolean backupDirectory(String newfile)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check the file name
          if (newfile.trim().length() == 0)
          {
             message (new String[] {"file name can't be blank"});
          }

          File file = new File(newfile);
          if (file.exists())
          {
             if (file.isDirectory())
             {
                message (new String[] {"file name is an existing directory"});
             }
             else
             {
                message (new String[] {"file already exists"});
             }

             return false;
          }

          int rc = wa.ds.backupDirectory(newfile);
          String err = null;

          switch (rc)
          {
             case 0:
                return true;

             case DirectoryService.ERR_SAVE_NOT_SUPPORTED:
                err = "Directory backup is not supported in this environment";
                break;

             case DirectoryService.ERR_CANT_WRITE:
                err = "File write operation failed";
                break;

             case DirectoryService.ERR_CANT_RENAME:
                err = "Failure during file rename";
                break;

             case DirectoryService.ERR_EDITING_IS_ACTIVE:
                err = "Directory editing batch is open; can't proceed";
                break;

             case DirectoryService.ERR_CANT_LOCK_DIRECTORY:
                err = "Directory lock can't be obtained";
                break;

             case DirectoryService.ERR_FATAL:
                err = "Unrecoverable error in directory backend";
                break;
          }

          message(new String[] {err});
          return false;
       }

       /**
        * Lock directory for rereading from the file.
        *
        * @param    seconds
        *           Seconds to wait before lock will expire.
        *
        * @return   {@code true} if lock was successful.
        */
       public static boolean lockForRereadDir(int seconds)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          boolean rc = wa.ds.lockForRefresh(seconds);
          if (!rc)
          {
             message (new String[] {"directory lock failed"});
          }

          return rc;
       }

       /**
        * Reread the directory file into the backend's cache after obtaining
        * a refresh lock.
        *
        * @return   error code. Following error codes are possible:
        *           <ol>
        *           <li>ERR_NO_LOCK - is returned when no lock is present</li>
        *           <li>ERR_LOCK_EXPIRED - is returned if lock is present
        *               but it is expired or belongs to other thread.</li>
        *           <li>ERR_CANT_REFRESH_BACKEND - is returned when
        *               backend can't update directory.</li>
        *           <li>ERR_ILLEGAL - is returned when no rights etc</li>
        *           </ol>
        */
       public static int rereadDirectory()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return AdminConstants.ERR_ILLEGAL;
          }

          int rc = wa.ds.refresh();
          String err = null;

          switch (rc)
          {
             case 0:
                return 0;

             case AdminConstants.ERR_NO_LOCK:
                err = "Directory was not locked";
                break;

             case AdminConstants.ERR_LOCK_EXPIRED:
                err = "Lock expired. Try again";
                break;

             case AdminConstants.ERR_CANT_REFRESH_BACKEND:
                err = "Problem reading the directory file";
                break;
          }

          message (new String[] {err});
          return rc;
       }

       /**
        * Cancel (possibly) planned refreshing operation.
        */
       public static void cancelRereadDir()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return;
          }

          wa.ds.cancelRefresh();
       }

       /**
        * Creates a report of all current user and process sessions.
        *
        * @return   Session descriptors suitable for reporting purposes or
        *           {@code null} if insufficient rights exist to obtain the
        *           list.
        */
       public static SessionInfo[] getSessionList()
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          SessionInfo[] sessions = wa.sa.getSessionList();

          if (sessions != null)
          {
             setStacktraces(sessions);
          }

          return sessions;
       }

       /**
        * Creates a report based on the session id provided. In the situation where a single report is
        * needed, this function is faster compared to calling {@link #getSessionList()} and then manually
        * selecting the report, because internally it uses a cache map.
        *
        * @param    id
        *           The context's session id of the {@link SecuritySession}.
        *           It can be retrieved through {@link SessionToken#getSessionID()}.
        *
        * @return   The session descriptor suitable for reporting purposes.
        *           {@code null} if insufficient rights exist to obtain it
        *           or the session was not found.
        */
       public static SessionInfo getSessionById(int id)
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          SessionInfo session = wa.sa.getSessionById(id);
          if (session != null)
          {
             setStacktraces(new SessionInfo[]{session});
          }

          return session;
       }

       /**
        * Forcibly terminate the specified session.
        *
        * @param    sid
        *           Session identifier.
        */
       public static void terminateSession(int sid)
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted)
          {
             throw new IllegalStateException("No target instance.");
          }
          else if (!wa.sa.adminAccess())
          {
             throw new SecurityException("Administrative access denied.");
          }

          wa.sa.killSession(sid);
       }

       /**
        * Converts a list of traces into one single JSON node.
        *
        * @param    traces
        *           The stacktrace which will be serialized.
        * @param    node
        *           A JSON array node which will hold the serialized traces.
        */
       public static void convertProgramTrace(StacktraceInfo[] traces, ArrayNode node)
       {
          int length = traces == null ? 0 : traces.length;
          if (length == 0 || node == null)
          {
             return;
          }

          for (int i = 0; i < traces.length; i++)
          {
             StacktraceInfo trace = traces[i];
             node.add(trace.program + "." + trace.method + "(line:" + trace.line + ")");
          }
       }

       /**
        * Creates a report of all record locks for the server.
        *
        * @return   Record lock descriptors suitable for reporting purposes or
        *           {@code null} if insufficient rights exist to obtain the
        *           list.
        */
       public static RecordLockInfo[] getRecordLockList()
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          RecordLockInfo[] locks = DatabaseManager.getAllRecordLockInfo();

          if (locks == null)
          {
             return null;
          }

          Set<Thread> threads = getAllConversationThreads();

          Map<String, StacktraceInfo[]> programTraces = getProgramTraceByThreadNameMap(threads);
          Map<String, String[]> javaTraces= getJavaTraceByThreadNameMap(threads);

          for (RecordLockInfo lock : locks)
          {
             SessionToken[] lockers = lock.getLockers();
             StacktraceInfo[][] traces = new StacktraceInfo[lockers.length][];
             String[][] jtraces = new String[lockers.length][];
             int i = 0;
             for (SessionToken locker : lockers)
             {
                String key = String.format("Conversation [%08X:%s]",
                                           locker.getSessionID(),
                                           locker.getSubject());
                traces[i] = programTraces.get(key);
                jtraces[i] = javaTraces.get(key);
             }

             lock.setStacktraces(traces);
             lock.setJavaStacktraces(jtraces);
          }

          return locks;
       }

       /**
        * Get the list of primary databases managed by this P2J server.
        *
        * @return   Names of primary databases managed by this P2J server.
        */
       public static String[] getDatabases()
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          List<Database> databases = DatabaseManager.getManagedDatabases();

          List<String> list = new ArrayList<String>();
          for (Database database : databases)
          {
             if (database.isPrimary())
             {
                list.add(database.getName());
             }
          }
          Collections.sort(list);

          return list.toArray(new String[list.size()]);
       }

       /**
        * Get the list of tables of the given database.
        *
        * @param    database
        *           Target database.
        *
        * @return   See above. Names of the tables of the given database.
        */
       public static String[] getDatabaseTables(String database)
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          List<String> tableNames = DatabaseManager.getDatabaseTables(new Database(database));
          if (tableNames == null)
          {
             return null;
          }

          Collections.sort(tableNames);
          return tableNames.toArray(new String[tableNames.size()]);
       }

       /**
        * Lock the specified records. Locks are obtained in a single batch. Note
        * that you can lock non-existing records (in order to create them
        * externally).
        * <p>
        * You can unlock records using {@link #unlockRecords()}.
        *
        * @param    records
        *           Records to lock.
        *
        * @return   {@code true} if records were successfully locked.
        */
       public static boolean lockRecords(RecordInfo[] records)
       {
          WorkArea wa = workArea.get();

          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          for (RecordInfo record : records)
          {
             Database database = new Database(record.getDatabaseName());
             Persistence persistence = PersistenceFactory.getInstance(database);

             RecordIdentifier<String> recId = new RecordIdentifier<>(record.getTableName(), record.getRecordID());

             // try to obtain lock for periodically until the lock is obtained or
             // a reasonable timeout occurs
             long startTime = System.currentTimeMillis();
             while(true)
             {
                try
                {
                   persistence.lock(LockType.EXCLUSIVE_NO_WAIT, recId, true, record.isSharedDb());

                   // store locked record
                   if (wa.lockedRecords == null)
                   {
                      wa.lockedRecords = new HashSet<>();
                   }
                   wa.lockedRecords.add(record);
                }
                catch (LockUnavailableException e)
                {
                   long time = System.currentTimeMillis() - startTime;

                   if (time > LOCK_ACQUIRE_MAXIMUM_WAIT_TIME)
                   {
                      message(new String[]{"Cannot acquire lock on " + recId});
                      unlockRecords();
                      return false;
                   }

                   try
                   {
                      Thread.sleep(LOCK_ACQUIRE_INTERVAL);
                   }
                   catch (InterruptedException ie)
                   {
                      // suppress
                   }

                   // try one more time
                   continue;
                }
                break;
             }
          }

          return true;
       }

       /**
        * Gets records that have been previously locked in the given context using {@link #lockRecords}.
        * While the records were locked by the admin UI, they could be changed by the external tools,
        * so the state of these records into the Hibernate cache is refreshed before records have
        * been collected.
        *
        * @return   Records that have been previously locked in the given context.
        */
       public static RecordInfo[] getLockRecords()
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return EMPTY_LOCK_LIST;
          }

          if (wa.lockedRecords == null || wa.lockedRecords.isEmpty())
          {
             return EMPTY_LOCK_LIST;
          }

          RecordInfo[] records = new RecordInfo[wa.lockedRecords.size()];
          int index = 0;
          for (RecordInfo record : wa.lockedRecords)
          {
             Database database = new Database(record.getDatabaseName());
             Persistence persistence = PersistenceFactory.getInstance(database);
             String entityName = DatabaseManager.getEntityNameByTableName(database, record.getTableName());

             RecordIdentifier<String> recordId = new RecordIdentifier<>(entityName, record.getRecordID());
             try
             {
                persistence.quickLoad(recordId, true, record.isSharedDb());
             }
             catch (PersistenceException e)
             {
                LOG.warning("Error while reloading records", e);
             }
             records[index++] = record;
          }

          return records;
       }

       /**
        * Unlock the records which has been previously locked in the given context
        * using {@link #lockRecords}. While the records were locked by the admin
        * UI, they could be changed by the external tools, so the state of these
        * records into the Hibernate cache is refreshed before unlocking.
        *
        * @return   {@code true} if records were successfully unlocked.
        */
       public static boolean unlockRecords()
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          if (wa.lockedRecords == null || wa.lockedRecords.isEmpty())
          {
             return true;
          }

          for (RecordInfo record : wa.lockedRecords)
          {
             Database database = new Database(record.getDatabaseName());
             Persistence persistence = PersistenceFactory.getInstance(database);
             String entityName = DatabaseManager.getEntityNameByTableName(database, record.getTableName());

             RecordIdentifier<String> recordId = new RecordIdentifier<>(entityName, record.getRecordID());
             try
             {
                // refresh Hibernate cache
                persistence.quickLoad(recordId, true, record.isSharedDb());
             }
             catch (PersistenceException e)
             {
                LOG.warning("Error while reloading records", e);
             }

             recordId = new RecordIdentifier<>(record.getTableName(), record.getRecordID());
             try
             {
                persistence.lock(LockType.NONE, recordId, true, record.isSharedDb());
             }
             catch (LockUnavailableException e)
             {
                // shouldn't be raised
             }
          }
          wa.lockedRecords.clear();

          return true;
       }

       /**
        * Shuts the server down.
        *
        * @return   {@code true} if successful
        */
       public static boolean shutdown()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // shut the server down
          try
          {
             SessionManager.get().shutdown();
          }
          catch (SecurityException e)
          {
             return false;
          }

          return true;
       }

       //
       // General Server Configuration API
       //

       /**
        * Returns an array of resource plugin names.
        *
        * @return   All defined resource plugin names
        */
       public static String[] listResourceNames()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.listResourceNames();
       }

       /**
        * Returns the registered resource type name for the given resource plugin.
        *
        * @param    name
        *           plugin name
        *
        * @return   registered resource type name or {@code null} if no match
        */
       public static String getResourceType(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.getResourceType(name);
       }

       /**
        * Modifies resource name definitions.
        *
        * @param    additions
        *           array of resource names to add or {@code null} if no
        *           additions are specified
        * @param    deletions
        *           array of resource names to delete or {@code null} if no
        *           deletions are specified
        * @return   boolean success indicator
        */
       public static boolean changeResourceNames(String[] additions, String[] deletions)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Gets authentication configuration, which includes authentication mode,
        * password aging and security tracing level.
        *
        * @return   an instance with all current authentication values
        */
       public static AuthConfigDef getAuthConfig()
       {
          // TO-DO:
          return new AuthConfigDef();
       }

       /**
        * Sets authentication configuration from the specified instance, which
        * includes authentication mode, password aging and security tracing level.
        *
        * @param    authConfig
        *           an instance loaded with the required configuration
        * @return   boolean success indicator
        */
       public static boolean setAuthConfig(AuthConfigDef authConfig)
       {
          // TO-DO:
          return false;
       }

       /**
        * Returns an array of defined holidays.
        *
        * @return   All defined holidays
        */
       public static date[] listHolidays()
       {
          // TO-DO:
          return new date[] {};
       }

       /**
        * Modifies defined holidays.
        *
        * @param    additions
        *           array of dates to add or {@code null} if no additions are
        *           specified
        * @param    deletions
        *           array of dates to delete or {@code null} if no deletions
        *           are specified
        * @return   boolean success indicator
        */
       public static boolean changeHolidays(date[] additions, date[] deletions)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Returns audit info.
        *
        * @return   AuditDef The instance of AuditDef.
        */
       public static AuditDef getAudit()
       {
          // TO-DO:
          return new AuditDef();
       }

       /**
        * Sets audit info.
        *
        * @param    audit
        *           The instance of AuditDef.
        *
        * @return   boolean indicating the success of this operation
        */
       public static boolean setAudit(AuditDef audit)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       //
       // Accounts Management API
       //

       /**
        * Returns an array of defined group names.
        *
        * @return   All defined group accounts or {@code null}
        */
       public static TaggedName[] listGroups()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of group accounts
          return wa.sa.listGroups();
       }

       /**
        * Returns the group definition for the specified group name.
        *
        * @param    name
        *           group name
        *
        * @return   group definition or {@code null} if no such group
        */
       public static GroupDef getGroup(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the group definition
          return wa.sa.getGroup(name);
       }

       /**
        * Check the group account existence.
        *
        * @param    name
        *           group name
        *
        * @return   minimum group information (name, description) or
        *                     {@code null} if the named group does not exist
        */
       public static TaggedName checkGroup(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the group definition
          GroupDef gd = wa.sa.getGroup(name);
          if (gd == null)
          {
             return null;
          }

          return new TaggedName(gd.subjectId, gd.description);
       }

       /**
        * Changes the group definition for the named group.
        *
        * @param    group
        *           group definition
        *
        * @return   {@code true} if successfully changed the group
        */
       public static boolean setGroup(GroupDef group)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the group definition
          return wa.incrementCounters(wa.sa.setGroup(group));
       }

       /**
        * Adds new group account definition.
        *
        * @param    group
        *           group definition for a new account
        *
        * @return   {@code true} if successfully added the group
        */
       public static boolean addGroup(GroupDef group)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // add the group definition
          return wa.incrementCounters(wa.sa.addGroup(group));
       }

       /**
        * Deletes a group account definition. The account is deleted. All
        * references to this group are deleted from the user accounts and from
        * ACLs.
        *
        * @param    name
        *           group name for the group being deleted
        *
        * @return   {@code true} if successfully deleted the group
        */
       public static boolean deleteGroup(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // delete the group definition
          return wa.incrementCounters(wa.sa.deleteGroup(name));
       }

       /**
        * Clones an existing group account into a new one. Users of the source
        * group become also the users of the clone. ACLs are modified to give the
        * source group's rights to the clone, if this option is requested.
        *
        * @param    nameFrom
        *           group name for the source group
        *
        * @param    nameTo
        *           group name for the clone
        *
        * @param    acls
        *           {@code true} tells to clone ACLs as well
        *
        * @return   {@code true} if successfully cloned the group
        */
       public static boolean cloneGroup(String nameFrom, String nameTo,
                                        boolean acls)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // get and return the array of group accounts
          return wa.incrementCounters(wa.sa.cloneGroup(nameFrom, nameTo, acls));
       }

       /**
        * Enumerates the users of a named group.
        *
        * @param    name
        *           group name
        *
        * @return   array of user names, that may be empty, or {@code null} if
        *           there is no such group
        */
       public static TaggedName[] listGroupUsers(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of group accounts
          return wa.sa.listGroupUsers(name);
       }

       /**
        * Changes the group by adding and/or removing users.
        *
        * @param    name
        *           group name
        *
        * @param    additions
        *           array of user names to be added to the group; may be empty
        *
        * @param    deletions
        *           array of user names to be excluded from the group; may be
        *           empty
        *
        * @return   {@code true} if successfully changed the group
        */
       public static boolean changeGroupUsers(String name, String[] additions,
                                              String[] deletions)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // get and return the array of group accounts
          return wa.incrementCounters(wa.sa.changeGroupUsers(name, additions, deletions));
       }

       /**
        * Returns an array of defined user account names and their owners.
        *
        * @return   All defined user accounts or {@code null}
        */
       public static TaggedName[] listUsers()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of user accounts
          return wa.sa.listUsers();
       }

       /**
        * Check the user account existence.
        *
        * @param    name
        *           user account name
        *
        * @return   minimum user account information (name, person) or
        *                     {@code null} if the named account does not exist
        */
       public static TaggedName checkUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get the user account definition
          UserDef ud = wa.sa.getUser(name);
          if (ud == null)
          {
             return null;
          }

          return new TaggedName(ud.subjectId, ud.person);
       }

       /**
        * Returns the user account definition for the specified account name.
        *
        * @param    name
        *           user account name
        *
        * @return   user account definition or {@code null} if no such user
        */
       public static UserDef getUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the user account definition
          return wa.sa.getUser(name);
       }

       /**
        * Changes the user account definition for the named account. Optional
        * fields are synchronized with the directory. Password aging information
        * is set only if a password is set.
        *
        * @param    user
        *           user account definition
        *
        * @return   {@code true} if successfully changed the account
        */
       public static boolean setUser(UserDef user)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the user account definition
          return wa.incrementCounters(wa.sa.setUser(user, null, false));
       }

       /**
        * Changes the user account definition for the named account. Optional
        * fields are synchronized with the directory. Password aging information
        * is set only if a password is set.
        *
        * @param    user
        *           user account definition
        *
        * @param    ext
        *           optional extension container for custom account extensions
        *
        * @return   {@code true} if successfully changed the account
        */
       public static boolean setUser(UserDef user, Serializable ext)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the user account definition
          return wa.incrementCounters(wa.sa.setUser(user, ext, true));
       }

       /**
        * Adds new user account definition.
        *
        * @param    user
        *           user account definition
        *
        * @param    ext
        *           optional extension container for custom account extensions
        *
        * @return   {@code true} if successfully added the account
        */
       public static boolean addUser(UserDef user, Serializable ext)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // add the user account definition
          return wa.incrementCounters(wa.sa.addUser(user, ext));
       }

       /**
        * Deletes a user account definition. The account is deleted. All
        * references to this account are deleted from the ACLs, but the associated
        * user certificate is left untouched.
        *
        * @param    name
        *           user account name being deleted
        *
        * @return   {@code true} if successfully deleted the account
        */
       public static boolean deleteUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          return wa.incrementCounters(wa.sa.deleteUser(name));
       }

       /**
        * Clones an existing user account into a new one, password included. ACLs
        * are modified to give the source user rights to the clone, if this option
        * is requested.
        *
        * @param    nameFrom
        *           user account name for the source
        *
        * @param    nameTo
        *           user account name for the clone
        *
        * @param    acls
        *           {@code true} tells to clone ACLs as well
        *
        * @return   {@code true} if successfully cloned the user account
        */
       public static boolean cloneUser(String nameFrom, String nameTo,
                                       boolean acls)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          return wa.incrementCounters(wa.sa.cloneUser(nameFrom, nameTo, acls, false, null));
       }

       /**
        * Clones an existing user account into a new one and assigns the provided
        * password to the clone. ACLs are modified to give the source user rights
        * to the clone, if this option is requested.
        *
        * @param    nameFrom
        *           user account name for the source
        *
        * @param    nameTo
        *           user account name for the clone
        *
        * @param    acls
        *           {@code true} tells to clone ACLs as well
        *
        * @param    newPassword
        *           a replacement password for the clone; {@code null} if the
        *           password should be preserved
        *
        * @return   {@code true} if successfully cloned the user account
        */
       public static boolean cloneUser(String nameFrom, String nameTo,
                                       boolean acls, byte[] newPassword)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          boolean replacePassword = newPassword != null;

          return wa.incrementCounters(wa.sa.cloneUser(nameFrom,
                                                      nameTo,
                                                      acls,
                                                      replacePassword,
                                                      newPassword));
       }

       /**
        * Enumerates the groups the named user is part of.
        *
        * @param    name
        *           user account name
        *
        * @return   array of tagged group names, that may be empty, or
        *                     {@code null} if there is no such user etc
        */
       public static TaggedName[] listUserGroups(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.listUserGroups(name);
       }

       /**
        * Returns an array of defined OS user names and their hashed passwords.
        *
        * @return   All defined OS users or <code>null</code>
        */
       public static TaggedName[] listOsUsers()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.listOsUsers();
       }

       /**
        * Check the user account existence.
        *
        * @param    name
        *           OS user account name
        *
        * @return   Minimum OS user account information  or <code>null</code> if the account doesn't exist
        */
       public static TaggedName checkOsUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get the user account definition
          OsUserDef ud = wa.sa.getOsUser(name);
          if (ud == null)
          {
             return null;
          }

          return new TaggedName(ud.subjectId, null);
       }

       /**
        * Returns the OS user account definition for the specified account name.
        *
        * @param    name
        *           OS user account name
        *
        * @return   OS user account definition or <code>null</code> if no such user
        */
       public static OsUserDef getOsUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the OS user account definition
          return wa.sa.getOsUser(name);
       }

       /**
        * Adds new OS user account definition.
        *
        * @param    user
        *           OS user account definition
        *
        * @return   {@code true} if successfully added the account
        */
       public static boolean addOsUser(OsUserDef user)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // add the user account definition
          return wa.incrementCounters(wa.sa.addOsUser(user));
       }

       /**
        * Changes the user account definition for the OS user account. Optional
        * fields are synchronized with the directory. Password aging information
        * is set only if a password is set.
        *
        * @param    user
        *           OS user account definition
        *
        * @return   {@code true} if successfully changed the account
        */
       public static boolean setOsUser(OsUserDef user)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the OS user definition
          return wa.incrementCounters(wa.sa.setOsUser(user));
       }

       /**
        * Returns an array of defined FWD user names, associated with the OS user.
        *
        * @param    osUser
        *           The OS user name
        *
        * @return   The associated FWD users or <code>null</code>
        */
       public static TaggedName[] listAssociatedFwdUsers(String osUser)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return wa.sa.listAssociatedFwdUsers(osUser);
       }

       /**
        * Deletes an OS user account definition. The account is deleted. All
        * references to this account are deleted from the ACLs, but the associated
        * user certificate is left untouched.
        *
        * @param    name
        *           OS user account name being deleted
        *
        * @return   {@code true} if successfully deleted the account
        */
       public static boolean deleteOsUser(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          return wa.incrementCounters(wa.sa.deleteOsUser(name));
       }

       /**
        * Returns an array of defined process names with their descriptions.
        *
        * @return   All defined processes accounts or {@code null}
        */
       public static TaggedName[] listProcesses()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of process accounts
          return wa.sa.listProcesses();
       }

       /**
        * Returns the process definition for the specified process name.
        *
        * @param    name
        *           process name
        *
        * @return   process definition or {@code null} if no such process
        */
       public static ProcessDef getProcess(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the process definition
          return wa.sa.getProcess(name);
       }

       /**
        * Check the process account existence.
        *
        * @param    name
        *           process account name
        *
        * @return   minimum process account information (name, description) or
        *                     {@code null} if the named account does not exist
        */
       public static TaggedName checkProcess(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the process definition
          ProcessDef pd = wa.sa.getProcess(name);
          if (pd == null)
          {
             return null;
          }

          return new TaggedName(pd.subjectId, pd.description);
       }

       /**
        * Changes the process definition for the named process.
        *
        * @param    process
        *           process definition
        *
        * @return   {@code true} if successfully changed the process
        */
       public static boolean setProcess(ProcessDef process)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the process definition
          return wa.incrementCounters(wa.sa.setProcess(process, null, false));
       }

       /**
        * Changes the process definition for the named process.
        *
        * @param    process
        *           process definition
        *
        * @param    ext
        *           optional extension container for custom account extensions
        *
        * @return   {@code true} if successfully changed the process
        */
       public static boolean setProcess(ProcessDef process, Serializable ext)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // change the process definition
          return wa.incrementCounters(wa.sa.setProcess(process, ext, true));
       }

       /**
        * Adds new process account definition.
        *
        * @param    process
        *           process definition for a new account
        *
        * @param    ext
        *           optional extension container for custom account extensions
        *
        * @return   {@code true} if successfully added the process
        */
       public static boolean addProcess(ProcessDef process, Serializable ext)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // add the process definition
          return wa.incrementCounters(wa.sa.addProcess(process, ext));
       }

       /**
        * Deletes a process account definition. The account is deleted. All
        * references to this process are deleted from the ACLs, but the associated
        * process certificate is left untouched.
        *
        * @param    name
        *           process account name being deleted
        *
        * @return   {@code true} if successfully deleted the process
        */
       public static boolean deleteProcess(String name)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // delete the process definition
          return wa.incrementCounters(wa.sa.deleteProcess(name));
       }

       /**
        * Clones an existing process account into a new one. Master server flag is
        * always false for the clone. ACLs are modified to give the source
        * process' rights to the clone, if this option is requested.
        *
        * @param    nameFrom
        *           process name for the source
        *
        * @param    nameTo
        *           process name for the clone
        *
        * @param    acls
        *           {@code true} tells to clone ACLs as well
        *
        * @return   {@code true} if successfully cloned the process
        */
       public static boolean cloneProcess(String nameFrom, String nameTo,
                                          boolean acls)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // clone process
          return wa.incrementCounters(wa.sa.cloneProcess(nameFrom, nameTo, acls));
       }

       /**
        * Returns an array of defined Certificate Authority certificates with
        * their distinctive names.
        *
        * @param    unusedOnly
        *           if {@code true}, lists unused certificates only,
        *           otherwise all certificates
        *
        * @return   All defined CA certificates or {@code null}
        */
       public static TaggedName[] listAuthCerts(boolean unusedOnly)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of CA certificates
          return wa.sa.listCerts(true, unusedOnly);
       }

       /**
        * Returns an array of defined peer (non-CA) certificates with their
        * distinctive names.
        *
        * @param    unusedOnly
        *           if {@code true}, lists unused certificates only,
        *           otherwise all certificates
        *
        * @return   All defined peer certificates or {@code null}
        */
       public static TaggedName[] listPeerCerts(boolean unusedOnly)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of peer certificates
          return wa.sa.listCerts(false, unusedOnly);
       }

       /**
        * Returns an array of user account names with their description for users
        * sharing this certificate.
        *
        * @param    alias
        *           certificate alias name
        *
        * @return   all user accounts referring to this certificate or
        *                     {@code null}
        */
       public static TaggedName[] getCertUsers(String alias)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of accounts
          return wa.sa.getCertUsers(alias);
       }

       /**
        * Returns an array of process account names with their description for
        * processes sharing this certificate.
        *
        * @param    alias
        *           certificate alias name
        *
        * @return   all process accounts referring to this certificate or
        *                     {@code null}
        */
       public static TaggedName[] getCertProcesses(String alias)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of accounts
          return wa.sa.getCertProcesses(alias);
       }

       /**
        * Returns an array of all account names with their description for
        * accounts sharing this certificate.
        *
        * @param    alias
        *           certificate alias name
        *
        * @return   all accounts referring to this certificate or {@code null}
        */
       public static TaggedName[] getCertAccounts(String alias)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of accounts
          return wa.sa.getCertAccounts(alias);
       }

       /**
        * Returns a DER-encoded certificate like those found in *.PEM files.
        *
        * @param    alias
        *           the alias of the certificate to get
        *
        * @return   the encoded certificate or {@code null}
        */
       public static CertDef getCert(String alias)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the certificate
          return wa.sa.getCert(alias);
       }

       /**
        * Given a certificate alias, it will compute the certificate signing chain.
        *
        * @param    alias
        *           The certificate from which to start chain building
        *
        * @return   a list of (alias, DN) entries, where root certificate is on first position
        */
       public static TaggedName[] getCertificateHierarchy(String alias)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          ArrayList<TaggedName> list = new ArrayList<TaggedName>();

          do
          {
             CertDef cdef = wa.sa.getCert(alias);

             X509Certificate xcert = CertificateUtils.loadCertificate(cdef.certificate);
             // if certificate could not be loaded, add a message
             if (xcert == null)
             {
                list.add(0, new TaggedName(alias, CertificateUtils.CERT_BROKEN));
                continue;
             }

             String DN = xcert.getSubjectX500Principal().getName();

             list.add(0, new TaggedName(alias, DN));

             // get parent certificate alias, only if this certificate is not
             // self-signed
             if (CertificateUtils.isSelfSigned(xcert))
             {
                // found a self-signed certificate, end search
                break;
             }
             else
             {
                String issuerDN = xcert.getIssuerX500Principal().getName();
                // find issuer certificate
                alias = wa.sa.searchCert(issuerDN);
                if (alias == null)
                {
                   // certificate chain is broken
                   list.add(0, new TaggedName("*** broken chain ***", ""));
                }
             }
          }
          while (alias != null);

          return list.toArray(new TaggedName[0]);

       }

       /**
        * Validate the given certificate, by its PEM data.
        *
        * @param    cert
        *           The PEM representation of the certificate
        *
        * @return   a value representing certificate validity state.
        */
       public static int validateCertificate(CertDef cert)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return -1;
          }

          // check and reassemble the certificate encoding
          int len = cert.certificate.length - 2;
          if (len < 1                               ||
              !cert.certificate[0].equals(CertificateUtils.CERT_BEG) ||
              !cert.certificate[len + 1].equals(CertificateUtils.CERT_END))
          {
             return CertificateUtils.CERT_INVALID_BROKEN;
          }

          for (int i = 1; i < len + 1; i++)
          {
             if ((i != len && cert.certificate[i].length() != CertificateUtils.SPLIT_CHUNK) ||
                 (i == len && cert.certificate[i].length() > CertificateUtils.SPLIT_CHUNK))
             {
                return CertificateUtils.CERT_INVALID_BROKEN;
             }
          }

          X509Certificate x509 = CertificateUtils.loadCertificate(cert.certificate);
          // certificate could not be instantiated
          if (x509 == null)
          {
             return CertificateUtils.CERT_INVALID_BROKEN;
          }

          boolean selfSigned = CertificateUtils.isSelfSigned(x509);

          // check if Peer certificate is self-signed
          if (!cert.authority && selfSigned)
          {
             // peer certificates can't be self signed
             return CertificateUtils.CERT_INVALID_PEER_SELF_SIGNED;
          }

          // get the certificate's signer
          String signerAlias = selfSigned ? cert.alias
                                          : wa.sa.searchCert(x509.getIssuerX500Principal()
                                                               .getName());
          if (signerAlias == null)
          {
             // could not find signer
             return CertificateUtils.CERT_INVALID_SIGNER_NOT_FOUND;
          }

          CertDef signer = selfSigned ? cert : wa.sa.getCert(signerAlias);

          if (!signer.authority)
          {
             // special validation of Peer and CA certificates - must be signed by
             // a CA certificate
             if (!cert.authority)
             {
                // Peer certificate is not signed by a CA certificate
                return CertificateUtils.CERT_INVALID_PEER_NOT_SIGNED_BY_CA;
             }
             else
             {
                // CA certificate must be signed by a CA certificate
                return CertificateUtils.CERT_INVALID_CA_NOT_SIGNED_BY_CA;
             }
          }

          X509Certificate s509 = CertificateUtils.loadCertificate(signer.certificate);

          // check certificate signature
          try
          {
             x509.verify(s509.getPublicKey());
          }
          catch (InvalidKeyException xik)
          {
             return CertificateUtils.CERT_INVALID_CORRUPTED1;
          }
          catch (NoSuchProviderException xnsp)
          {
             return CertificateUtils.CERT_INVALID_CORRUPTED2;
          }
          catch (NoSuchAlgorithmException xnsa)
          {
             return CertificateUtils.CERT_INVALID_CORRUPTED3;
          }
          catch (SignatureException xs)
          {
             return CertificateUtils.CERT_INVALID_CORRUPTED4;
          }
          catch (CertificateException xc)
          {
             return CertificateUtils.CERT_INVALID_CORRUPTED5;
          }

          // check certificate period validity
          try
          {
             x509.checkValidity();
          }
          catch (CertificateExpiredException e)
          {
             return CertificateUtils.CERT_INVALID_EXPIRED;
          }
          catch (CertificateNotYetValidException e)
          {
             return CertificateUtils.CERT_INVALID_NOT_YET_VALID;
          }

          return CertificateUtils.CERT_VALID;
       }

       /**
        * Get a map of the owner or issuer Distinctive Name properties.
        *
        * @param    cd
        *           The certificate definition from which the info will be extracted.
        * @param    owner
        *           {@code true} if the owner property should be retrieved;
        *           {@code false} for the issuer property.
        *
        * @return   A map with certificate properties or {@code null} if the certificate PEM data
        *           could not be loaded.
        */
       public static Map<String, String> getCertificateDNProperties(CertDef cd, boolean owner)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return CertificateUtils.getCertificateDNProperties(cd, owner);
       }

       /**
        * Get the value of the specified property, from the Distinctive Name entry associated with
        * the owner or issuer.
        *
        * @param    cd
        *           The certificate definition from which the info will be extracted.
        * @param    prop
        *           The property to query.
        * @param    owner
        *           {@code true} if the owner property should be retrieved;
        *           {@code false} for the issuer property.
        *
        * @return   The value for the specified property.
        */
       public static String getCertificateDNProperty(CertDef cd, String prop, boolean owner)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return CertificateUtils.getCertificateDNProperty(cd, prop, owner);
       }

       /**
        * Loads the X509 certificate from the specified definition and returns
        * a map with properties for:
        * <ul>
        *    <li>
        *       {@link CertificateUtils#CERT_OWNER} - this property will keep a Map of Distinctive
        *       Name properties for the owner
        *    </li>
        *    <li>
        *       {@link CertificateUtils#CERT_ISSUER} - this property will keep a Map of Distinctive
        *       Name properties for the issuer
        *    </li>
        *    <li>
        *       {@link CertificateUtils#CERT_VALIDITY_NOT_BEFORE} - the date from which this
        *       certificate starts to be valid.
        *    </li>
        *    <li>
        *       {@link CertificateUtils#CERT_VALIDITY_NOT_AFTER} - the date from which this
        *       certificate's validity period ends.
        *    </li>
        * </ul>
        *
        * @param    cd
        *           The certificate definition from which the info will be extracted.
        *
        * @return   A map with certificate properties or {@code null} if the certificate PEM data
        *           could not be loaded.
        */
       public static Map<String, Object> getCertificateProperties(CertDef cd)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return CertificateUtils.getCertificateProperties(cd);
       }

       /**
        * Check if the given certificate is self-signed.
        *
        * @param    cert
        *           The certificate which should be checked.
        *
        * @return   {@code true} if this certificate is self-signed
        */
       public static Boolean isSelfSigned(CertDef cert)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          X509Certificate x509 = CertificateUtils.loadCertificate(cert.certificate);

          return x509 != null && CertificateUtils.isSelfSigned(x509);
       }

       /**
        * Replaces an existing certificate with the given one.
        *
        * @param    cert
        *           encoded certificate
        *
        * @return   {@code true} if replaced, {@code false} if errors
        */
       public static boolean setCert(CertDef cert)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // replace the certificate
          return wa.incrementCounters(wa.sa.setCert(cert));
       }

       /**
        * Adds a new certificate according to its definition.
        *
        * @param    cert
        *           encoded certificate
        *
        * @return   {@code true} if added, {@code false} if errors
        */
       public static boolean addCert(CertDef cert)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // add the certificate
          return wa.incrementCounters(wa.sa.addCert(cert));
       }

       /**
        * Changes the certificate's alias.
        *
        * @param    aliasFrom
        *           current certificate's alias
        *
        * @param    aliasTo
        *           new certificate's alias
        *
        * @return   {@code true} if changed, {@code false} if errors
        */
       public static boolean moveCert(String aliasFrom, String aliasTo)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // rename certificate
          return wa.incrementCounters(wa.sa.moveCert(aliasFrom, aliasTo));
       }

       /**
        * Searches the CA directory for an alias of the certificate by the
        * distinctive name of the subject.
        *
        * @param    dn
        *           distinctive name
        *
        * @return   alias of the certificate if exists, or {@code null}.
        */
       public static String searchCert(String dn)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // find the certificate
          return wa.sa.searchCert(dn);
       }

       /**
        * Deletes an existing certificate conditionally or unconditionally.
        *
        * @param    alias
        *           the alias of the certificate being deleted
        *
        * @param    force
        *           if {@code true}, the certificate is deleted even if in
        *           use
        *
        * @return   {@code true} if deleted, {@code false} if errors
        *
        */
       public static boolean deleteCert(String alias, boolean force)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // delete the certificate
          return wa.incrementCounters(wa.sa.deleteCert(alias, force));
       }

       //
       // Access Control List Management API
       //

       /**
        * Locates all instances of ACLs in the directory and returns their names.
        * The shared instance has an empty name.
        * <p>
        * ACL instances are the directory branches that store ACLs. The shared
        * instance is the one found under <code>/security/acl</code> branch.
        * Private instances are found under
        * <code>/security/acls/<i>name</i></code> branches.
        *
        * @return   array of ACL instance names; may be empty
        */
       public static String[] listAclInstances()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // list ACL instances
          return wa.sa.listAclInstances();
       }

       /**
        * Locates all instances of ACLs in the directory and returns their names
        * for those instances which have ACLs defined for the specified resource
        * type. The shared instance has an empty name.
        * <p>
        * ACL instances are the directory branches that store ACLs. The shared
        * instance is the one found under <code>/security/acl</code> branch.
        * Private instances are found under
        * <code>/security/acls/<i>name</i></code> branches.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @return   array of ACL instance names; may be empty
        */
       public static String[] listAclInstances(String resourceType)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // list ACL instances
          return wa.sa.listAclInstances(resourceType);
       }

       /**
        * Lists resource names for which the specified ACL instance has ACLs
        * defined.
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one
        *
        * @return   array of resource type names; may be empty
        */
       public static String[] listInstanceAcls(String instanceName)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // list ACLs for the instance
          return wa.sa.listInstanceAcls(instanceName);
       }

       /**
        * Gets the full ACL definition information from the directory for the
        * specified ACL instance and resource type.
        *
        * @param    resourceType
        *           name of the resource of interest; can't be {@code null}
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one;
        *           can't be {@code null}
        *
        * @return   ACL definition or {@code null}
        */
       public static AclDef getAcl(String resourceType, String instanceName)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
          }

          if (instanceName == null)
          {
             message (new String[] {"instance name can't be omitted"});
          }

          if (resourceType == null || instanceName == null)
          {
             return null;
          }

          // list ACLs for the instance
          return wa.sa.getAcl(resourceType, instanceName);
       }

       /**
        * Gets the full ACL definition information from the directory for the
        * specified ACL instance or all of them.
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one; if
        *           {@code null}, all ACLs are returned and some may be
        *           combined
        *
        * @return   an array of ACL definitions, one per resource type and instance,
        *           or {@code null}
        */
       public static AclDef[] getAllAcls(String instanceName)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // list ACLs for the instance
          return wa.sa.getAcls(instanceName);
       }

       /**
        * Gets the combined ACL definition information from the directory for the
        * specified resource type. The combination is done according to the
        * regular server's rules taking the private instance for the live server
        * and the shared one.
        *
        * @param    resourceType
        *           name of the resource of interest; can't be {@code null}
        *
        * @return   ACL definition or {@code null}
        */
       public static AclDef getEffectiveAcls(String resourceType)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return null;
          }

          // list the combined ACLs for the resource
          return wa.sa.getAcl(resourceType, null);
       }

       /**
        * Adds new set of ACLs based on the contents of the given ACL definition.
        * Both the target ACL instance and resource part will be created if they
        * don't exist.
        *
        * @param    resourceType
        *           name of the resource to add the ACL for
        *
        * @param    acl
        *           ACL definition, that should come fully defined, including the
        *           instance name.
        *
        * @param    subjMap
        *           a map to translate numeric subject IDs back to names
        *
        * @return   {@code true} if added, {@code false} if errors
        */
       public static boolean addAcl(String resourceType, Acl acl,
                                    Map<Integer, String> subjMap)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (acl == null)
          {
             message (new String[] {"ACL can't be omitted"});
             return false;
          }

          if (acl.origin == null)
          {
             message (new String[] {"target instance can't be omitted"});
             return false;
          }

          if (subjMap == null)
          {
             message (new String[] {"subjects map can't be omitted"});
             return false;
          }

          // add the ACL
          return wa.incrementCounters(wa.sa.addAcl(resourceType, acl, subjMap));
       }

       /**
        * Get the list of available auth plugins.
        *
        * @return  the list of available auth plugins encoded into tagged names or
        *          {@code null} if no plugins are available.
        */
       public static TaggedName[] listAuthPlugins()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // get and return the array of auth plugins
          return wa.sa.listAuthPlugins();
       }

       /**
        * Preforms bulk update of authentication settings of the accounts of the
        * specified type.
        *
        * @param  targetAccounts
        *         Type of accounts to update. <code>Account.ACC_USER</code> and
        *         <code>Account.ACC_GROUP</code> are supported.
        * @param  excludedAccounts
        *         The list of IDs of the accounts that should NOT be updated. Can
        *         be {@code null}.
        * @param  authMode
        *         Authentication mode to set.
        * @param  authPlugin
        *         Authentication plugin to set.
        *
        * @return the number of updated accounts or <code>-1</code> if an error
        *         has occured.
        */
       public static int bulkAuthModeUpdate(int targetAccounts,
                                            String[] excludedAccounts,
                                            int authMode,
                                            String authPlugin)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return -1;
          }

          // perform auth mode update
          int updateCount = wa.sa.bulkAuthModeUpdate(targetAccounts,
                                                     excludedAccounts,
                                                     authMode,
                                                     authPlugin);
          wa.incrementCounters(updateCount > 0);
          return updateCount;
       }

       /**
        * Refresh specified plugin.
        *
        * @param   name
        *          Plugin name.
        */
       public static void refreshPlugin(String name)
       {
          WorkArea wa = workArea.get();

          wa.sa.refreshPlugin(name);
       }

       /**
        * Conditionally edits the existing ACL based on the contents of the given
        * ACL and the selected conditions.
        *
        * @param    resourceType
        *           name of the resource to set the ACL for
        * @param    acl
        *           ACL definition
        * @param    subjMap
        *           a map to translate numeric subject IDs back to names; needed
        *           only when setSubjects is {@code true} and can be
        *           {@code null} all other times
        * @param    setInstance
        *           {@code true} if setting the resource instance name
        * @param    setInstanceMode
        *           {@code true} if setting the resource instance name mode
        * @param    setSubjects
        *           {@code true} if setting the subjects IDs
        * @param    setRights
        *           {@code true} if setting the rights for this ACL
        * @return   {@code true} if set, {@code false} if errors
        */
       public static boolean setAcl(String resourceType, Acl acl,
                                    Map<Integer, String> subjMap,
                                    boolean setInstance, boolean setInstanceMode,
                                    boolean setSubjects, boolean setRights)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (acl == null)
          {
             message (new String[] {"ACL can't be omitted"});
             return false;
          }

          if (acl.origin == null)
          {
             message (new String[] {"target instance can't be omitted"});
             return false;
          }

          if (subjMap == null)
          {
             message (new String[] {"subjects map can't be omitted"});
             return false;
          }

          // set the ACL
          return wa.incrementCounters(wa.sa.setAcl(resourceType, acl, subjMap,
                                               setInstance, setInstanceMode,
                                               setSubjects, setRights));
       }

       /**
        * Moves the specified ACL to another ACL instance, private or shared. The
        * target instance will be created, if it doesn't exist.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    oid
        *           ACL object ID that selects the ACL to move
        *
        * @param    newInstanceName
        *           name of the target ACL instance; empty string for the shared
        *           one
        *
        * @return   {@code true} if moved, {@code false} if errors
        */
       public static boolean moveAcl(String resourceType, String instanceName,
                                     int oid, String newInstanceName)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (instanceName == null || newInstanceName == null)
          {
             message (new String[] {"instance names can't be omitted"});
             return false;
          }

          // move the ACL to another instance
          return wa.incrementCounters(wa.sa.moveAcl(resourceType, instanceName,
                                                oid, newInstanceName));
       }

       /**
        * Moves the specified ACL to another position within the same ACL
        * instance.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    oid
        *           ACL object ID that selects the ACL to move
        *
        * @param    newOid
        *           new ACL object ID
        *
        * @return   {@code true} if moved, {@code false} if errors
        */
       public static boolean moveAcl(String resourceType, String instanceName,
                                     int oid, int newOid)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (instanceName == null)
          {
             message (new String[] {"source instance can't be omitted"});
             return false;
          }

          // move the ACL around within the instance
          return wa.incrementCounters(wa.sa.moveAcl(resourceType, instanceName,
                                                oid, newOid));
       }

       /**
        * Deletes the specified ACL from the specified ACL instance.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    oid
        *           ACL object ID that selects the ACL to delete
        *
        * @return   {@code true} if deleted, {@code false} if errors
        */
       public static boolean deleteAcl(String resourceType, String instanceName,
                                       int oid)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (instanceName == null)
          {
             message (new String[] {"source instance can't be omitted"});
             return false;
          }

          // delete the ACL
          return wa.incrementCounters(wa.sa.deleteAcl(resourceType, instanceName, oid));
       }

       /**
        * Clones the specified ACL to another position within the same ACL
        * instance.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    oid
        *           ACL object ID that selects the ACL to clone
        *
        * @param    cloneOid
        *           ACL object ID for the clone
        *
        * @return   {@code true} if cloned, {@code false} if errors
        */
       public static boolean cloneAcl(String resourceType, String instanceName,
                                      int oid, int cloneOid)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (instanceName == null)
          {
             message (new String[] {"source instance can't be omitted"});
             return false;
          }

          // clone the ACL within the instance
          return wa.incrementCounters(wa.sa.cloneAcl(resourceType, instanceName,
                                                 oid, cloneOid));
       }

       /**
        * Assigns new object IDs sequentially for all ACLs in the given ACL
        * instance and resource type.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    start
        *           the starting number
        *
        * @param    step
        *           the increment
        *
        * @return   {@code true} if success, {@code false} if errors
        */
       public static boolean resequenceAcls(String resourceType,
                                            String instanceName,
                                            int start, int step)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return false;
          }

          // check arguments
          if (resourceType == null)
          {
             message (new String[] {"resource type can't be omitted"});
             return false;
          }

          if (instanceName == null)
          {
             message (new String[] {"source instance can't be omitted"});
             return false;
          }

          // resequence the ACLs within the instance
          return wa.incrementCounters(wa.sa.resequenceAcls(resourceType, instanceName,
                                                       start, step));
       }

       //
       // Access Control List Management API extension that we may need later
       //

       /**
        * Creates an empty container for the specified ACL instance. The instance
        * should not exist.
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one
        *
        * @return   {@code true} if created, {@code false} if errors
        */
       public static boolean addAclInstance(String instanceName)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Deletes entire ACL instance from the directory. The instance should
        * exist.
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one
        *
        * @return   {@code true} if deleted, {@code false} if errors
        */
       public static boolean deleteAclInstance(String instanceName)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Deletes the resource specific part of an ACL instance from the
        * directory. The instance and the resource part should exist.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceName
        *           name of the ACL instance; empty string for the shared one
        *
        * @return   {@code true} if deleted, {@code false} if errors
        */
       public static boolean deleteAclInstance(String resourceType,
                                               String instanceName)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Clones entire ACL instance. The source ACL instance should exist. The
        * target ACL instance should not exist.
        *
        * @param    instanceFrom
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    instanceTo
        *           name of the target ACL instance; empty string for the shared
        *           one
        *
        * @return   {@code true} if cloned, {@code false} if errors
        */
       public static boolean cloneAclInstance(String instanceFrom,
                                              String instanceTo)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Clones the resource specific part of an ACL instance. The source ACL
        * instance and the resource part should exist. The target ACL instance
        * should exist. The resource part in the target ACL instance should NOT
        * exist.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceFrom
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    instanceTo
        *           name of the target ACL instance; empty string for the shared
        *           one
        *
        * @return   {@code true} if cloned, {@code false} if errors
        */
       public static boolean cloneAclInstance(String resourceType,
                                              String instanceFrom,
                                              String instanceTo)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Moves entire ACL instance. The source ACL instance should exist. The
        * target ACL instance should not exist.
        *
        * @param    instanceFrom
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    instanceTo
        *           name of the target ACL instance; empty string for the shared
        *           one
        *
        * @return   {@code true} if moved, {@code false} if errors
        */
       public static boolean moveAclInstance(String instanceFrom,
                                             String instanceTo)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       /**
        * Moves the resource specific part of an ACL instance. The source ACL
        * instance and the resource part should exist. The target ACL instance
        * should exist. The resource part in the target ACL instance should NOT
        * exist.
        *
        * @param    resourceType
        *           name of the resource of interest
        *
        * @param    instanceFrom
        *           name of the source ACL instance; empty string for the shared
        *           one
        *
        * @param    instanceTo
        *           name of the target ACL instance; empty string for the shared
        *           one
        *
        * @return   {@code true} if moved, {@code false} if errors
        */
       public static boolean moveAclInstance(String resourceType,
                                             String instanceFrom,
                                             String instanceTo)
       {
          WorkArea wa = workArea.get();
          // TO-DO:
          return wa.incrementCounters(false);
       }

       //
       // Per Server Runtime configuration: default or named
       //

       /**
        * Returns the list of all server instances found in the directory under
        * the "/server" node. The "default" instance is returned as an empty
        * string.
        *
        * @return   array of server names
        */
       public static String[] listServers()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // enumerate direct children of "/server"
          String[] servers = wa.ds.enumerateNodes("/server");

          // edit the list and hide the "default"
          for (int i = 0; i < servers.length; i++)
          {
             if (servers[i].equalsIgnoreCase("default"))
             {
                servers[i] = "";
             }
          }

          return servers;
       }

       /**
        * Returns the list of server instances under the "/server" node, that have
        * the specified container (or nested chain of) as a child.
        *
        * @param    containers
        *           one or more container names separated by "/"
        *
        * @return   array of server names holding the named containers
        */
       public static String[] listServers(String containers)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          // firstly, enumerate direct children of "/server"
          String[] servers = wa.ds.enumerateNodes("/server");

          // test servers one by one
          List<String> found = new LinkedList<String>();
          StringBuilder sb = new StringBuilder();

          for (int i = 0; i < servers.length; i++)
          {
             sb.setLength(0);
             sb.append("/server/");
             sb.append(servers[i]);
             sb.append("/");
             sb.append(containers);

             if (wa.ds.getNodeClass(sb.toString()) != null)
             {
                found.add(servers[i]);
             }
          }

          servers = found.toArray(new String[]{});

          // edit the list and hide the "default"
          for (int i = 0; i < servers.length; i++)
          {
             if (servers[i].equalsIgnoreCase("default"))
             {
                servers[i] = "";
             }
          }

          return servers;
       }

       /**
        * Returns the list of logging instances.
        *
        * @return   The list of logging instances.
        */
       public static String[] listLoggingInstances()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the logging definition by the given name.
        *
        * @param    name
        *           The given name
        *
        * @return   The target logging definition.
        */
       public static LoggingDef getLogging(String name)
       {
          // TO-DO:
          return new LoggingDef();
       }

       /**
        * Sets the logging definition.
        *
        * @param    logging
        *           The logging definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setLogging(LoggingDef logging)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the logging definition for the given name
        *
        * @param    name
        *           The given name
        * @param    logging
        *           The logging definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addLogging(String name, LoggingDef logging)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the logging definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean cloneLogging(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the logging definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean moveLogging(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the logging definition of the given name.
        *
        * @param    name
        *           The given name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deleteLogging(String name)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Returns the list of export instances.
        *
        * @return   The string array of export instances.
        */
       public static String[] listExportsInstances()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the export definition by the given export instance name.
        *
        * @param    name
        *           The given export instance name
        *
        * @return   The export definition
        */
       public static ExportsDef getExports(String name)
       {
          // TO-DO:
          return new ExportsDef();
       }

       /**
        * Sets the export definition.
        *
        * @param    logging
        *           The export definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setExports(ExportsDef logging)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the export definition for the given name
        *
        * @param    name
        *           The given name
        * @param    logging
        *           The export definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addExports(String name, ExportsDef logging)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the export definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean cloneExports(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the export definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean moveExports(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the export definition of the given name.
        *
        * @param    name
        *           The given name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deleteExports(String name)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Returns the list of startup instances.
        *
        * @return   The string array of startup instances.
        */
       public static String[] listStartupInstances()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the startup definition by the given startup instance name.
        *
        * @param    name
        *           The given startup instance name
        *
        * @return   The startup definition
        */
       public static StartupDef getStartup(String name)
       {
          // TO-DO:
          return new StartupDef();
       }

       /**
        * Sets the startup definition.
        *
        * @param    startup
        *           The startup definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setStartup(StartupDef startup)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the startup definition for the given instance name
        *
        * @param    name
        *           The given name
        * @param    startup
        *           The startup definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addStartup(String name, StartupDef startup)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the startup definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean cloneStartup(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the startup definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean moveStartup(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the startup definition of the given name.
        *
        * @param    name
        *           The given name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deleteStartup(String name)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Returns the list of persistence instances.
        *
        * @return   The string array of persistence instances.
        */
       public static String[] listPersistenceInstances()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the persistence definition by the given persistence instance name.
        *
        * @param    name
        *           The given persistence instance name
        *
        * @return   The persistence definition
        */
       public static PersistenceDef getPersistence(String name)
       {
          // TO-DO:
          return new PersistenceDef();
       }

       /**
        * Sets the persistence definition.
        *
        * @param    persistence
        *           The persistence definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setPersistence(PersistenceDef persistence)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the persistence definition for the given instance name
        *
        * @param    name
        *           The given name
        * @param    persistence
        *           The persistence definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addPersistence(String name, PersistenceDef persistence)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the persistence definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean clonePersistence(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the persistence definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean movePersistence(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the persistence definition of the given name.
        *
        * @param    name
        *           The given name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deletePersistence(String name)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Returns the list of database instances.
        *
        * @return   The string array of database instances.
        */
       public static String[] listDatabaseInstances()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the database definition by the given database instance name.
        *
        * @param    name
        *           The given database instance name
        *
        * @return   The database definition
        */
       public static DatabaseDef getDatabase(String name)
       {
          // TO-DO:
          return new DatabaseDef();
       }

       /**
        * Sets the database definition.
        *
        * @param    database
        *           The database definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setDatabase(DatabaseDef database)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the database definition for the given instance name
        *
        * @param    name
        *           The given name
        * @param    database
        *           The database definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addDatabase(String name, DatabaseDef database)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the database definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean cloneDatabase(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the database definition from the source to the target.
        *
        * @param    nameFrom
        *           The source name
        * @param    nameTo
        *           The target name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean moveDatabase(String nameFrom, String nameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the database definition of the given database name.
        *
        * @param    name
        *           The given database name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deleteDatabase(String name)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       //
       // Per Account/Server Runtime configuration: default or named
       //

       /**
        * Returns the list of runtime names.
        *
        * @return   The string array of runtime names.
        */
       public static String[] listRuntimeNames()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the list of runtime servers.
        *
        * @return   The string array of runtime servers.
        */
       public static String[] listRuntimeServers()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the list of runtime accounts names.
        *
        * @return   The string array of runtime accounts names.
        */
       public static String[] listRuntimeAccounts()
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the list of target runtime names for the given account and the given item name.
        *
        * @param    account
        *           The given account
        * @param    itemName
        *           The given item name
        *
        * @return   The string array of target runtime names
        */
       public static String[] searchRuntimeByAccount(String account, String itemName)
       {
          // TO-DO:
          return new String[] {""};
       }

       /**
        * Returns the runtime definition for the given server and account names.
        *
        * @param    serverName
        *           The given server name
        * @param    accountName
        *           The given account name
        *
        * @return   The runtime definition
        */
       public static RuntimeDef getRuntime(String serverName, String accountName)
       {
          // TO-DO:
          return new RuntimeDef();
       }

       /**
        * Sets the runtime definition.
        *
        * @param    runtime
        *           The runtime definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean setRuntime(RuntimeDef runtime)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Adds the runtime definition for the given server and account names.
        *
        * @param    serverName
        *           The given server name
        * @param    accountName
        *           The given account name
        * @param    runtime
        *           The runtime definition
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean addRuntime(String     serverName,
                                        String     accountName,
                                        RuntimeDef runtime)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Clones the runtime definition from the source to the target.
        *
        * @param    serverNameFrom
        *           The source server name
        * @param    accountNameFrom
        *           The source account name
        * @param    serverNameTo
        *           The target server name
        * @param    accountNameTo
        *           The target account name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean cloneRuntime(String serverNameFrom,
                                          String accountNameFrom,
                                          String serverNameTo,
                                          String accountNameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Moves the runtime definition from the source to the target.
        *
        * @param    serverNameFrom
        *           The source server name
        * @param    accountNameFrom
        *           The source account name
        * @param    serverNameTo
        *           The target server name
        * @param    accountNameTo
        *           The target account name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean moveRuntime(String serverNameFrom,
                                         String accountNameFrom,
                                         String serverNameTo,
                                         String accountNameTo)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Deletes the runtime definition for the given server and account names.
        *
        * @param    serverName
        *           The given server name
        * @param    accountName
        *           The given account name
        *
        * @return   boolean indicating the success of this operation.
        */
       public static boolean deleteRuntime(String serverName, String accountName)
       {
          // TO-DO:
          // TO-DO: if there are changed in directory, then return code should
          // be wrapped into wa.incrementCounters(rc)
          return false;
       }

       /**
        * Return the current {@link SecurityAdmin} instance.
        *
        * @return   the {@link WorkArea#sa} value.
        */
       public static SecurityAdmin getSecurityAdmin()
       {
          WorkArea wa = workArea.get();
          return wa.sa;
       }

       /**
        * Return the current {@link DirectoryService} instance.
        *
        * @return   the {@link WorkArea#ds} value.
        */
       public static DirectoryService getDirectoryService()
       {
          WorkArea wa = workArea.get();
          return wa.ds;
       }

       /**
        * Utility method to store server messages for later retrieval by clients.
        *
        * @param    msg
        *           The list of strings
        */
       public static void message(String... msg)
       {
          WorkArea wa = workArea.get();
          wa.freshMessages.add(msg);
       }

       /**
        * Get directory service with permission check.
        *
        * @return   The directory service
        */
       public static DirectoryService getDirectoryServiceChecked()
       {
          WorkArea wa = workArea.get();

          return (!wa.targeted || !wa.sa.adminAccess()) ? null : wa.ds;
       }

       /**
        * Get the list of registered jars (which represent customer libs).
        *
        * @return the list of registered jar, each returned
        *         <code>TaggedName</code> represents a single jar and contains jar
        *         file name, fully qualified class name of the active hook (it
        *         may be {@code null}) and value indicating whether active
        *         hook class matches stored hook class which is
        *         <code>"true"</code> if they match.
        */
       public static TaggedName[] getRegisteredJars()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          String[] jars = MultiClassLoader.listRegisteredJars();

          // get active hooks
          Map<String, String> activeHooksByJar = new HashMap<String, String>();
          for (String jar : jars)
          {
             String hook = StandardServer.getHookForJar(jar);
             activeHooksByJar.put(jar, hook);
          }

          // get stored hooks
          Map<String, String> storedHooksByJar = new HashMap<String, String>();
          String path = Utils.findDirectoryNodePath(wa.ds,
                                                    "server-hooks",
                                                    "strings",
                                                    false);
          if (path != null)
          {
             String[] hooks = wa.ds.getNodeStrings(path, "values");
             if (hooks != null && hooks.length > 0)
             {
                for (String hookClass : hooks)
                {
                   for(String jar : jars)
                   {
                      if (storedHooksByJar.containsKey(jar))
                         continue;

                      if (MultiClassLoader.handledByJar(hookClass, jar))
                      {
                         storedHooksByJar.put(jar, hookClass);
                         break;
                      }
                   }
                }
             }
          }

          // form TaggedNames
          TaggedName[] res = new TaggedName[jars.length];
          for (int i = 0; i < jars.length; i++)
          {
             String activeHook = activeHooksByJar.get(jars[i]);
             String storedHook = storedHooksByJar.get(jars[i]);

             boolean match;

             if (activeHook == null && storedHook == null)
             {
                match = true;
             }
             else if (activeHook == null || storedHook == null)
             {
                match = false;
             }
             else
             {
                match = activeHook.equals(storedHook);
             }

             res[i] = new TaggedName(jars[i], activeHook,
                                     match ? "true" : "false");
          }

          return res;
       }

       /**
        * Get the list of jars, which reside in the customer libs directory,
        * but haven't been registered yet and do not present in the server
        * classpath.
        *
        * @return the list of the names of unregistered jar files available for
        *         registration.
        */
       public static String[] getUnregisteredJars()
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return MultiClassLoader.listUnregisteredJars();
       }

       /**
        * Get the list of classes which reside into the specified jar file and
        * implement the {@link com.goldencode.p2j.main.InitTermListener}
        * interface. These classes can be used as "hook" classes which should
        * be called after the jar representing a customer lib was successfully
        * registered, to initialize it.
        *
        * @param  jar
        *         The name of the target jar file.
        *
        * @return the list of fully qualified names of the classes which reside
        *         into the specified jar and implement the
        *         {@link com.goldencode.p2j.main.InitTermListener} interface.
        */
       public static String[] getAvailableJarHookClasses(String jar)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          List<String> hooks = new ArrayList<String>();

          ClassInfo[] classes = MultiClassLoader.listClasses(jar);
          if (classes == null)
             return null;

          for (ClassInfo cls : classes)
          {
             if (cls.isPublic() &&
                 !cls.isAbstract() &&
                 !cls.isInterface() &&
                 cls.isImplements(InitTermListener.class))
             {
                hooks.add(cls.getClassName());
             }
          }

          return hooks.toArray(new String[hooks.size()]);
       }

       /**
        * Get hook class specified in the directory for the target jar.
        *
        * @param  jar
        *         The name of the target jar file (should be loaded).
        *
        * @return fully qualified class name of the hook specified in the
        *         directory for the target jar or {@code null} if the jar has
        *         no hook specified or you have insufficient rights.
        */
       public static String getStoredJarHookClass(String jar)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          String path = Utils.findDirectoryNodePath(wa.ds,
                                                    "server-hooks",
                                                    "strings",
                                                    false);
          if (path != null)
          {
             String[] hooks = wa.ds.getNodeStrings(path, "values");
             if (hooks != null && hooks.length > 0)
             {
                for (String hookClass : hooks)
                {
                   if (MultiClassLoader.handledByJar(hookClass, jar))
                   {
                      return hookClass;
                   }
                }
             }
          }

          return null;
       }

       /**
        * Register the specified jar file (which represent a customer lib). Also,
        * a "hook" class can be registered for this jar, this class will be called
        * after the jar was successfully registered, to initialize it.
        *
        * @param  jar
        *         The name of the jar file to register.
        * @param  hookClass
        *         Fully qualified name of the "hook" class for this jar, can be
        *         {@code null}.
        * @param  storeInDirectory
        *         If {@code true} then the jar is registered in the
        *         directory, else only runtime registration is performed.
        *
        * @return operation result.
        */
       public static JarOpErrorCode registerJar(String jar,
                                                String hookClass,
                                                boolean storeInDirectory)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          // load jar
          ErrorCode res;
          try
          {
             res = MultiClassLoader.addJarLoader(jar);
          }
          catch (RestrictedUseException e)
          {
             throw new IllegalStateException("Call of " +
                   "MultiClassLoader.addJarLoader from this function should" +
                   "be granted.");
          }

          if (res != ErrorCode.SUCCESS)
          {
             return toJarOpErrorCode(res);
          }

          // load hook
          InitTermListener listener = null;
          if (hookClass != null)
          {
             try
             {
                listener = StandardServer.loadHook(hookClass, jar);
             }
             catch (Exception e)
             {
                LOG.severe( "Error while loading hook " + hookClass + " for jar " + jar, e);

                //back out
                try
                {
                   MultiClassLoader.removeJarLoader(jar);
                }
                catch (RestrictedUseException ex)
                {
                   throw new IllegalStateException("Call of " +
                      "MultiClassLoader.removeJarLoader from this function " +
                      "should be granted.");
                }
             }

             if (listener == null)
             {
                return JarOpErrorCode.ERROR_LOADING_HOOK;
             }

             // register with the server
             StandardServer.register(listener);
          }

          if (storeInDirectory)
          {
             // store jar in the directory
             JarOpErrorCode result = addNodeStrings(wa.ds,
                   "/server/default/jars",
                   new String[]{jar});

             if (result != JarOpErrorCode.SUCCESS)
             {
                //back out
                try
                {
                   MultiClassLoader.removeJarLoader(jar);
                }
                catch (RestrictedUseException ex)
                {
                   throw new IllegalStateException("Call of " +
                      "MultiClassLoader.removeJarLoader from this function " +
                      "should be granted.");
                }

                if (hookClass != null)
                   StandardServer.removeHook(hookClass);
                return result;
             }
          }

          if (hookClass != null)
          {
             String path = null;
             if (storeInDirectory)
             {
                // store hook in the directory
                path = Utils.findDirectoryNodePath(wa.ds,
                                                   "server-hooks",
                                                   "strings",
                                                   false);
                if (path == null)
                   path = "/server/default/server-hooks";

                JarOpErrorCode result = addNodeStrings(wa.ds,
                                                       path,
                                                       new String[]{hookClass});

                if (result != JarOpErrorCode.SUCCESS)
                {
                   //back out
                   try
                   {
                      MultiClassLoader.removeJarLoader(jar);
                   }
                   catch (RestrictedUseException ex)
                   {
                      throw new IllegalStateException("Call of " +
                         "MultiClassLoader.removeJarLoader from this function " +
                         "should be granted.");
                   }

                   StandardServer.removeHook(hookClass);
                   removeNodeStrings(wa.ds,
                                     "/server/default/jars",
                                     new String[]{jar});
                   return result;
                }
             }

             // initialize hook
             try
             {
                listener.initialize();
             }
             catch (Throwable t)
             {
                //back out
                try
                {
                   MultiClassLoader.removeJarLoader(jar);
                }
                catch (RestrictedUseException ex)
                {
                   throw new IllegalStateException("Call of " +
                      "MultiClassLoader.removeJarLoader from this function " +
                      "should be granted.");
                }

                StandardServer.removeHook(hookClass);
                if (storeInDirectory)
                {
                   removeNodeStrings(wa.ds,
                                     "/server/default/jars",
                                     new String[]{jar});
                   removeNodeStrings(wa.ds, path, new String[]{hookClass});
                }

                return JarOpErrorCode.ERROR_RUN_HOOK;
             }
          }

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Remove registration for the specified jar.
        *
        * @param  jar
        *         Jar file which registration should be removed.
        * @param  force
        *         If it is {@code true} then any sessions that have executed
        *         API(s) associated to this jar are forcibly terminated. If this
        *         parameter is {@code false} then, if the jar has any
        *         sessions that have executed API(s) associated to it, the
        *         function returns
        *         {@link JarOpErrorCode#ACTIVE_SESSIONS_PRESENT}.
        * @param  removeFromDirectory
        *         If {@code true} then the jar registration is removed from
        *         the directory, else only runtime deregistration is performed.
        *
        * @return operation result.
        */
       public static JarOpErrorCode deregisterJar(String jar,
                                                  boolean force,
                                                  boolean removeFromDirectory)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          if (force)
          {
             // terminate sessions if necessary
             if (terminateSessions(jar, null) != JarOpErrorCode.SUCCESS)
             {
                return JarOpErrorCode.ERROR_TERMINATING_SESSIONS;
             }
          }
          else
          {
             // check whether there are active sessions
             SessionInfo[] info = wa.sa.getActiveSessions(jar, null);
             if (info == null)
             {
                return JarOpErrorCode.INSUFFICIENT_RIGHTS;
             }
             else if (info.length > 0)
             {
                return JarOpErrorCode.ACTIVE_SESSIONS_PRESENT;
             }
          }

          // get registered APIs
          TaggedName[] apis = getRegisteredJarAPIs(jar);
          if (apis == null)
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;

          String[] ifaces = new String[apis.length];
          for (int i = 0; i < apis.length; i++)
          {
             ifaces[i] = apis[i].getName();
          }

          // deregister APIs
          if (!RemoteObject.deregisterStaticNetworkServer(ifaces))
          {
             return JarOpErrorCode.ERROR_DEREG_NETWORK_SERVER;
          }

          // terminate jar hook
          String activeHookClass = StandardServer.getHookForJar(jar);
          String storedHookClass = removeFromDirectory ?
                                   getStoredJarHookClass(jar) :
                                   null;

          if (activeHookClass != null)
          {
             StandardServer.removeHook(activeHookClass);
          }

          // deregister jar
          ErrorCode res;
          try
          {
             res = MultiClassLoader.removeJarLoader(jar);
          }
          catch (RestrictedUseException e)
          {
             throw new IllegalStateException("Call of " +
                   "MultiClassLoader.removeJarLoader from this function should" +
                   "be granted.");
          }

          if (res != ErrorCode.SUCCESS)
          {
             return toJarOpErrorCode(res);
          }

          if (removeFromDirectory)
          {
             return deregisterJarFromDirectory(jar, storedHookClass);
          }

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Remove jar registration from the directory.
        *
        * @param  jar
        *         The name of the jar to be deregistered.
        * @param  storedHookClass
        *         The name of the hook class which is stored for this jar in the
        *         directory or {@code null} if no hook is defined for this
        *         jar.
        *
        * @return operation result code.
        */
       public static JarOpErrorCode deregisterJarFromDirectory(String jar,
                                                               String storedHookClass)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          if (storedHookClass != null)
          {
             // remove hook information from the directory
             String path = Utils.findDirectoryNodePath(wa.ds,
                                                       "server-hooks",
                                                       "strings",
                                                       false);
             if (path != null)
             {
                JarOpErrorCode result = removeNodeStrings(wa.ds,
                                                   path,
                                                   new String[]{storedHookClass});

                if (result != JarOpErrorCode.SUCCESS)
                {
                   return result;
                }
             }
          }

          // remove jar information from the directory
          return removeNodeStrings(wa.ds,
                                   "/server/default/jars",
                                   new String[]{jar});
       }

       /**
        * Change the server-wide or global hook class of the specific jar.
        *
        * @param  jar
        *         The name of the target jar file.
        * @param  hookClass
        *         The new hook class for the target jar. {@code null}
        *         indicates that no hook class should be specified.
        *
        * @return operation result.
        */
       public static JarOpErrorCode changeJarHookClass(String jar,
                                                       String hookClass)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          String path = Utils.findDirectoryNodePath(wa.ds,
                                                    "server-hooks",
                                                    "strings",
                                                    false);
          if (path == null)
             path = "/server/default/server-hooks";

          String oldHookClass = getStoredJarHookClass(jar);
          if (oldHookClass != null)
          {
             JarOpErrorCode result = removeNodeStrings(wa.ds,
                                                       path,
                                                       new String[]{oldHookClass});
             if (result != JarOpErrorCode.SUCCESS)
                return result;
          }

          if (hookClass != null)
          {
             JarOpErrorCode result = addNodeStrings(wa.ds,
                                                    path,
                                                    new String[]{hookClass});
             if (result != JarOpErrorCode.SUCCESS)
                return result;
          }

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Get the list of APIs registered for the specified jar.
        *
        * @param  jar
        *         The name of the target jar file.
        *
        * @return the list of APIs registered for the specified jar, each returned
        *         TaggedName represents a single API and contains fully qualified
        *         name of the interface and fully qualified name of the
        *         implementation class.
        */
       public static TaggedName[] getRegisteredJarAPIs(String jar)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          String[] apis = getAvailableJarAPIInterfaces(jar, true);
          List<TaggedName> res = new ArrayList<TaggedName>();
          for (String api : apis)
          {
             Class<?> implClass = RemoteObject.getImplClass(api);

             if (implClass != null)
             {
                res.add(new TaggedName(api, implClass.getName()));
             }
          }

          return res.toArray(new TaggedName[res.size()]);
       }

       /**
        * Get the list of classes which reside in the specified jar and
        * statically implement all methods defined by the specified interface.
        *
        * @param  jar
        *         The name of the target jar file.
        * @param  interfaceClass
        *         Fully qualified name of the target interface.
        *
        * @return the list of fully qualified names of classes which reside in the
        *         specified jar and statically implement all methods defined by
        *         the specified interface.
        */
       public static String[] getAvailableJarAPIImplClasses(String jar,
                                                            String interfaceClass)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          Class<?> iface;
          try
          {
             iface = MultiClassLoader.getClassLoader().loadClass(interfaceClass);
          }
          catch (ClassNotFoundException e)
          {
             LOG.severe("Cannot load class " + interfaceClass + " for jar " + jar, e);
             return null;
          }

          ClassInfo[] implClasses      = MultiClassLoader.listClasses(jar);
          List<String> selectedClasses = new ArrayList<String>();

          // get all possible implementation classes from the jar and see if any
          // of them implement all methods of the given interface, as static
          // methods
          try
          {
             for (ClassInfo implClass : implClasses)
             {
                if (!implClass.isInterface() &&
                    implClass.isPublic() &&
                    !implClass.isAbstract() &&
                    implClass.isStaticallyImplements(iface))
                   selectedClasses.add(implClass.getClassName());
             }
          }
          catch (NoClassDefFoundError e)
          {
             LOG.severe("Cannot load methods of " + interfaceClass + " for jar " + jar, e);
             return null;
          }

          return selectedClasses.toArray(new String[selectedClasses.size()]);
       }

       /**
        * Register API specified by the given interface and implementation class.
        *
        * @param  interfaceClass
        *         Fully qualified name of the interface which defines the target
        *         API.
        * @param  implementationClass
        *         Fully qualified name of the class which implements the target
        *         API.
        *
        * @return operation result.
        */
       public static JarOpErrorCode registerJarAPI(String interfaceClass,
                                                   String implementationClass)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          Class<?> iClass;
          Class<?> backing;

          try
          {
             iClass = MultiClassLoader.getClassLoader().loadClass(interfaceClass);
          }
          catch (ClassNotFoundException e)
          {
             return JarOpErrorCode.ERROR_LOADING_API_IFACE;
          }

          try
          {
             backing = MultiClassLoader.getClassLoader().loadClass(implementationClass);
          }
          catch (ClassNotFoundException e)
          {
             return JarOpErrorCode.ERROR_LOADING_API_IMPL;
          }

          Class<?>[] list = new Class<?>[] { iClass };
          RemoteObject.registerStaticNetworkServer(list, backing);

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Remove registration of the specified API in the specified jar.
        *
        * @param  jar
        *         The name of the target jar file.
        * @param  interfaceClass
        *         Fully qualified name of the interface which defines the target
        *         API.
        * @param  force
        *         If it is {@code true} then any sessions that have executed
        *         the target API are forcibly terminated. If this parameter is
        *         {@code false} then, if the jar has any
        *         sessions that have executed API(s) associated to it, the
        *         function returns
        *         {@link JarOpErrorCode#ACTIVE_SESSIONS_PRESENT}.
        *
        * @return operation result.
        */
       public static JarOpErrorCode deregisterJarAPI(String jar,
                                                     String interfaceClass,
                                                     boolean force)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          if (force)
          {
             // terminate sessions if necessary
             if (terminateSessions(jar, interfaceClass) != JarOpErrorCode.SUCCESS)
             {
                return JarOpErrorCode.ERROR_TERMINATING_SESSIONS;
             }
          }
          else
          {
             // check whether there are active sessions
             SessionInfo[] info = wa.sa.getActiveSessions(jar, interfaceClass);
             if (info == null)
             {
                return JarOpErrorCode.INSUFFICIENT_RIGHTS;
             }
             else if (info.length > 0)
             {
                return JarOpErrorCode.ACTIVE_SESSIONS_PRESENT;
             }
          }

          String[] list = new String[] { interfaceClass };
          if (!RemoteObject.deregisterStaticNetworkServer(list))
          {
             return JarOpErrorCode.ERROR_DEREG_NETWORK_SERVER;
          }

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Returns information whether server-wide or global hooks are used for
        * the current server.
        *
        * @return {@code true} if server-wide hooks are used,
        *         {@code false} if global hooks are used, {@code null}
        *         if you have insufficient access rights.
        */
       public static Boolean isUsingServerWideHooks()
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          StringBuilder sb = new StringBuilder("/server/");
          sb.append(getServerName());
          sb.append("/server-hooks");

          return wa.ds.getNodeClass(sb.toString()) != null;
       }

       /**
        * Get the list of all active sessions which have executed the specified
        * API or API(s) associated to the specified jar.
        *
        * @param   jar
        *          The name of the target jar file.
        * @param   apiInterfaceClass
        *          Fully qualified name of the interface which defines the target
        *          API. Can be {@code null} if you want to get sessions of
        *          all APIs of the target jar.
        *
        * @return  the list of all active sessions which have executed the
        *          specified API or API(s) associated to the specified jar.
        */
       public static SessionInfo[] getActiveSessions(String jar,
                                                     String apiInterfaceClass)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          SessionInfo[] sessions = wa.sa.getActiveSessions(jar, apiInterfaceClass);

          if (sessions != null)
          {
             setStacktraces(sessions);
          }

          return sessions;
       }

       /**
        * Terminate all active sessions which have invoked APIs in that jar or
        * the given exported interface, if <code>iface</code> is not
        * {@code null}.
        *
        * @param   jar
        *          The jar name.
        * @param   iface
        *          The interface name. May be {@code null}.
        *
        * @return  operation result.
        */
       public static JarOpErrorCode terminateSessions(String jar, String iface)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          if (!wa.sa.terminateSessions(jar, iface))
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          return JarOpErrorCode.SUCCESS;
       }

       /**
        * Get the list of interfaces available in the specified jar file.
        *
        * @param  jar
        *         The name of the target jar file.
        * @param  showRegistered
        *         If {@code true} then resulting list contains interfaces
        *         that have already have been registered for an API for the given
        *         jar (assuming that each API is defined using an unique
        *         interface).
        *
        * @return the list of fully qualified names of interfaces available in the
        *         specified jar file.
        */
       public static String[] getAvailableJarAPIInterfaces(String jar,
                                                           boolean showRegistered)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          List<String> interfaces = new ArrayList<String>();

          Set<String> registeredIfaceSet = null;
          if (!showRegistered)
          {
             registeredIfaceSet = new HashSet<String>();
             TaggedName[] registeredApisArray = getRegisteredJarAPIs(jar);
             for (TaggedName api : registeredApisArray)
             {
                registeredIfaceSet.add(api.getName());
             }
          }

          ClassInfo[] classes = MultiClassLoader.listClasses(jar);
          for (ClassInfo cls : classes)
          {
             if (!showRegistered && registeredIfaceSet.contains(cls.getClassName()))
                continue;

             if (cls.isPublic() && cls.isInterface())
             {
                interfaces.add(cls.getClassName());
             }
          }

          return interfaces.toArray(new String[interfaces.size()]);
       }

       /**
        * Make the current server use server-wide or global hooks, optionally
        * copying or moving hooks from one scope to another.
        *
        * @param  serverWide
        *         if {@code true} then server-wide hooks should be used,
        *         else global hooks should be used.
        * @param  move
        *         if {@code true} then existing hooks are copied/moved from
        *         one scope to another, otherwise they don't.
        *
        * @return operation result.
        */
       public static JarOpErrorCode changeJarHooksType(boolean serverWide,
                                                       boolean move)
       {
          // access the target
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return JarOpErrorCode.INSUFFICIENT_RIGHTS;
          }

          StringBuilder sb = new StringBuilder("/server/");
          sb.append(getServerName());
          sb.append("/server-hooks");

          String serverWidePath = sb.toString();
          String globalPath = "/server/default/server-hooks";

          boolean currentServerWide = wa.ds.getNodeClass(serverWidePath) != null;

          if (currentServerWide == serverWide)
             return JarOpErrorCode.SUCCESS;

          if (serverWide)
          {
             // from global to server-wide
             String[] valuesToCopy = new String[0];
             if (move)
             {
                // copy hooks from global scope to server-wide scope
                String[] old = wa.ds.getNodeStrings(globalPath, "values");
                if (old != null)
                   valuesToCopy = old;
             }

             // create server-wide hooks node
             return addNodeStrings(wa.ds, serverWidePath, valuesToCopy);
          }
          else
          {
             // from server-wide to global
             String[] valuesToMove = new String[0];
             if (move)
             {
                // copy hooks from server-wide scope to global scope
                String[] oldServerWideHooks = wa.ds.getNodeStrings(serverWidePath,
                                                                   "values");
                if (oldServerWideHooks != null)
                   valuesToMove = oldServerWideHooks;

                String[] oldGlobalHooks = wa.ds.getNodeStrings(globalPath,
                                                               "values");
                // remove existing global hooks
                if (oldGlobalHooks != null && oldGlobalHooks.length > 0)
                {
                   JarOpErrorCode result = removeNodeStrings(wa.ds,
                                                             globalPath,
                                                             oldGlobalHooks);
                   if (result != JarOpErrorCode.SUCCESS)
                      return result;
                }
             }

             // copy server-wide hooks to global hooks node
             if (valuesToMove.length > 0)
             {
                JarOpErrorCode result = addNodeStrings(wa.ds,
                                                       globalPath,
                                                       valuesToMove);
                if (result != JarOpErrorCode.SUCCESS)
                   return result;
             }

             // remove server-wide hooks node
             sb = new StringBuilder("/server/");
             sb.append(getServerName());
             String serverWideBase = sb.toString();

             if (!wa.ds.openBatch(serverWideBase))
                return JarOpErrorCode.CANNOT_OPEN_DIRECTORY_BRANCH;

             JarOpErrorCode result = JarOpErrorCode.SUCCESS;
             boolean deleteRes = wa.ds.deleteNode(serverWidePath);
             if (!deleteRes)
                result = JarOpErrorCode.ERROR_DELETING_NODE;

             boolean res = wa.ds.closeBatch(deleteRes, false);
             if (!res && deleteRes)
                result = JarOpErrorCode.CANNOT_COMMIT_DIRECTORY;

             return result;
          }
       }

        /**
        * Allows to determine whether
        * {@link com.goldencode.p2j.classloader.MultiClassLoader} is used as the
        * system class loader.
        *
        * @return {@code true} if
        *         {@link com.goldencode.p2j.classloader.MultiClassLoader} is used
        *         as the system class loader, {@code false} if the standard
        *         class loader is used, {@code null} if you do not have
        *         enough permissions
        */
       public static Boolean isUsingMultiClassLoader()
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return MultiClassLoader.getClassLoader() instanceof MultiClassLoader;
       }

       /**
        * Get information about managed libraries directory (path to it and
        * information whether it exists).
        *
        * @return TaggedName which contains fully qualified path to the managed
        *         libraries directory (can be {@code null} if the directory
        *         is not specified) and string value which is <code>"true"</code>
        *         if the directory is specified, exists and is accessible.
        *         If you do not have admin rights, {@code null} is returned.
        */
       public static TaggedName getManagedLibsDirInformation()
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          String path   = MultiClassLoader.managedCustomerLibs();
          if (path == null)
             return new TaggedName(null, "false");

          File f        = new File(path);
          String exists = f.exists() && f.isDirectory() ? "true" : "false";

          try
          {
             path = f.getCanonicalPath();
          }
          catch (IOException e)
          {
             exists = "false";
          }
          catch (SecurityException e)
          {
             exists = "false";
          }

          return new TaggedName(path, exists);
       }

       /**
        * Returns the map of report parameters to their values saved in the directory.
        *
        * @return   The report parameters map
        */
       public static Map<String, Object> getReportParametersMap()
       {
          DirectoryService dir = getDirectoryService();

          HashMap<String, Object> map = new HashMap<String, Object>();

          getIntReportSettings(dir, ReportParameters.MAX_PAGES_NUMBER, 250, map);

          // page settings
          getStringReportSettings(dir, ReportParameters.PAPER_FORMAT, "A4", map);

          getStringReportSettings(dir, ReportParameters.PAPER_ORIENT, "PORTRAIT", map);

          // font settings
          getStringReportSettings(dir, ReportParameters.FONT_NAME, "Courier", map);

          getStringReportSettings(dir, ReportParameters.BOLD_FONT_NAME, "Courier-Bold", map);

          getIntReportSettings(dir, ReportParameters.FONT_SIZE, 10, map);

          getIntReportSettings(dir, ReportParameters.COLUMN_FONT_SIZE, 12, map);

          getIntReportSettings(dir, ReportParameters.HEADER_FONT_SIZE, 14, map);

          getIntReportSettings(dir, ReportParameters.FOOTER_FONT_SIZE, 10, map);

          // report geometry settings
          // Defines page margin in this order: left, top, right and bottom.
          getFloatReportSettings(dir, ReportParameters.LEFT_PAGE_MARGIN, 10f, map);
          getFloatReportSettings(dir, ReportParameters.TOP_PAGE_MARGIN, 10f, map);
          getFloatReportSettings(dir, ReportParameters.RIGHT_PAGE_MARGIN, 10f, map);
          getFloatReportSettings(dir, ReportParameters.BOTTOM_PAGE_MARGIN, 10f, map);

          getFloatReportSettings(dir, ReportParameters.CELL_MARGIN, 2f, map);

          getFloatReportSettings(dir, ReportParameters.CELL_PADDING, 2f, map);

          getFloatReportSettings(dir, ReportParameters.HEADER_MARGIN, 10f, map);

          getFloatReportSettings(dir, ReportParameters.FOOTER_MARGIN, 5f, map);

          return map;
       }

       /**
        * Get a list with all the tenants.
        *
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  A list with the registered tenants.
        */
       public static List<Tenant> listTenants(List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.listTenants(errorSink);
       }

       /**
        * Given a tenant name, get the tenant information.
        *
        * @param   tenantName
        *          The tenant name for which to retrieve the information.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  The tenant with the given tenant name.
        */
       public static Tenant listTenant(String tenantName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.getTenant(tenantName, errorSink);
       }

       /**
        * Add a tenant to the list.
        *
        * @param   tenantName
        *          The name of the tenant.
        * @param   tenantDescription
        *          The description of the tenant.
        * @param   tenantInfo
        *          The info of the tenant.
        * @param   databases
        *          A list of the databases that will be used by the tenant.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation is performed successfully,
        *          {@code false} if the operation is not performed successfully,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean addTenant(String tenantName,
                                       String tenantDescription,
                                       String tenantInfo,
                                       List<TenantDatabaseDescriptor> databases,
                                       List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.addTenant(tenantName, tenantDescription, tenantInfo, databases, errorSink);
       }

       /**
        * Function for adding a database instance to a tenant.
        *
        * @param   tenantName
        *          The tenant for which to add the database instance.
        * @param   db
        *          The database information to be added.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation is performed successfully,
        *          {@code false} if the operation is not performed successfully,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean addDatabaseToTenant(String tenantName,
                                                 TenantDatabaseDescriptor db,
                                                 List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.addDatabaseToTenant(tenantName, db, errorSink);
       }

       /**
        * Update a tenant from the master table. This method allows one or multiple column values to be edited.
        * Possible {@code column} values/names are:
        * <ul>
        *    <li> tenant_name - the name of the tenant.
        *    <li> db_name - the physical name of a database instance that this tenant uses.
        *    <li> url - the URL of a database instance.
        *    <li> username - the username required in order to access a database instance.
        *    <li> password - the password required in order to access a database instance.
        * </ul>
        *
        * @param   tenantName
        *          The name of the tenant that is targeted by the update.
        * @param   pdbName
        *          The physical name of the database that is targeted by the update.
        * @param   values
        *          A Map with pairs (column name - new value) that are intended for the update.
        * @param   databaseUpdate
        *          Is this update targeting a database change?
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if all the updates are performed successfully,
        *          {@code false} if there is at least an update that fails,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean updateTenant(String tenantName,
                                          String pdbName,
                                          Map<String, String> values,
                                          boolean databaseUpdate,
                                          List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.updateTenant(tenantName, pdbName, values, databaseUpdate, errorSink);
       }

       /**
        * Function for deleting a tenant from the master table.
        *
        * @param   tenantName
        *          The name of the tenant that is targeted by delete operation.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation was successful, {@code false} otherwise.
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean deleteTenant(String tenantName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.deleteTenant(tenantName, errorSink);
       }

       /**
        * Delete a database instance that is assigned to a tenant.
        *
        * @param   tenantName
        *          The tenant for which to remove the database instance.
        * @param   pdbName
        *          The physical database name for the database to be deleted.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation was successful, {@code false} otherwise.
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean deleteDatabaseFromTenant(String tenantName, String pdbName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.deleteDatabaseFromTenant(tenantName, pdbName, errorSink);
       }

       /**
        * Enable/Disable a specific tenant.
        *
        * @param   tenantName
        *          The tenant to work with.
        * @param   enable
        *          {@code true} if the intent is to enable the tenant, {@code false} otherwise.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation is performed successfully,
        *          {@code false} if the operation is not performed successfully,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean changeTenantStatus(String tenantName, boolean enable, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.changeTenantStatus(tenantName, enable, errorSink);
       }

       /**
        * Check if a specific tenant is enabled or not.
        *
        * @param   tenantName
        *          The tenant to work with.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the tenant is enabled, {@code false} otherwise.
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean isTenantEnabled(String tenantName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.isTenantEnabled(tenantName, errorSink);
       }

       /**
        * Get a list with all domains.
        *
        * @param   ldbName
        *          The logical database name whose domains need to be listed.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  A list with the registered domains.
        */
       public static List<String> listDomains(String ldbName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.listDomains(ldbName, errorSink);
       }

       /**
        * Get a list with all domain types.
        *
        * @param   ldbName
        *          The logical database name whose domain types need to be listed.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  A list with the registered domain types.
        */
       public static List<String> listDomainTypes(String ldbName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.listDomainTypes(ldbName, errorSink);
       }

       /**
        * Get the details for a specific domain of a logical database.
        *
        * @param   ldbName
        *          The logical database name whose domain are requested.
        * @param   domain
        *          The domain whose details are requested.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  A map with details of a registered domain from {@code _sec-authentication-domain} meta table of
        *          the specified logical database.
        */
       public static HashMap<String, BaseDataType> getDomain(String ldbName,
                                                             String domain,
                                                             List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.getDomain(ldbName, domain, errorSink);
       }

       /**
        * Get the details for a specific domain of a logical database.
        *
        * @param   ldbName
        *          The logical database name whose domain are requested.
        * @param   domainTypeName
        *          The name of the domain type whose details are requested.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  A map with details of a registered domain type from {@code _sec-authentication-system} meta
        *          table of the specified logical database.
        */
       public static HashMap<String, BaseDataType> getDomainType(String ldbName,
                                                                 String domainTypeName,
                                                                 List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.getDomainType(ldbName, domainTypeName, errorSink);
       }

       /**
        * Add a domain to the list.
        *
        * @param   ldbName
        *          The logical database name where the new domains is about to be created.
        * @param   properties
        *          The properties of the new domain, including the name of the domain.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation is performed successfully,
        *          {@code false} if the operation is not performed successfully,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean addDomain(String ldbName, JsonNode properties, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.addDomain(ldbName, properties, errorSink);
       }

       /**
        * Add a domain to the list.
        *
        * @param   ldbName
        *          The logical database name where the new domains is about to be created.
        * @param   properties
        *          The properties of the new domain, including the name of the domain.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation is performed successfully,
        *          {@code false} if the operation is not performed successfully,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean addDomainType(String ldbName, JsonNode properties, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.addDomainType(ldbName, properties, errorSink);
       }

       /**
        * Update a domain from the master table.
        *
        * @param   ldbName
        *          The logical database name where the new domains is about to be updated.
        * @param   domainName
        *          The name of the domain that is targeted by the update.
        * @param   properties
        *          A Map with pairs (column name - new value) that are intended for the update.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if all the updates are performed successfully,
        *          {@code false} if there is at least an update that fails,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean updateDomain(String ldbName,
                                          String domainName,
                                          JsonNode properties,
                                          List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.updateDomain(ldbName, domainName, properties, errorSink);
       }

       /**
        * Update a domain from the master table.
        *
        * @param   ldbName
        *          The logical database name where the new domains is about to be updated.
        * @param   domainType
        *          The domain type that is targeted by the update.
        * @param   properties
        *          A Map with pairs (column name - new value) that are intended for the update.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if all the updates are performed successfully,
        *          {@code false} if there is at least an update that fails,
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean updateDomainType(String ldbName,
                                              String domainType,
                                              JsonNode properties,
                                              List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.updateDomainType(ldbName, domainType, properties, errorSink);
       }

       /**
        * Function for deleting a domain from the master table.
        *
        * @param   ldbName
        *          The logical database name whose domain is about to be deleted.
        * @param   domainName
        *          The name of the domain that is to be deleted.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation was successful, {@code false} otherwise.
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean deleteDomain(String ldbName, String domainName, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.deleteDomain(ldbName, domainName, errorSink);
       }

       /**
        * Function for deleting a domain type from the master table.
        *
        * @param   ldbName
        *          The logical database name whose domain is about to be deleted.
        * @param   domainType
        *          The domain type that is to be deleted.
        * @param   errorSink
        *          A sink for errors encountered.
        *
        * @return  {@code true} if the operation was successful, {@code false} otherwise.
        *          {@code null} if the caller does not have sufficient rights.
        */
       public static Boolean deleteDomainType(String ldbName, String domainType, List<String> errorSink)
       {
          WorkArea wa = workArea.get();
          if (!wa.targeted || !wa.sa.adminAccess())
          {
             return null;
          }

          return TenantManager.deleteDomainType(ldbName, domainType, errorSink);
       }

       /**
        * Compute and set the current stacktrace for each session in the given list.
        *
        * @param    sessions
        *           The session list.
        */
       private static void setStacktraces(SessionInfo[] sessions)
       {
          Set<Thread> threads = getAllConversationThreads();

          Map<String, StacktraceInfo[]> programTraces = getProgramTraceByThreadNameMap(threads);
          Map<String, String[]> javaTraces= getJavaTraceByThreadNameMap(threads);

          for (SessionInfo session : sessions)
          {
             String key = String.format("Conversation [%08X:%s]", session.id, session.name);
             session.stacktrace = programTraces.get(key);
             session.javaStacktrace = javaTraces.get(key);
          }
       }

       /**
        * Given a set of Conversation threads, builds a map from full thread names to their computed
        * program trace string (programs, line numbers call chain).
        *
        * @param    threads
        *           All Conversation threads.
        *
        * @return   See above.
        */
       private static Map<String, StacktraceInfo[]> getProgramTraceByThreadNameMap(Set<Thread> threads)
       {
          Map<String, StacktraceInfo[]> programTraceMap = new HashMap<>();

          for (Thread t : threads)
          {
             String threadName = t.getName();
             StackTraceElement[] stackTrace = t.getStackTrace();
             StacktraceInfo[] thisProgramTrace = buildProgramTrace(stackTrace);

             if (thisProgramTrace != null)
             {
                programTraceMap.put(threadName, thisProgramTrace);
             }
          }

          return programTraceMap;
       }

       /**
        * Given a set of Conversation threads, builds a map from full thread names to their computed
        * Java stacktrace.
        *
        * @param    threads
        *           All Conversation threads.
        *
        * @return   See above.
        */
       private static Map<String, String[]> getJavaTraceByThreadNameMap(Set<Thread> threads)
       {
          Map<String, String[]> javaTraceMap = new HashMap<>();

          for (Thread t : threads)
          {
             String threadName = t.getName();
             StackTraceElement[] stackTrace = t.getStackTrace();

             if (stackTrace != null)
             {
                String[] traces = new String[stackTrace.length + 1];
                traces[0] = threadName;
                for (int i = 0; i < stackTrace.length; i++)
                {
                   traces[i + 1] = stackTrace[i].toString();
                }

                javaTraceMap.put(threadName, traces);
             }
          }

          return javaTraceMap;
       }

       /**
        * Given an array of stack trace elements, attempts to build a program trace string in the
        * following format:
        * <p>
        * program (line&lt;fromLine&lt;...&lt;fromLine) &lt;&lt; fromProgram...
        * </p>
        * Where program is the name of the Java class currently running (e.g. foo.SomeProgram),
        * line is the line where the thread is currently at, and all subsequent fromLine and
        * fromProgram lists are from the Java call stack.
        * <p>
        * Returns {@code null} if no stack trace is provided. Returns empty string if program
        * class name wasn't found.
        *
        * @param    stackTrace
        *           The stacktrace to process.
        *
        * @return   See above.
        */
       private static StacktraceInfo[] buildProgramTrace(StackTraceElement[] stackTrace)
       {
          if (stackTrace == null)
          {
             return null;
          }

          String pkgroot = Utils.getDirectoryNodeString(null, "legacy-system/pkgroot", null) + ".";
          int programNameFromClassBeginIndex = pkgroot.length();
          ArrayList<StacktraceInfo> traces = new ArrayList<>();

          for (StackTraceElement stackTraceElement : stackTrace)
          {
             if (stackTraceElement.getClassName().startsWith(pkgroot) &&
                 stackTraceElement.getLineNumber() > 0)
             {
                String thisProgram = stackTraceElement.getClassName();
                thisProgram = thisProgram.substring(programNameFromClassBeginIndex);

                int javaSubclassIdx = thisProgram.indexOf("$");
                if (javaSubclassIdx > 0)
                {
                   thisProgram = thisProgram.substring(0, javaSubclassIdx);
                }

                String method = stackTraceElement.getMethodName();
                if (method.startsWith("lambda$"))
                {
                   method = method.substring("lambda$".length());
                }
                javaSubclassIdx = method.indexOf("$");
                if (javaSubclassIdx > 0)
                {
                   method = method.substring(0,  javaSubclassIdx);
                }
                if (method.equals("null"))
                {
                   method = null;
                }

                StacktraceInfo trace = new StacktraceInfo();
                trace.program = thisProgram;
                trace.method = method;
                trace.line = stackTraceElement.getLineNumber();

                if (method != null)
                {
                   // go back through all traces and set this method - these were lambda expressions
                   // defined inside this method
                   int i = traces.size() - 1;
                   while (i >= 0)
                   {
                      StacktraceInfo lambdaTrace = traces.get(i);
                      if (lambdaTrace.method != null)
                      {
                         break;
                      }

                      lambdaTrace.method = method;

                      i--;
                   }
                }
                traces.add(trace);
             }
          }

          return traces.toArray(new StacktraceInfo[0]);
       }

       /**
        * Gets all session conversation threads.
        *
        * @return   See above.
        */
       private static Set<Thread> getAllConversationThreads()
       {
          Set<Thread> threads = new HashSet<Thread>();
          Pattern threadNameRegexPattern = Pattern.compile("^Conversation \\[");

          Set<Thread> allThreads = Thread.getAllStackTraces().keySet();

          for (Thread t : allThreads)
          {
             if (t != null)
             {
                Matcher matcher = threadNameRegexPattern.matcher(t.getName());
                if (matcher.find())
                {
                   threads.add(t);
                }
             }
          }

          return threads;
       }

       /**
        * Gets the report parameter value saved at the directory if it has been set, otherwise returns
        * the default value.
        *
        * @param    dirService
        *           The directory service
        * @param    parameter
        *           The given report parameter
        * @param    defValue
        *           The default value
        * @param    map
        *           The report parameters map that is updated with the retrieved value
        *
        * @return   The report parameter value
        */
       private static float getFloatReportSettings(DirectoryService dirService,
                                                   ReportParameters parameter,
                                                   float defValue,
                                                   Map<String, Object> map)
       {
          float dirValue;
          try
          {
             dirValue = Float.parseFloat(Utils.getDirectoryNodeString(
                      dirService,
                      REPORT_SETTINGS.format(new String[]
                                             {
                                                parameter.getParameter()
                                             }),
                      String.valueOf(defValue),
                      DirScope.BOTH,
                      null));
          }
          catch (NumberFormatException ex)
          {
             dirValue = defValue;
          }

          map.put(parameter.getParameter(), dirValue);

          return dirValue;
       }

       /**
        * Gets the report parameter value saved at the directory if it has been set, otherwise returns
        * the default value.
        *
        * @param    dirService
        *           The directory service
        * @param    parameter
        *           The given report parameter
        * @param    defValue
        *           The default value
        * @param    map
        *           The report parameters map that is updated with the retrieved value
        *
        * @return   The report parameter value
        */
       private static String getStringReportSettings(DirectoryService dirService,
                                                     ReportParameters parameter,
                                                     String defValue,
                                                     Map<String, Object> map)
       {
          String dirValue = Utils.getDirectoryNodeString(dirService,
                                                         REPORT_SETTINGS.format(new String[]
                                                                                {
                                                                                   parameter.getParameter()
                                                                                }),
                                                         defValue,
                                                         DirScope.BOTH,
                                                         null);

          map.put(parameter.getParameter(), dirValue);

          return dirValue;
       }

       /**
        * Gets the report parameter value saved at the directory if it has been set, otherwise returns
        * the default value.
        *
        * @param    dirService
        *           The directory service
        * @param    parameter
        *           The given report parameter
        * @param    defValue
        *           The default value
        * @param    map
        *           The report parameters map that is updated with the retrieved value
        *
        * @return   The report parameter value
        */
       private static int getIntReportSettings(DirectoryService dirService,
                                               ReportParameters parameter,
                                               int defValue,
                                               Map<String, Object> map)
       {
          int dirValue = Utils.getDirectoryNodeInt(dirService,
                                                   REPORT_SETTINGS.format(new String[]
                                                                         {
                                                                            parameter.getParameter()
                                                                         }),
                                                   defValue,
                                                   DirScope.BOTH,
                                                   null);

          map.put(parameter.getParameter(), dirValue);

          return dirValue;
       }

       /**
        * Add the strings to the strings node (assuming that the node contains
        * unique string values). It creates node if it doesn't exist (even if
        * <code>values</code> is an empty array).
        *
        * @param  ds
        *         Target directory service.
        * @param  nodeId
        *         Path of the target node.
        * @param  values
        *         Values to be added.
        *
        * @return operation result.
        */
       private static JarOpErrorCode addNodeStrings(DirectoryService ds,
                                                    String nodeId,
                                                    String[] values)
       {
          // check existing values
          String[] old = ds.getNodeStrings(nodeId, "values");
          Set<String> valuesToAdd = new HashSet<String>(Arrays.asList(values));
          if (old != null)
          {
             for (String s : old)
             {
                valuesToAdd.remove(s);
             }
          }

          // check that node exist
          if (ds.getNodeClass(nodeId) == null)
          {
             // create node

             int idx = nodeId.lastIndexOf("/");
             String nodeBasePath = nodeId.substring(0, idx);
             boolean rc = ds.openBatch(nodeBasePath);

             if (!rc)
             {
                return JarOpErrorCode.CANNOT_OPEN_DIRECTORY_BRANCH;
             }

             Attribute[] data = new Attribute[]
             {
                // subjects
                new Attribute(ds.getClassNodeAttribute("strings", "values"),
                              valuesToAdd.toArray()),
             };

             JarOpErrorCode res = JarOpErrorCode.SUCCESS;
             boolean rcModified = ds.addNode(nodeId, "strings", data);
             if (!rcModified)
                res = JarOpErrorCode.CANNOT_MODIFY_DIRECTORY_NODE;

             // close batch with commit or rollback and no refresh
             rc = ds.closeBatch(rcModified, false);
             if (!rc && rcModified)
                res = JarOpErrorCode.CANNOT_COMMIT_DIRECTORY;

             return res;
          }
          else
          {
             // modify existing node
             if (valuesToAdd.size() == 0)
             {
                // nothing to add
                return JarOpErrorCode.SUCCESS;
             }

             boolean rc = ds.openBatch(nodeId);

             if (!rc)
             {
                return JarOpErrorCode.CANNOT_OPEN_DIRECTORY_BRANCH;
             }

             JarOpErrorCode res = JarOpErrorCode.SUCCESS;
             boolean rcModified = true;
             for (String value : valuesToAdd)
             {
                rcModified = ds.addNodeString(nodeId, "values", value);
                if (!rcModified)
                {
                   res = JarOpErrorCode.CANNOT_MODIFY_DIRECTORY_NODE;
                   break;
                }
             }

             // close batch with commit or rollback and no refresh
             rc = ds.closeBatch(rcModified, false);
             if (!rc && rcModified)
                res = JarOpErrorCode.CANNOT_COMMIT_DIRECTORY;

             return res;
          }
       }

       /**
        * Remove the strings from the strings node (assuming that the node
        * contains unique string values).
        *
        * @param  ds
        *         Target directory service.
        * @param  nodeId
        *         Path of the target node.
        * @param  values
        *         Values to be removed.
        *
        * @return operation result.
        */
       private static JarOpErrorCode removeNodeStrings(DirectoryService ds,
                                                       String nodeId,
                                                       String[] values)
       {
          // check that node exist
          if (ds.getNodeClass(nodeId) == null)
          {
             // node does not exist
             return JarOpErrorCode.SUCCESS;
          }
          else
          {
             // modify existing node
             boolean rc = ds.openBatch(nodeId);

             if (!rc)
             {
                return JarOpErrorCode.CANNOT_OPEN_DIRECTORY_BRANCH;
             }

             // get attribute indexes
             List<Integer> indexes = new ArrayList<Integer>();
             String[] old = ds.getNodeStrings(nodeId, "values");
             if (old != null)
             {
                for (String value : values)
                {
                   for (int i = 0; i < old.length; i++)
                   {
                      if (old[i].equals(value))
                      {
                         indexes.add(i);
                         break;
                      }
                   }
                }
             }

             JarOpErrorCode res = JarOpErrorCode.SUCCESS;
             boolean rcModified = false;

             if (indexes.size() > 0)
             {
                // the list of indexes in reverse order (for deletion of values
                // starting from the bottom of the list)
                Collections.sort(indexes, Collections.<Object>reverseOrder());

                for (Integer index : indexes)
                {
                   rcModified = ds.deleteNodeAttributeValue(nodeId,
                                                            "values",
                                                            index);
                   if (!rcModified)
                   {
                      res = JarOpErrorCode.CANNOT_MODIFY_DIRECTORY_NODE;
                      break;
                   }
                }
             }

             // close batch with commit or rollback and no refresh
             rc = ds.closeBatch(rcModified, false);
             if (!rc && rcModified)
                res = JarOpErrorCode.CANNOT_COMMIT_DIRECTORY;

             return res;
          }
       }

       /**
        * Converts {@link ErrorCode} to {@link JarOpErrorCode}.
        *
        * @param  code
        *         source class loader error code.
        *
        * @return corresponding jar operation result code.
        */
       private static JarOpErrorCode toJarOpErrorCode(ErrorCode code)
       {
          switch (code)
          {
             case SUCCESS:
                return JarOpErrorCode.SUCCESS;
             case NOT_FOUND:
                return JarOpErrorCode.NOT_FOUND;
             case IN_CLASSPATH:
                return JarOpErrorCode.IN_CLASSPATH;
             case ALREADY_REGISTERED:
                return JarOpErrorCode.ALREADY_REGISTERED;
             case INIT_ERROR:
                return JarOpErrorCode.INIT_ERROR;
             case NOT_REGISTERED:
                return JarOpErrorCode.NOT_REGISTERED;
             case CLOSE_ERROR:
                return JarOpErrorCode.CLOSE_ERROR;
             case BAD_LOADER:
                return JarOpErrorCode.BAD_LOADER;
          }
          return null;
       }

       /**
        * Returns the list of appserver processes.
        *
        * @return   Array of appserver definitions.
        */
       public static AppserverInfo[] getAppserverList()
       {
          try
          {
             return Stream.concat(Arrays.stream(ClassicAppserverDefinition.getAll()),
                                  Arrays.stream(MultiSessionAppserverDefinition.getAll()))
                          .parallel()
                          .map(def ->
                               {
                                  AppserverInfo appserverInfo = new AppserverInfo();
                                  appserverInfo.name = def.getAppserverName();
                                  appserverInfo.autoStartTime = def.getSchedulerMode() == null ?
                                     "Disabled" :
                                     def.getSchedulerMode();
                                  appserverInfo.systemUser = def.getSystemUser();
                                  appserverInfo.fwdProcessName = def.getProcessName();
                                  appserverInfo.propath = def.getPropath();
                                  appserverInfo.enableSsl = def.isEnableSsl();
                                  appserverInfo.privateKeyAlias = def.getAlias();

                                  boolean isNewMultiSession = def instanceof MultiSessionAppserverDefinition;
                                  if (isNewMultiSession)
                                  {
                                     MultiSessionAppserverDefinition msAppserver = (MultiSessionAppserverDefinition) def;
                                     appserverInfo.isMultiSession = true;
                                     appserverInfo.isNewMultiSession = true;
                                     appserverInfo.operatingMode = "Mixed";
                                     appserverInfo.initialServers = 1;
                                     appserverInfo.minServers = 1;
                                     appserverInfo.maxServer = 1;
                                     appserverInfo.requestTimeout = msAppserver.getRequestWaitTimeout();
                                     appserverInfo.autoTrimTimeout = -1;
                                     appserverInfo.activateProc = msAppserver.getSessionActivateProc();
                                     appserverInfo.deactivateProc = msAppserver.getSessionDeactivateProc();
                                     appserverInfo.connectProc = msAppserver.getSessionConnectProc();
                                     appserverInfo.disconnectProc = msAppserver.getSessionDisconnProc();
                                     appserverInfo.startupProc = msAppserver.getAgentStartupProc();
                                     appserverInfo.shutdownProc = msAppserver.getAgentShutdownProc();
                                     appserverInfo.startupProcParams = msAppserver.getAgentStartupProcParam();
                                  }
                                  else
                                  {
                                     ClassicAppserverDefinition classicAppserver =
                                        (ClassicAppserverDefinition) def;
                                     appserverInfo.isMultiSession = classicAppserver.isMultiSession();
                                     appserverInfo.operatingMode = classicAppserver.getOperatingMode().toString();
                                     appserverInfo.initialServers = classicAppserver.getInitialAgents();
                                     appserverInfo.minServers = classicAppserver.getMinAgents();
                                     appserverInfo.maxServer = classicAppserver.getMaxAgents();
                                     appserverInfo.requestTimeout = classicAppserver.getRequestTimeout();
                                     appserverInfo.autoTrimTimeout = classicAppserver.getAutoTrimTimeout();
                                     appserverInfo.activateProc = classicAppserver.getActivate();
                                     appserverInfo.deactivateProc = classicAppserver.getDeactivate();
                                     appserverInfo.connectProc = classicAppserver.getConnect();
                                     appserverInfo.disconnectProc = classicAppserver.getDisconnect();
                                     appserverInfo.startupProc = classicAppserver.getStartup();
                                     appserverInfo.shutdownProc = classicAppserver.getShutdown();
                                     appserverInfo.startupProcParams = classicAppserver.getStartupParameter();
                                  }

                                  appserverInfo.agentInfos = getAppserverAgentInfos(def.getAppserverName());

                                  LegacyLogManagerConfigs legacyLogManagerConfigs =
                                     LegacyLogOps.appserverNameLogConfigPairs.get(appserverInfo.name);
                                  appserverInfo.logFilename = legacyLogManagerConfigs.getLogFile();
                                  try
                                  {
                                     appserverInfo.loggingLevel =
                                        Integer.parseInt(legacyLogManagerConfigs.getLevelString());
                                  }
                                  catch (NumberFormatException ignored)
                                  {
                                  }
                                  appserverInfo.logFileThresholdSize = legacyLogManagerConfigs.getFileSize();
                                  appserverInfo.logFilesMaxNumber = legacyLogManagerConfigs.getNumLogFiles();
                                  return appserverInfo;
                               })
                          .toArray(AppserverInfo[]::new);
          }
          catch (Exception e)
          {
             LOG.warning("getAppserverList", e);
             throw e;
          }
       }

       /**
        * Returns the list of appserver agents.
        *
        * @param    appserver
        *           The appserver name.
        *
        * @return   Array of appserver agent definitions.
        */
       public static AppserverInfo.AgentInfo[] getAppserverAgentInfos(String appserver)
       {
          AppserverInfo.AgentInfo[] agentInfos = null;

          if (MultiSessionAppserverDefinition.get(appserver) != null)
          {
             MultiSessionAppserverManager msaManager = MultiSessionAppserverManager.getInstance();
             MultiSessionAgentInfo msAgentInfo = msaManager.getAgentInfo(appserver);

             agentInfos = new AppserverInfo.AgentInfo[1];
             AppserverInfo.AgentInfo agentInfo = new AppserverInfo.AgentInfo();
             agentInfo.appserverName = appserver;
             if (msAgentInfo != null)
             {
                agentInfo.agentStartTime = msAgentInfo.getStartupTime();
                agentInfo.agentId = msAgentInfo.getAgentId();
                agentInfo.sessionInfos = msAgentInfo.getMsaSessionInfos();
             }

             agentInfos[0] = agentInfo;
             return agentInfos;
          }

          AgentPool agentPool = AppServerManager.getAgentPoolForAppserver(appserver);
          if (agentPool != null)
          {
             Set<Agent> agents = agentPool.getAll();
             agentInfos = new AppserverInfo.AgentInfo[agents.size()];

             int index = 0;
             for (Agent agent : agents)
             {
                AppserverInfo.AgentInfo agentInfo = new AppserverInfo.AgentInfo();
                agentInfo.appserverName = appserver;
                agentInfo.pid = agent.getPid();
                agentInfo.agentStartTime = agent.getStartupTime();
                agentInfo.sessionStartTime = agent.getRunningProcStartTime();
                agentInfo.sessionProcedure = agent.getRunningProc();
                agentInfo.agentId = agent.getAgentId();
                agentInfo.bound = agent.isBound();
                agentInfo.state = agent.getState();
                agentInfos[index++] = agentInfo;
             }
          }
          return agentInfos;
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /** instance of the directory service */
          private DirectoryService ds = null;

          /** if true, the session is bound to the directory service */
          private boolean bound = false;

          /** if true, the target is set */
          private boolean targeted = false;

          /** if true, the live directory is the target */
          private boolean liveTarget = false;

          /** variations of the live target: latest(true) or session */
          private boolean stayCurrent = false;

          /**
           * If true, the current context is already initialized via setTargetLive().
           * This affects only REST calls.
           */
          private boolean initialized = false;

          /** instance of SecurityAdmin for this session for live target */
          private SecurityAdmin sa = null;

          /** list of fresh messages: elements are string arrays */
          private LinkedList<String[]> freshMessages = new LinkedList<String[]>();

          /** list of saved messages: elements are string arrays */
          private LinkedList<String[]> savedMessages = new LinkedList<String[]>();

          /** list of locked records */
          private Set<RecordInfo> lockedRecords = null;

          /** Current refresh counter. */
          private Integer refresh = Integer.valueOf(0);

          /**
           * Constructor.
           */
          public WorkArea()
          {
          }

          /**
           * Increment refresh counters.
           */
          public void resetCounters()
          {
             setRefreshCounters(0);
          }

          /**
           * Increment refresh counters.
           *
           * @param    rc
           *           directory commit return code.
           *
           * @return   The boolean value inidicating the success of this operation
           */
          public boolean incrementCounters(boolean rc)
          {
             if (rc)
                setRefreshCounters(-1);

             return rc;
          }

          /**
           * Set refresh counters.
           *
           * @param    i
           *           New value for the counters. If this parameter is less than 0,
           *           then both counters are incremented.
           */
          private void setRefreshCounters(int i)
          {
             synchronized(refreshMutex)
             {
                if (i < 0)
                   i = refreshGlobal.intValue() + 1;

                refresh  = Integer.valueOf(i);
                refreshGlobal = Integer.valueOf(i);
             }
          }

          /**
           * Check if there is pending refresh.
           *
           * @return   {@code true} if refresh is pending and {@code false}
           *           is there is no pending refresh.
           */
          public boolean isRefreshPending()
          {
             synchronized (refreshMutex)
             {
                Integer local = refresh;

                if (local == null || local.intValue() == 0)
                   return false;

                // local counter is not null and is above 0
                if (refreshGlobal.intValue() == 0)
                   return false;

                // global counter is above 0 too, check permissions
                return sa.canRefresh();
             }
          }
       }
    }