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();
         }
      }
   }
}