DirectoryService.java

/*
** Module   : DirectoryService.java
** Abstract : directory front-end implementation.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050218   @20018 Created initial version.
** 002 SIY 20050316   @20357 Implemented missing functionality. Many other
**                           changes and many methods written at first 
**                           stage were rewritten to take into account
**                           locking, batch editing and "/meta" subtree 
**                           processing.
** 003 SIY 20050317   @20459 Fixed getNodeXXXs() methods and disable 
**                           result code check in the NodeRemover.
** 004 SIY 20050328   @20577 Cleaned up code and fixed comments,
**                           enforced binding check.
** 005 SIY 20050406   @21010 Replaced XmlRemapper with RamRemapper where
**                           appropriate.             
** 006 NVS 20050422   @20869 Batch editing is now done directly with
**                           DirectoryService calls. DirectoryService
**                           notifies SecurityManager in case of openBatch
**                           and closeBatch so the latter can keep track
**                           of /security branch edits. Also, isEditing
**                           method queries the batch status of Security
**                           Manager by calling its isEditing method.
** 007 NVS 20050426   @20893 Fixed bug in getNodeAttributes method where
**                           there was no filtering by E permission which
**                           is required.
** 008 SIY 20050427   @21010 Use SecurityManager to hold per-session 
**                           variables. Many smaller changes.
** 009 SIY 20050505   @21191 Unified construction of the back-ends and 
**                           moved it to the new static private package 
**                           method. Schema initialization moved in 
**                           separate static package private method.
**                           Added handlers for SecurityManager-driven 
**                           cleanup. Refactored methods to minimize code
**                           duplication. Changed batch editing handling.
**                           Extended documentation.
** 010 SIY 20050518   @21235 Fixed incomplete cleanup after abnormal 
**                           session termination.
** 011 SIY 20050524   @21264 Fixed access rights check, updated batch 
**                           rollback.
** 012 NVS 20050831   @22442 Changed the logic of debug output and added
**                           getServerId() method.
** 013 ECF 20060123   @24032 Fix to bind() method. Reference counting for
**                           binds could never rise above 1.
** 014 GES 20061215   @31704 Changes in bootstrap config interface.
** 015 GES 20070112   @31809 Changes in createInstance signature.
** 016 NVS 20070316   @32415 Added closeBatch() method with controlled
**                           security cache refresh.
** 017 ECF 20071024   @35526 Added context-local bind reference counting.
**                           Replaced deprecated, SecurityManager context
**                           related code with ContextLocal variables.
** 018 ECF 20071027   @35588 Fixed regression introduced by #017 (@35526).
**                           Added safety check to avoid NPE in context
**                           local cleanup methods.
** 019 SIY 20090514   @42184 Added refresh() method, some minor cleanups.
** 020 SIY 20090529   @42496 Added copyNode() method.
** 021 SIY 20090923   @43986 Made isBound() public.
** 022 SIY 20090923   @44006 openBatch() is rewritten in order to minimized
**                           synchronization. 
** 023 NVS 20090928   @44045 rollBack() returns failure/success indication.
**                           Failure in rollBack() throws ConfigurationException
** 024 SIY 20091005   @44125 Reworked entire batch closing algorithm in order 
**                           to make it more safe/reliable. Reworked 
**                           transaction rollback. Most functionality is moved 
**                           to ShadowRemapper.
** 025 SIY 20091027   @44259 refresh() split into three separate methods.
** 026 SIY 20091118   @44416 Fixed ExpirableLock() behavior.
** 027 SIY 20091118   @44423 Added backupDirectory() method.
** 028 GES 20101116          Modified how the schema is loaded to load it from
**                           a JAR resource AND to split it into standard and
**                           extention portions.
** 029 OM  20121016          Fixed issue 1577 (methods called by 
**                           Cleanable.cleanup() code must not rely on 
**                           context-local variables)
** 030 CA  20140513          Added a weight for the context-local var, to ensure predetermined 
**                           order during context reset.
** 031 EVL 20160222          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 032 IAS 20160331          Made guard object final. Fixed positive random integer generation
**                           and removed incorrect lazy initialization of the LOG
** 033 SBI 20170224          Added addNodeAttributes method.
** 034 SVL 20180530          canAccessNode was made public.
** 035 TJD 20220504          Java 11 compatibility minor changes
** 036 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 037 SP  20250416          Code formatting and javadoc fixes.
*/

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

import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.*;

import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.util.logging.*;

/**
 * This class implements all logic of the P2J Directory Service.
 * <p>
 * Class <code>DirectoryService</code> provides front-end functionality for
 * one instance of the back-end. The type of the back-end is determined at
 * startup by reading configuration file.
 * <p>
 * <code>DirectoryService</code> interacts with the
 * <code>SecurityManager</code> for several purposes. First of all it uses
 * <code>ContextLocal</code> variables (backed by the
 * <code>SecurityManager</code>) to track clients which are working with the
 * P2J Directory. For each client separate bind status and batch editing
 * session status is tracked. If the client interrupts its session abnormally
 * then the active session is automatically closed and the open batch editing
 * transaction is rolled back.
 * <p>
 * During work <code>DirectoryService</code> can create temporary pseudo
 * back-ends of type <code>ShadowRemapper</code>. These back-ends are used
 * to track changes during batch editing and created when application opens
 * batch editing session. When batch editing session is closed, depending on
 * the closing mode (commit/rollback) changes are moved to the real back-end
 * or just skipped. The implementation is designed so all changes made during
 * editing session are visible to the application which did open that session
 * as if they are actually performed. Other clients don't see changes until
 * they are actually committed.
 * <p>
 * Beside implementation of the front-end interface
 * <code>DirectoryService</code> class provides several static helper
 * methods used by other members of the package, in particular
 * <code>DirectoryCopy</code> and <code>ShadowRemapper</code>.
 * 
 * @author  SIY
 */
public final class DirectoryService
{
   /** Batch activation synchronization object */
   private static Object batchSynch = new Object();

   /** Reference counter synchronization object */
   private static Object bindSync = new Object();

   /** Instance of the DirectoryService */
   private static DirectoryService directory = null;
   
   /** Context-local WorkArea instance */
   private static final ContextLocal<WorkArea> clwa = new ContextLocal<WorkArea>()
   {
      @Override
      public WeightFactor getWeight()
      {
         return WeightFactor.LAST;
      };
      
      @Override
      protected WorkArea initialValue() 
      {
         return new WorkArea();
      }
      
      @Override
      protected void cleanup(WorkArea workArea) 
      {
         if (workArea != null) 
         {
            workArea.cleanup();
         }
      }
   }; 
   
   /** {@link #backupDirectory(String)} call was successful. */
   public static final int OK = 0;

   /** Base error code. */
   private static final int ERR_BASE = 1;
   
   /** Error code which indicates that editing is in progress. */
   public static final int ERR_NO_LOCK = ERR_BASE + 1;

   /** Error code which indicates that directory lock can't be placed. */
   public static final int ERR_LOCK_EXPIRED = ERR_BASE + 2;

   /** Error code which indicates that backend can't perform refresh. */
   public static final int ERR_CANT_REFRESH_BACKEND = ERR_BASE + 3;

   /** {@link #backupDirectory(String)} call is not supported. */
   public static final int ERR_SAVE_NOT_SUPPORTED = ERR_BASE + 4;
   
   /** Writing error happened during {@link #backupDirectory(String)} call. */
   public static final int ERR_CANT_WRITE = ERR_BASE + 5;
   
   /** {@link #backupDirectory(String)} cant rename file. */
   public static final int ERR_CANT_RENAME = ERR_BASE + 6;
   
   /** {@link #backupDirectory(String)} failed because editing is active. */
   public static final int ERR_EDITING_IS_ACTIVE = ERR_BASE + 7;
   
   /** {@link #backupDirectory(String)} failed because it can't obtain the lock. */
   public static final int ERR_CANT_LOCK_DIRECTORY = ERR_BASE + 8;
   
   /** {@link #backupDirectory(String)} failed because of fatal backend error. */
   public static final int ERR_FATAL = ERR_BASE + 9;
   
   /** The logger instance. */
   private static CentralLogger LOG = CentralLogger.get(DirectoryService.class);

   /** Reference to underlying back-end */
   protected Remapper backend = null;

   /** An object which performs all tracking of the locks */
   protected LockManager lockManager = null;

   /** Synchronization object for refresh handling. */
   private final Object refreshLock = new Object();
   
   /** Temporary batch storage when SecurityManager is not present */
   private BatchRef tempActiveBatch = null;

   /** Initialized flag that controls access check methods */
   private boolean initialized = false;

   /** Information about classes information */
   private Remapper metaData = null;

   /** bind()/unbind() call counter */
   private int refCount = 0;

   /** server's own ID */
   private String serverId = null;

   /** Current refresh expirable lock (if any). */
   private ExpirableLock refreshExpirableLock = null;

   /** ID of the refreshing thread. */
   private long refreshingId = 0;

   /**
    * Construct an instance using the given configuration to instantiate
    * the directory back-end.
    * 
    * @param    config
    *           Configuration information.
    *
    * @throws   ConfigurationException
    *           In case of any configuration error.
    */
   private DirectoryService(BootstrapConfig config)
   throws ConfigurationException
   {
      if (!config.isServer())
      {
         throw new ConfigurationException("Not a server configuration");
      }

      serverId = config.getConfigItem("security", "server", "id");

      SchemaLoad.initSchema();
      init();

      backend = genRemapper(config);
      
      synchronized (refreshLock)
      {
         initMetaInfo();
      }

      if (!rollBack())
      {
         throw new ConfigurationException("Can't initialize directory");
      }
   }

   /**
    * Returns the current singleton instance if it is already initialized or
    * creates a new instance using the current configuration to instantiate
    * the back-end.
    * 
    * @param    cfg
    *           Configuration information.
    *
    * @return   The new instance.
    *
    * @throws   ConfigurationException
    *           In case of configuration error.
    */
   public static synchronized DirectoryService createInstance(BootstrapConfig cfg)
   throws ConfigurationException
   {
      if (directory == null)
      {
         directory = new DirectoryService(cfg);
      }
      return directory;
   }

   /**
    * Returns <code>DirectoryService</code> instance.
    * 
    * @return  DirectoryService instance.
    */ 
   public static synchronized DirectoryService getInstance()
   {
      return directory;
   }

   /**
    * Copy subtree from the source location (back-end and source node ID) to
    * destination (back-end and destination node ID). The root of the subtree
    * (pointed by the source node ID) is copied into destination node ID, and
    * so on.
    * 
    * @param   source
    *          The backend from which subtree will be copied.
    * @param   srcBase
    *          An ID of the root node of the source subtree.
    * @param   destination
    *          The backend to which subtree will be copied.
    * @param   dstBase
    *          An ID of the root node in the destination backend.
    * @param   recursive
    *          <code>true</code> enables recursive copying of children.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   protected static boolean copySubtree(Remapper source, 
                                        String srcBase,
                                        Remapper destination, 
                                        String dstBase, 
                                        boolean recursive)
   {
      NodeCopier proc = new NodeCopier(srcBase, destination, dstBase, recursive);

      Object res = traverseTree(source, srcBase, proc, true);
      if (res == null)
      {
         return true;
      }

      boolean result = false;
      if (res instanceof Boolean)
      {
         result = ((Boolean) res).booleanValue();
      }

      if (!result)
      {
         logWarning("Failed copy of subtree from '" + srcBase + "' to '" + dstBase + "'");
      }

      return result;
   }

   /**
    * Retrieve entire attribute from specified node.
    * 
    * @param   remapper
    *          Back-end instance.
    * @param   nodeId
    *          Node ID
    * @param   name
    *          Attribute name
    *
    * @return  New instance of the <code>Attribute</code> or
    *          <code>null</code> if there is no such node or no such
    *          attribute name.
    */
   protected static Attribute getNodeAttribute(Remapper remapper,
                                               String nodeId, String name)
   {
      Attribute res = genEmptyAttribute(remapper, nodeId, name);
      do
      {
         if (res == null)
         {
            break;
         }

         boolean result;
         do
         {
            result = getSingleAttributeValue(remapper, nodeId, res);
         }
         while (result == true);

         if (res.getCount() == 0)
         {
            res = null;
         }
      }
      while (false);

      return res;
   }
   
   /**
    * Main logging entry point.  Do NOT call this before the directory is
    * initialized as logging is not available until that time.
    * 
    * @param   level
    *          Debug level of this message.
    * @param   message
    *          Text of the message.
    */
   protected static void log(Level level, String message)
   {
      log(level, message, null);
   }
   
   /**
    * Main logging entry point.  Do NOT call this before the directory is
    * initialized as logging is not available until that time.
    * 
    * @param   level
    *          Debug level of this message.
    * @param   message
    *          Text of the message.
    * @param   t
    *          Cause of the problem.
    */
   protected static void log(Level level, String message, Throwable t)
   {
      LOG.log(level, message, t);
   }
   
   /**
    * Log message as debug (sensitive trace) entry.
    * 
    * @param   string
    *          Message text.
    */
   protected static void logDebug(String string)
   {
      log(Level.FINEST, string);
   }

   /**
    * Log message as error entry.
    * 
    * @param   string
    *          Message text.
    */
   protected static void logError(String string)
   {
      log(Level.SEVERE, string);
   }
   
   /**
    * Log message as error entry.
    * 
    * @param   string
    *          Message text.
    * @param   t
    *          Cause of the error.
    */
   protected static void logError(String string, Throwable t)
   {
      log(Level.SEVERE, string, t);
   }
   
   /**
    * Log message as list entry.
    * 
    * @param   string
    *          Message text.
    */
   protected static void logList(String string)
   {
      log(Level.FINE, string);
   }
   
   /**
    * Log message as trace entry.
    * 
    * @param   string
    *          Message text.
    */
   protected static void logTrace(String string)
   {
      log(Level.FINER, string);
   }

   /**
    * Log message as warning entry.
    * 
    * @param   string
    *          Message text.
    */
   protected static void logWarning(String string)
   {
      log(Level.WARNING, string);
   }

   /**
    * Prepare node for the setting of all attribute values.
    * 
    * @param   remapper
    *          Back-end on which operation will be performed.
    * @param   nodeId
    *          A node whose attribute will be updated
    * @param   name
    *          Node attribute name
    *
    * @return  <code>0</code> in case of error, <code>1</code> if
    *          attribute is mandatory, <code>2</code> if attribute is
    *          ordinary.
    */
   protected static int prepareNodeAttribute0(Remapper remapper,
                                              String nodeId, String name)
   {
      Attribute attr = genEmptyAttribute(remapper, nodeId, name);
      if (attr == null) //No such node or no such attribute
      {
         return 0;
      }

      if (attr.getDefinition().isMandatory())
      {
         //Delete node values one by one
         while (remapper.deleteNodeAttributeValue(nodeId, name, 0))
            ;

         return 1;
      }

      //Ignore result here because it does not matter.
      remapper.deleteNodeAttribute(nodeId, name);

      return 2;
   }

   /**
    * Remove subtree from the specified back-end starting from specified
    * subtree root node ID. Subtree root node is also removed unless
    * <code>preserveLock</code> parameter is set to <code>true</code>. In
    * this case the subtree root, and any "backup" and "lock" child nodes (and
    * appropriate subtrees, if there are any) are preserved.
    * 
    * @param   remapper
    *          Back-end where subtree is stored.
    * @param   nodeId
    *          Subtree starting point.
    * @param   preserveLock
    *          Preserve child nodes with names "lock" and "backup".
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   protected static boolean pruneSubtree(Remapper remapper, String nodeId,
                                         boolean preserveLock)
   {
      NodeRemover proc = new NodeRemover();

      boolean skipBackup = !nodeId.startsWith("/backup");
      
      if (!preserveLock)
      {
         if (traverseTree(remapper, nodeId, proc, false) != null)
         {
            return false;
         }
      }
      else
      {
         String[] nodes = remapper.enumerateNodes(nodeId);

         if (nodes == null)
         {
            return true;
         }

         for (int i = 0; i < nodes.length; i++)
         {
            if (!skipBackup && nodes[i].equals("backup"))
            {
               continue;
            }
            
            if (nodes[i].equals("lock") && remapper.getNodeClassName(nodeId + "/lock").equals("lock"))
            {
               continue;
            }
            if (traverseTree(remapper, nodeId + "/" + nodes[i], proc, false) != null)
            {
               return false;
            }
         }
      }

      return true;
   }
   
   /**
    * Add node at specified location. The node is created if it does not
    * exists or all attributes of the existing node are replaced. Note that
    * when node already exists then node object class name must match object
    * class name passed as a parameter.
    * 
    * @param   remapper
    *          Remapper where node will be added or replaced.
    * @param   nodeId
    *          ID of the node to add/replace
    * @param   nodeClass
    *          Name of the Object Class of the created node.
    * @param   data
    *          List of attributes for the node.
    * @param   canReplace
    *          Allow replacing existing nodes.
    * @param   checkLeafs
    *          Check parent node class to make sure that it is not leaf.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   static boolean addNode(Remapper remapper, String nodeId, String nodeClass,
                          Attribute[] data, boolean canReplace,
                          boolean checkLeafs)
   {
      if (checkLeafs)
      {
         if (nodeId.equals(""))
         {
            return true;
         }
         
         String[] path = IdUtils.splitId(nodeId);
         if (path == null) //Invalid path or something
         {
            return false;
         }
         
         String oldClass = remapper.getNodeClassName(path[0]);
         if (oldClass == null)
         {
            return false;  //No such node
         }
         
         if (remapper.isClassLeaf(oldClass))
         {
            return false;
         }
      }   
      
      String oldClass = remapper.getNodeClassName(nodeId);

      if (oldClass != null)
      {
         if (!canReplace)
         {
            return false;
         }

         if (!oldClass.equals(nodeClass))
         {
            return false;
         }

         return setNodeAttributes(remapper, nodeId, data);
      }

      ArrayList<String> names = new ArrayList<String>();
      ArrayList<Object> values = new ArrayList<Object>();

      if (data != null)
      {
         for (int i = 0; i < data.length; i++)
         {
            String name = data[i].getName();

            for (int j = 0; j < data[i].getCount(); j++)
            {
               names.add(name);
               values.add(data[i].getValue(j));
            }
         }
      }

      return remapper.addNode(nodeId, nodeClass,
                              names.toArray(new String[names.size()]),
                              values.toArray());
   }

   /**
    * Construct an back-end instance using provided configuration information.
    * <p>
    * The back-end instantiated using Java reflection. There are two possible
    * ways in which name of the class is determined. If user specifies
    * <b>class </b> configuration variable then it must represent full class
    * name (including package name) and is used as is. If user specifies
    * <b>type </b> configuration variable then class name is constructed as
    * follows: given back-end alias provided in <b>type </b> configuration
    * variable converted to lower case and its first character converted to
    * upper case. Then to alias added prefix - full package name (
    * <b>com.goldencode.p2j.directory </b>) and suffix - <b>Remapper </b>. For
    * example: <br>
    * <br>
    * <b>ldap </b> is converted to
    * <b>com.goldencode.p2j.directory.LdapRemapper </b>, <br>
    * <b>xml </b> is converted to <b>com.goldencode.p2j.directory.XmlRemapper
    * </b>.
    * <p>
    * The <b>class </b> configuration variable has higher priority than
    * <b>type </b> configuration variable and if both variables are specified
    * then <b>class </b> variable is used.
    * <p>
    * Regardless from the method in which back-end class name is constructed,
    * the class itself must meet two main requirements: 1) it must implement
    * <code>Remapper</code> interface, 2) it must have constructor which
    * accepts one parameter (reference to <code>BootstrapConfig</code>
    * instance) accessible from the <b>com.goldencode.p2j.directory </b>
    * package.
    * 
    * @param   config
    *          Configuration data.
    *
    * @return  New Remapper instance.
    *
    * @throws  ConfigurationException
    *          In case of configuration error.
    */
   static Remapper genRemapper(BootstrapConfig config)
   throws ConfigurationException
   {
      String name = config.getConfigItem("directory", "backend", "type");
      String clName = config.getConfigItem("directory", "backend", "class");
      if (name == null)
      {
         throw new ConfigurationException("Back-end type is not undefined");
      }
      if (name.length() < 3)
      {
         throw new ConfigurationException("Back-end type is invalid");
      }
      
      name = "com.goldencode.p2j.directory." + IdUtils.upcaseFirst(name) + "Remapper";
      if (clName != null && clName.length() > 0)
      {
         name = clName;
      }
      
      try
      {
         Class<?> aClass = Class.forName(name);
         
         Constructor<?>[] list = aClass.getDeclaredConstructors();
         
         for (int i = 0; i < list.length; i++)
         {
            Class<?>[] parms = list[i].getParameterTypes();
            
            if (parms.length == 1 && parms[0].equals(BootstrapConfig.class))
            {
               return (Remapper)list[i].newInstance(new Object[] { config });      
            }
         }
         
         throw new ConfigurationException("Unable to find suitable " +
                                          "constructor for " + name);
      }
      catch (Exception e)
      {
         if (e instanceof ConfigurationException)
            throw (ConfigurationException)e;
         
         throw new ConfigurationException("Unable to instantiate back-end", e);
      }
   }

   /**
    * Returns the entire set of attributes with all their values, or
    * <code>null</code> if request fails.
    * 
    * @param   remapper
    *          Remapper instance which will be used to perform operation.
    * @param   nodeId
    *          Node for which list of attributes is requested.
    *
    * @return  Array of attributes or <code>null</code>.
    */
   static Attribute[] getNodeAttributes(Remapper remapper, String nodeId)
   {
      String cName = remapper.getNodeClassName(nodeId);
      if (cName == null)
      {
         return null;
      }
      ObjectClass objClass = SchemaStorage.getSchema().getObjectClass(cName);
      if (objClass == null)
      {
         return null;
      }
      AttributeDefinition[] attrDefs = objClass.getClassDefinition();
      if (attrDefs == null)
      {
         return null;
      }

      ArrayList<Attribute> res = new ArrayList<Attribute>();

      for (int i = 0; i < attrDefs.length; i++)
      {
         Attribute attr = getNodeAttribute(remapper, nodeId, attrDefs[i].getName());
         if (attr == null || attr.getCount() == 0)
         {
            continue;
         }
         res.add(attr);
      }
      return res.toArray(new Attribute[res.size()]);
   }

   /**
    * Generate new empty (without values) instance of the
    * <code>Attribute</code> for specified node and attribute name.
    * 
    * @param   remapper
    *          A back-end on which operation will be performed.
    * @param   nodeId
    *          Node ID
    * @param   name
    *          Attribute name
    * @return  New instance of the <code>Attribute</code> or
    *          <code>null</code> if there is no such node or no such
    *          attribute name.
    */
   private static Attribute genEmptyAttribute(Remapper remapper,
                                              String nodeId, String name)
   {
      if (nodeId == null || name == null)
      {
         return null;
      }

      String cName = remapper.getNodeClassName(nodeId);
      if (cName == null)
      {
         return null;
      }
      
      ObjectClass objClass = SchemaStorage.getSchema().getObjectClass(cName);
      if (objClass == null)
      {
         return null;
      }
      
      AttributeDefinition def = objClass.getAttributeDefinition(name);
      if (def == null)
      {
         return null;
      }

      return new Attribute(def.genNodeAttribute(), null);
   }

   /**
    * Retrieve one attribute value using attribute type specific call.
    * 
    * @param   remapper
    *          Remapper instance which will be used to perform operation.
    * @param   node
    *          Node ID
    * @param   attr
    *          Attribute which will receive new value.
    * @return  <code>true</code> if operation was successful.
    */
   private static boolean getSingleAttributeValue(Remapper remapper,
                                                  String node, Attribute attr)
   {
      int ndx = attr.getCount();
      String name = attr.getName();

      //Select proper get* method...
      switch (attr.getDefinition().getType())
      {
         case AttributeType.ATTR_INTEGER :
            return attr.addValue(remapper.getNodeInteger(node, name, ndx));

         case AttributeType.ATTR_BOOLEAN :
            return attr.addValue(remapper.getNodeBoolean(node, name, ndx));

         case AttributeType.ATTR_STRING :
            return attr.addValue(remapper.getNodeString(node, name, ndx));

         case AttributeType.ATTR_DOUBLE :
            return attr.addValue(remapper.getNodeDouble(node, name, ndx));

         case AttributeType.ATTR_BYTEARRAY :
            return attr.addValue(remapper.getNodeByteArray(node, name, ndx));

         case AttributeType.ATTR_BITFIELD :
            return attr.addValue(remapper.getNodeBitField(node, name, ndx));

         case AttributeType.ATTR_BITSELECTOR :
            return attr.addValue(remapper.getNodeBitSelector(node, name, ndx));

         case AttributeType.ATTR_DATE :
            return attr.addValue(remapper.getNodeDate(node, name, ndx));

         case AttributeType.ATTR_TIME :
            return attr.addValue(remapper.getNodeTime(node, name, ndx));
      }

      return false;
   }

   /**
    * Set one attribute to the node.
    * 
    * @param   remapper
    *          Backend on which operation will be performed.
    * @param   nodeId
    *          Node ID.
    * @param   data
    *          New attribute value.
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   private static boolean setNodeAttribute(Remapper remapper, String nodeId,
                                           Attribute data)
   {
      int rc = prepareNodeAttribute0(remapper, nodeId, data.getName());
      
      boolean res = false;

      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }

         int start = 0;

         if (rc == 1) //Mandatory attribute
         {
            switch (data.getDefinition().getType())
            {
               case AttributeType.ATTR_INTEGER :
                  res = remapper.setNodeInteger(nodeId, data.getName(), start,
                                 ((Integer) data.getValue(start)).intValue());
                  break;
               case AttributeType.ATTR_BOOLEAN :
                  res = remapper.setNodeBoolean(nodeId, data.getName(), start,
                             ((Boolean) data.getValue(start)).booleanValue());
                  break;
               case AttributeType.ATTR_STRING :
                  res = remapper.setNodeString(nodeId, data.getName(), start,
                                               (String) data.getValue(start));
                  break;
               case AttributeType.ATTR_DOUBLE :
                  res = remapper.setNodeDouble(nodeId, data.getName(), start,
                               ((Double) data.getValue(start)).doubleValue());
                  break;
               case AttributeType.ATTR_BYTEARRAY :
                  res = remapper.setNodeByteArray(nodeId, data.getName(), 
                                                  start,
                                             ((byte[]) data.getValue(start)));
                  break;
               case AttributeType.ATTR_BITFIELD :
                  res = remapper.setNodeBitField(nodeId, data.getName(), start,
                                             (BitField) data.getValue(start));
                  break;
               case AttributeType.ATTR_BITSELECTOR :
                  res = remapper.setNodeBitSelector(nodeId, data.getName(),
                                   start, (BitSelector) data.getValue(start));
                  break;
               case AttributeType.ATTR_DATE :
                  res = remapper.setNodeDate(nodeId, data.getName(), start,
                                             (DateValue) data.getValue(start));
                  break;
               case AttributeType.ATTR_TIME :
                  res = remapper.setNodeTime(nodeId, data.getName(), start,
                                             (TimeValue) data.getValue(start));
                  break;
            }

            if (res == false)
            {
               break;
            }

            start++;
         }

         for (; start < data.getCount(); start++)
         {
            switch (data.getDefinition().getType())
            {
               case AttributeType.ATTR_INTEGER :
                  res = remapper.addNodeInteger(nodeId, data.getName(),
                                 ((Integer) data.getValue(start)).intValue());
                  break;
               case AttributeType.ATTR_BOOLEAN :
                  res = remapper.addNodeBoolean(nodeId, data.getName(),
                             ((Boolean) data.getValue(start)).booleanValue());
                  break;
               case AttributeType.ATTR_STRING :
                  res = remapper.addNodeString(nodeId, data.getName(),
                                               (String) data.getValue(start));
                  break;
               case AttributeType.ATTR_DOUBLE :
                  res = remapper.addNodeDouble(nodeId, data.getName(),
                               ((Double) data.getValue(start)).doubleValue());
                  break;
               case AttributeType.ATTR_BYTEARRAY :
                  res = remapper.addNodeByteArray(nodeId, data.getName(),
                                             ((byte[]) data.getValue(start)));
                  break;
               case AttributeType.ATTR_BITFIELD :
                  res = remapper.addNodeBitField(nodeId, data.getName(),
                                             (BitField) data.getValue(start));
                  break;
               case AttributeType.ATTR_BITSELECTOR :
                  res = remapper.addNodeBitSelector(nodeId, data.getName(),
                                          (BitSelector) data.getValue(start));
                  break;
               case AttributeType.ATTR_DATE :
                  res = remapper.addNodeDate(nodeId, data.getName(),
                                            (DateValue) data.getValue(start));
                  break;
               case AttributeType.ATTR_TIME :
                  res = remapper.addNodeTime(nodeId, data.getName(),
                                            (TimeValue) data.getValue(start));
                  break;
            }

            if (res == false)
            {
               break;
            }
         }
         
         if (start == data.getCount())
         {
            res = true;
         }
      }
      while (false);

      return res;
   }

   /**
    * Set all attributes on specified node.
    * 
    * @param   remapper
    *          An instance of <code>Remapper</code> on which operation will
    *          be performed.
    * @param   nodeId
    *          Node ID.
    * @param   data
    *          List of new attributes.
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   static boolean setNodeAttributes(Remapper remapper, String nodeId,
                                    Attribute[] data)
   {
      AttributeDefinition[] attrDefs = null;
      attrDefs = remapper.enumerateNodeAttributes(nodeId);

      if (data == null)
      {
         if (attrDefs == null)
         {
            return true;
         }
         for (int i = 0; i < attrDefs.length; i++)
         {
            //Ignore errors here
            remapper.deleteNodeAttribute(nodeId, attrDefs[i].getName());
         }
         
         return true;
      }

      for (int i = 0; i < data.length; i++)
      {
         if (!setNodeAttribute(remapper, nodeId, data[i]))
         {
            return false;
         }
      }
      
      //Remove unused attributes
      for (int i = 0; attrDefs != null && i < attrDefs.length; i++)
      {
         boolean present = false;
         
         for (int j = 0; j < data.length; j++)
         {
            if(data[j].getName().equals(attrDefs[i].getName()))
            {
               present = true;
               break;
            }
         }
         
         if (!present)
         {
            //Ignore errors here
            remapper.deleteNodeAttribute(nodeId, attrDefs[i].getName());
         }
      }

      return true;
   }

   /**
    * Traverse entire subtree from specified node down to the leafs. In each
    * visited node call <code>visit()</code> method of provided
    * <code>NodeProcessor</code> object. If <code>visit()</code> returns
    * <code>null</code> then processing continues. If <code>visit()</code>
    * returns an instance of <code>Object</code> then processing is stopped
    * and that instance is returned.
    * <p>
    * Depending on the parameter method may traverse tree in <b>top-down </b>
    * or <b>bottom-up </b> manner. If <code>direction</code> parameter is
    * set to <code>true</code>( <b>top-down </b> mode) then node
    * <code>visit()</code> is called for the node <code>nodeId</code>
    * first and then for child nodes. If <code>direction</code> is set to
    * <code>false</code>( <b>bottom-up </b> mode) then <code>visit()</code>
    * is called for all child nodes and then for the <code>nodeId</code>
    * node. <br>
    * The <b>top-down </b> mode is convenient for operations such as backup,
    * when copy of the parent node should be created before copies of the
    * child nodes. <br>
    * The <b>bottom-up </b> mode is convenient for operations such as pruning
    * subtree, when child nodes must be removed before parent node.
    * <p>
    * This method does not check parameters for performance reasons so it is
    * caller responsibility to provide valid parameters (at least they should
    * not be <code>null</code>).
    * 
    * @param   remapper
    *          Remapper instance which will be traversed.
    * @param   nodeId
    *          Node ID to start traverse from.
    * @param   proc
    *          Instance of <code>NodeProcessor</code>
    * @param   direction
    *          Traversal direction flag. If it is set to <code>true</code>
    *          then <code>visit()</code> is called for parent node and then
    *          for each child node. If parameter is <code>false</code> then
    *          <code>visit()</code> is called for all child nodes and then
    *          for parent node.
    * @return  An instance of object returned from
    *          <code>NodeProcessor.visit()</code> or <code>null</code> if
    *          all nodes are visited.
    */
   private static Object traverseTree(Remapper remapper, String nodeId,
                                      NodeProcessor proc, boolean direction)
   {
      Object rc = null;
      if (direction)
      {
         rc = proc.visit(remapper, nodeId); // top-down order
      }

      if (rc != null)
      {
         return rc;
      }

      if (nodeId.equals("/"))
      {
         nodeId = "";
      }

      String[] nodes = remapper.enumerateNodes(nodeId);

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

      for (int i = 0; i < nodes.length; i++)
      {
         rc = traverseTree(remapper, nodeId + "/" + nodes[i], proc, direction);

         if (rc != null)
         {
            return rc;
         }
      }

      if (!direction)
      {
         rc = proc.visit(remapper, nodeId); // bottom-up order
      }

      return rc;
   }

   /**
    * Verify array of <code>boolean</code> values for duplicates.
    * 
    * @param   value
    *          Source array.
    * @return  <code>true</code> if array does not contain duplicates.
    */
   private static boolean verifyBooleanValues(boolean[] value)
   {
      if (value == null || value.length <= 1)
      {
         return true;
      }
      
      if (value.length > 2)
      {
         return false;
      }
      
      if (value[0] == value[1])
      {
         return false;
      }
      
      return true;
   }

   /**
    * Verify array of <code>double</code> values for duplicates.
    * 
    * @param   value
    *          Source array.
    * @return  <code>true</code> if array does not contain duplicates.
    */
   private static boolean verifyDoubleValues(double[] value)
   {
      if (value == null || value.length <= 1)
      {
         return true;
      }
      
      Double[] tmpValue = new Double[value.length];
      
      for (int i = 0; i < value.length; i++)
      {
         tmpValue[i] = value[i];
      }

      return verifyValues(tmpValue);
   }

   /**
    * Verify array of <code>integer</code> values for duplicates.
    * 
    * @param   value
    *          Source array.
    * @return  <code>true</code> if array does not contain duplicates.
    */
   private static boolean verifyIntegerValues(int[] value)
   {
      if (value == null || value.length <= 1)
      {
         return true;
      }
      
      Integer[] tmpValue = new Integer[value.length];
      for (int i = 0; i < value.length; i++)
      {
         tmpValue[i] = Integer.valueOf(value[i]);
      }

      return verifyValues(tmpValue);
   }

   /**
    * Verify array of values for duplicates.
    * 
    * @param   value
    *          Source array.
    *          
    * @return  <code>true</code> if array does not contain duplicates.
    */
   private static boolean verifyValues(Object[] value)
   {
      HashSet<Object> set = new HashSet<Object>();
      for (int i = 0; i < value.length; i++)
      {
         if (set.contains(value[i]))
         {
            return false;
         }
         
         set.add(value[i]);
      }
      
      return true;
   }

   /**
    * Refresh backend in one single step.
    * 
    * @return   <code>true</code> if refresh was successful.
    */
   public boolean atomicRefresh()
   {
      if (!lockForRefresh(100))
      {
         return false;
      }
      return (refresh() == 0);
   }

   /**
    * Lock directory for refreshing operation.
    * 
    * @param    seconds
    *           Seconds to wait before lock will expire.
    *           
    * @return   <code>true</code> if lock was successful.
    */
   public boolean lockForRefresh(int seconds)
   {
      // reject refreshing if editing session is active
      if (isEditing() || seconds <= 0)
      {
         return false;
      }
      
      Object lock = lockManager.enterLock("", LockManager.LOCK_RX);
      if (lock == null)
      {
         return false;
      }
      
      if (!lockManager.upgradeLock(lock))
      {
         lockManager.releaseLock(lock);
         return false;
      }
      
      synchronized (refreshLock)
      {
         refreshingId  = Thread.currentThread().getId();
         refreshExpirableLock = new ExpirableLock(lock, seconds);
      }
      
      return true;
   }
   
   /**
    * Refresh backend after placing a refresh lock.
    * 
    * @return   error code. Following error codes are possible:
    *           <ol>
    *           <li>{@link #ERR_NO_LOCK} - is returned when no lock is present</li>
    *           <li>{@link #ERR_LOCK_EXPIRED}- is returned if lock is present 
    *           but it is expired or belongs to other thread.</li>
    *           <li>{@link #ERR_CANT_REFRESH_BACKEND} - is returned when 
    *           backend can't update directory.</li>
    *           </ol>
    */
   public int refresh()
   {
      if (refreshExpirableLock == null)
      {
         return ERR_NO_LOCK;
      }

      synchronized (refreshLock)
      {
         // Actually it can be case, when there were no lock at all
         // in this thread, but we can't distinguish it.
         if (Thread.currentThread().getId() != refreshingId)
         {
            return ERR_LOCK_EXPIRED;
         }

         try
         {
            if (refreshExpirableLock.isExpired())
            {
               return ERR_LOCK_EXPIRED;
            }

            if (!backend.refresh())
            {
               return ERR_CANT_REFRESH_BACKEND;
            }

            initMetaInfo();
         }
         finally
         {
            cancelRefresh();
         }
      }
      
      return 0;
   }

   /**
    * Cancel planned refreshing operation.
    */
   public void cancelRefresh()
   {
      synchronized (refreshLock)
      {
         if (refreshExpirableLock != null &&
             Thread.currentThread().getId() == refreshingId)
         {
            refreshExpirableLock.unlock();
            refreshExpirableLock = null;
            refreshingId = 0;
         }
      }
   }
   
   /**
    * Returns the server identification string from the bootstrap 
    * configuration.
    *
    * @return  server identification string that may be used to locate server
    *          specific directory objects.
    */
   public String getServerId()
   {
      return serverId;
   }

   /**
    * Add new node of specified class with specified ID. For node
    * initialization list of attributes and corresponding list of attribute
    * values should be provided. The presence of the mandatory attributes in
    * the lists is obligatory.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>parent node has W permission;
    * <li>this node has C permission;
    * <li>all attributes have C permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node name.
    * @param   nodeClass
    *          Node class name.
    * @param   data
    *          A list of the attributes.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNode(String nodeId, String nodeClass, Attribute[] data)
   {
      nodeId = IdUtils.normalize(nodeId);
      if (nodeId == null)
      {
         return false;
      }

      Remapper remapper = synchBatch(nodeId, true);
      if (remapper == null)
      {
         return false;
      }

      // access rights check
      if (!canAccessNode(nodeId, DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_CREATE_ACCESS))
      {
         return false;
      }

      Object lock = null;
      if (remapper == backend)
      {
         lock = lockManager.enterLock(nodeId, LockManager.LOCK_RW);
      }

      // access rights check
      boolean access = true;
      if (data != null)
      {
         for (int i = 0; i < data.length; i ++)
         {
            if (!canAccessAttribute(data[i].getName(),
                                    DirectoryResource.DIR_CREATE_ACCESS,
                                    nodeClass))
            {
               access = false;
               break;
            }
         }
      }
 
      boolean res = false;
      if (access)
      {
         res = addNode(remapper, nodeId, nodeClass, data, false, true);
      }

      if (remapper == backend)
      {
         lockManager.releaseLock(lock);
      }

      return res;
   }

   /**
    * Add new node of specified class with specified ID. For node
    * initialization list of attributes and corresponding list of attribute
    * values should be provided. The presence of the mandatory attributes in
    * the lists is obligatory.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>parent node has W permission;
    * <li>this node has C permission;
    * <li>all attributes have C permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node name.
    * @param   nodeClass
    *          Node class name.
    * @param   data
    *          A map of an attribute name to its value.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeAttributes(String nodeId, String nodeClass, Map<String, Object[]> data)
   {
      ArrayList<Attribute> attributes = new ArrayList<Attribute>(data.size());
      
      for(Entry<String, Object[]> entry : data.entrySet())
      {
         attributes.add(new Attribute(
                  getClassNodeAttribute(nodeClass, entry.getKey()), entry.getValue()));
      }
      
      return addNode(nodeId, nodeClass, attributes.toArray(new Attribute[attributes.size()]));
   }
   
   /**
    * Add new <code>BitField</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeBitField(String nodeId, String name, BitField value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
          
         if (res)
         {
            res = op.remapper.addNodeBitField(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>BitSelector</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeBitSelector(String nodeId, String name,
                                     BitSelector value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
          
         if (res)
         {
            res = op.remapper.addNodeBitSelector(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>boolean</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeBoolean(String nodeId, String name, boolean value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
          
         if (res)
         {
            res = op.remapper.addNodeBoolean(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>byte[]</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeByteArray(String nodeId, String name, byte[] value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeByteArray(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>DateValue</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeDate(String nodeId, String name, DateValue value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeDate(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>Double</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeDouble(String nodeId, String name, double value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeDouble(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>int</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeInteger(String nodeId, String name, int value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeInteger(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>String</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeString(String nodeId, String name, String value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeString(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Add new <code>TimeValue</code> value to the attribute.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has A permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean addNodeTime(String nodeId, String name, TimeValue value)
   {
      //Trade convenience for performance.
      
      AttributeAdder op = new AttributeAdder(nodeId, name); 

      boolean res = op.lock();

      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, -1);
         
         if (res)
         {
            res = op.remapper.addNodeTime(nodeId, name, value);
         }
      }

      op.release();
      
      return res;
   }

   /**
    * Initialize directory service for current <code>SecurityManager</code>
    * session.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @return  <code>true</code> if operation was successful.
    */
   public boolean bind()
   {
      logTrace("Entering bind()");
      
      // no special permissions required for this API
      if (!initialized)
      {
         return newRef();
      }
      
      BindRef bindRef = clwa.get().bound;
      if (bindRef == null)
      {
         bindRef = new BindRef();
         clwa.get().bound = bindRef;
      }
      
      // increment context-local bind reference count
      bindRef.count++;

      //increments this.refCount
      return newRef();
   }
   
   /**
    * Close batch editing session assuming security cache refresh.
    * <p>
    * <code>closeBatch</code> call made for a batch against "/security" node
    * or anything under it, is subject to Security Manager notification.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @param   disposition
    *          If this parameter is <code>true</code> then changes need to
    *          be committed. Otherwise, they are just thrown away and lock is
    *          removed.
    *
    * @return  <code>true</code> if operation was successful and tree was
    *          successfully updated.
    */ 
   synchronized public boolean closeBatch(boolean disposition)
   {
      // no special permissions required for this API
      if (!isBound())
      {
         return false;
      }
      return closeBatchInt(getActiveBatch(), disposition, true);
   }

   /**
    * Close batch editing session.
    * <p>
    * <code>closeBatch</code> call made for a batch against "/security" node
    * or anything under it, is subject to Security Manager notification.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @param   disposition
    *          If this parameter is <code>true</code> then changes need to
    *          be committed. Otherwise they are just thrown away and lock is
    *          removed.
    * @param   refresh
    *          when committing the batch, tells whether to refresh the
    *          security cache or not
    *
    * @return  <code>true</code> if operation was successful and tree was
    *          successfully updated.
    */ 
   synchronized public boolean closeBatch(boolean disposition, 
                                          boolean refresh)
   {
      // no special permissions required for this API
      if (!isBound())
      {
         LOG.warning("Cannot close batch, current session is not bound to directory service.");
         return false;
      }

      return closeBatchInt(getActiveBatch(), disposition, refresh);
   }

   /**
    * Remove specified node from the directory (unless node has children).
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>parent node has W permission;
    * <li>this node has D permission;
    * <li>all attributes have D permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   public boolean deleteNode(String nodeId)
   {
      nodeId = IdUtils.normalize(nodeId);

      if (nodeId == null)
      {
         return false;
      }

      Remapper remapper = synchBatch(nodeId, true);

      if (remapper == null)
      {
         return false;
      }

      // access rights check
      if (!canAccessNode(nodeId, DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_DELETE_ACCESS))
      {
         return false;
      }

      Object lock = null;

      if (remapper == backend)
      {
         //lockManager.dumpLocks("deleting node start");
         lock = lockManager.enterLock(nodeId, LockManager.LOCK_RW);
      }

      // access rights check
      boolean access = true;
      Attribute[] data = getNodeAttributes(remapper, nodeId);
      if (data != null)
      {
         for (int i = 0; i < data.length; i ++)
         {
            if (!canAccessAttribute(nodeId, data[i].getName(),
                                    DirectoryResource.DIR_DELETE_ACCESS))
            {
               access = false;
               break;
            }
         }
      }

      boolean res = false;
      if (access)
      {
         res = remapper.deleteNode(nodeId);
      }
      else
      {
         res = false;
      }

      if (remapper == backend)
      {
         lockManager.releaseLock(lock);
         //lockManager.dumpLocks("deleting node end");
      }

      return res;
   }

   /**
    * Remove whole node attribute. Note that call will fail if attribute is
    * mandatory.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has D permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node name.
    * @param   name
    *          Attribute name.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   public boolean deleteNodeAttribute(String nodeId, String name)
   {
      AttributeRemover op = new AttributeRemover(nodeId, name);

      boolean res = op.lock();
      
      if (res)
      {
         res = op.remapper.deleteNodeAttribute(nodeId, name);
      }

      op.release();
      
      return res;
   }

   /**
    * Remove particular value from the node attribute. Note that removing last
    * value from the attribute deletes entire attribute unless attribute is
    * mandatory.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has D permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node name.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Attribute value index.
    *
    * @return  <code>true</code> if operation was successful and
    *          <code>false</code> otherwise.
    */
   public boolean deleteNodeAttributeValue(String nodeId, String name,
                                           int index)
   {
      AttributeRemover op = new AttributeRemover(nodeId, name);

      boolean res = op.lock();
      
      if (res)
      {
         res = op.remapper.deleteNodeAttributeValue(nodeId, name, index);
      }

      op.release();
      
      return res;
   }

   /**
    * Return information about existing node attributes.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>enumerable attributes are those having E permission under
    * /meta/class/ <i>node-class </i>/ <i>attribute-name </i>
    * </ul>
    * <p>
    * This call does NOT fail due to insufficient permissions to the
    * attributes. Instead, a filtering of the results is performed, which may
    * produce an empty array.
    * 
    * @param   nodeId
    *          Node for which list of attributes is requested.
    *
    * @return  Array of attribute definitions.
    */
   public NodeAttribute[] enumerateNodeAttributes(String nodeId)
   {
      NodeLocker op = new NodeLocker(nodeId);
      
      NodeAttribute[] res = null;
      
      do
      {
         if (!op.lockRO())
         {
            break;
         }
         
         //Get all attributes and then retrieve NodeAttribute from each
         Attribute[] attributes = getNodeAttributes(op.remapper, op.nodeId);

         if (attributes == null)
         {
            break;
         }

         ArrayList<NodeAttribute> data = new ArrayList<NodeAttribute>();
   
         for (int i = 0; i < attributes.length; i++)
         {
            // access rights check
            if (canAccessAttribute(op.nodeId, attributes[i].getName(),
                                   DirectoryResource.DIR_ENUMERATE_ACCESS))
            {
               data.add(attributes[i].getDefinition());
            }
         }
         
         res = data.toArray(new NodeAttribute[data.size()]);
      }
      while (false);
      
      op.release();

      return res;
   }

   /**
    * Get array of names of nodes for which given node is immediate parent.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>enumerable children nodes are those having E permission;
    * </ul>
    * <p>
    * This call does NOT fail due to insufficient permissions to the nodes.
    * Instead, a filtering of the results is performed, which may produce an
    * empty array.
    * 
    * @param   nodeId
    *          Node for which list of children nodes is requested.
    *
    * @return  An array of children node names.
    */
   public String[] enumerateNodes(String nodeId)
   {
      NodeLocker op = new NodeLocker(nodeId);

      String[] res = null;
      
      if (op.lockRO())
      {
         res = op.remapper.enumerateNodes(op.nodeId);
      }
      
      op.release();
      
      if (res == null)
      {
         return res;
      }
      
      ArrayList<String> data = new ArrayList<String>();

      for (int i = 0; i < res.length; i ++)
      {
         if (canAccessNode(op.nodeId + "/" + res[i],
                           DirectoryResource.DIR_ENUMERATE_ACCESS))
         {
            data.add(res[i]);
         }
      }
      
      // for the root object enumeration, add "meta" object to the results
      // if this is allowed by access rights.
      
      if (op.nodeId.equals("") &&
          canAccessNode("/meta", DirectoryResource.DIR_ENUMERATE_ACCESS))
      {
         data.add(new String("meta"));
      }
      
      return data.toArray(new String[data.size()]);
   }

   /**
    * Get NodeAttribute structure for the specified attribute. Note that since
    * this instance of <code>NodeAttribute</code> is not attached to
    * particular instance of <code>Attribute</code>, information about number
    * of the values assigned to the attribute is incorrect. The main purpose 
    * of this method is to create new attributes.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   className
    *          Object Class name.
    * @param   name
    *          Object Class attribute name.
    *
    * @return  An instance of <code>NodeAttribute</code> which describes
    *          specified attribute or <code>null</code> if no such object
    *          class or attribute name.
    */
   public NodeAttribute getClassNodeAttribute(String className, String name)
   {
      // access rights check
      if (!canAccessAttribute(name, DirectoryResource.DIR_READ_ACCESS, className))
      {
         return null;
      }

      AttributeDefinition[] defs = backend.getClassDefinition(className);
      if (defs == null)
      {
         return null;
      }

      for (int i = 0; i < defs.length; i++)
      {
         if (defs[i].getName().equals(name))
         {
            return defs[i].genNodeAttribute();
         }
      }
      return null;
   }

   /**
    * Generate an array of <code>NodeAttribute</code> structures for the all
    * attributes defined for the node class. Note that since these instances
    * of <code>NodeAttribute</code> are not attached to particular instance
    * of <code>Attribute</code>, information about number of the values
    * assigned to the attribute is incorrect. The purpose of this method is to
    * create new attributes.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission under /meta/class/ <i>node-class </i>
    * <li>enumerable attributes are those having E permission under
    * /meta/class/ <i>node-class </i>/ <i>attribute-name </i>
    * </ul>
    * <p>
    * This call does NOT fail due to insufficient permissions to the
    * attributes. Instead, a filtering of the results is performed, which may
    * produce an empty array.
    * 
    * @param   className
    *          Object Class name.
    *
    * @return  An array of <code>NodeAttribute</code> instances which
    *          describe attributes of node or <code>null</code> if no such
    *          object class or attributes defined for the class.
    */
   public NodeAttribute[] getClassNodeAttributes(String className)
   {
      // access rights check
      if (!canAccessNode("/meta/class/" + className,
                         DirectoryResource.DIR_READ_ACCESS))
      {
         return null;
      }

      AttributeDefinition[] defs = backend.getClassDefinition(className);

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

      ArrayList<NodeAttribute> data = new ArrayList<NodeAttribute>();

      for (int i = 0; i < defs.length; i++)
      {
         if (canAccessAttribute(defs[i].getName(),
                                DirectoryResource.DIR_ENUMERATE_ACCESS,
                                className))
         {
            data.add(defs[i].genNodeAttribute());
         }
      }

      return data.toArray(new NodeAttribute[data.size()]);
   }

   /**
    * Returns all existing attributes of given node.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>enumerable attributes are those having E permission under
    * /meta/class/ <i>node-class </i>/ <i>attribute-name </i>
    * <li>readable attributes are those having R permission under
    * /meta/class/ <i>node-class </i>/ <i>attribute-name </i>
    * </ul>
    * <p>
    * Note that attribute must have both R and E permissions set. If some
    * attribute does have E permission but lacks R permission then empty
    * attribute list is returned.
    * <p>
    * This call does NOT fail due to insufficient permissions to the
    * attributes. Instead, a filtering of the results is performed, which may
    * produce an empty array.
    * 
    * @param   nodeId
    *          Node for which list of attributes is requested.
    *
    * @return  Array of attributes or <code>null</code> in case of error.
    */
   public Attribute[] getNodeAttributes(String nodeId)
   {
      NodeLocker op = new NodeLocker(nodeId);

      Attribute[] res = null;
      
      ArrayList<Attribute> data = new ArrayList<Attribute>();

      if (op.lockRO())
      {
         Attribute[] list = getNodeAttributes(op.remapper, op.nodeId);
         
         for (int i = 0; list != null && i < list.length; i++)
         {
            // check attribute for E permission
            if (canAccessAttribute(op.nodeId, list[i].getName(),
                                   DirectoryResource.DIR_ENUMERATE_ACCESS))
            {
               data.add(list[i]);
               continue;
            }
            
            // check attribute for R permission
            if (!canAccessAttribute(op.nodeId, list[i].getName(),
                                    DirectoryResource.DIR_READ_ACCESS))
            {
               //Unclear situation: we can't return _all_ attributes as
               //required by specification. So, just return no attributes 
               //at all. Better to be safe than sorry.
               data.clear();
               break;
            }
         }
         
         res = data.toArray(new Attribute[data.size()]);
      }

      op.release();

      return res; 
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeBitField(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeBitField(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public BitField getNodeBitField(String nodeId, String name)
   {
      return getNodeBitField(nodeId, name, 0);
   }

   /**
    * Get <code>BitField</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>BitField</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public BitField getNodeBitField(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      BitField res = null;
      
      if (op.lock())
      {
         res = op.remapper.getNodeBitField(nodeId, name, index);
      }
      
      op.release();

      return res;
      
   }

   /**
    * Get all <code>BitField</code> attribute values for specified node ID.
    * If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>BitField</code> then <code>null</code> is
    * returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public BitField[] getNodeBitFields(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();

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

      return (BitField[]) attr.toArray(new BitField[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeBitSelector(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeBitSelector(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public BitSelector getNodeBitSelector(String nodeId, String name)
   {
      return getNodeBitSelector(nodeId, name, 0);
   }

   /**
    * Get <code>BitSelector</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>BitSelector</code> then
    * <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public BitSelector getNodeBitSelector(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      BitSelector res = null;
      
      if (op.lock())
      {
         res = op.remapper.getNodeBitSelector(nodeId, name, index);
      }
      
      op.release();

      return res;
   }

   /**
    * Get all <code>BitSelector</code> attribute values for specified node
    * ID. If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>BitSelector</code> then <code>null</code> is
    * returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public BitSelector[] getNodeBitSelectors(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();

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

      return (BitSelector[]) attr.toArray(new BitSelector[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeBoolean(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeBoolean(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public Boolean getNodeBoolean(String nodeId, String name)
   {
      return getNodeBoolean(nodeId, name, 0);
   }

   /**
    * Get <code>Boolean</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>Boolean</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public Boolean getNodeBoolean(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      Boolean res = null;
      
      if (op.lock())
         res = op.remapper.getNodeBoolean(nodeId, name, index);
      
      op.release();

      return res;
   }

   /**
    * Get all <code>Boolean</code> attribute values for specified node ID.
    * If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>Boolean</code> then <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public Boolean[] getNodeBooleans(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();

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

      return (Boolean[]) attr.toArray(new Boolean[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeByteArray(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeByteArray(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @return  Value of the specified attribute of specified node.
    */
   public byte[] getNodeByteArray(String nodeId, String name)
   {
      return getNodeByteArray(nodeId, name, 0);
   }

   /**
    * Get <code>byte array</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>byte[]</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public byte[] getNodeByteArray(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      byte[] res = null;
      
      if (op.lock())
         res = op.remapper.getNodeByteArray(nodeId, name, index);
      
      op.release();

      return res;
   }

   /**
    * Get all <code>byte array</code> attribute values for specified node
    * ID. If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>byte[]</code> then <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public byte[][] getNodeByteArrays(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();

      if (attr == null)
         return null;

      return (byte[][]) attr.toArray(new byte[0][0]);
   }

   /**
    * Gets meta class ID for the Object Class to which specified node belongs. 
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node's metaclass node has R permission (/meta/class/
    * <i>node-class</i>)
    * </ul>
    * 
    * @param   nodeId
    *          Node ID
    * @return  Meta class ID or <code>null</code> if no such node exists.
    */
   public String getNodeClass(String nodeId)
   {
      NodeLocker op = new NodeLocker(nodeId);
      
      String metaId = null;
      
      if (op.lockRO())
      {
         String res = op.remapper.getNodeClassName(nodeId);

         // access check
         if (res != null)
         {
            metaId = "/meta/class/"+ res;
            
            if (!canAccessNode(metaId, DirectoryResource.DIR_READ_ACCESS))
               metaId = null;
         }
      }
      
      op.release();

      return metaId;
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeDate(String, String, int)</code> method. It just invokes
    * that method assuming that last parameter is 0.
    * 
    * @see     #getNodeDate(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @return  Value of the specified attribute of specified node.
    */
   public DateValue getNodeDate(String nodeId, String name)
   {
      return getNodeDate(nodeId, name, 0);
   }

   /**
    * Get <code>DateValue</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>DateValue</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public DateValue getNodeDate(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      DateValue res = null;
      
      if (op.lock())
         res = op.remapper.getNodeDate(nodeId, name, index);
      
      op.release();

      return res;
   }

   /**
    * Get all <code>DateValue</code> attribute values for specified node ID.
    * If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>DateValue</code> then <code>null</code> is
    * returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public DateValue[] getNodeDates(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();

      if (attr == null)
         return null;

      return (DateValue[]) attr.toArray(new DateValue[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeDouble(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeDouble(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public Double getNodeDouble(String nodeId, String name)
   {
      return getNodeDouble(nodeId, name, 0);
   }

   /**
    * Get <code>Double</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>Double</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public Double getNodeDouble(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);

      Double res = null;
      
      if (op.lock())
         res = op.remapper.getNodeDouble(nodeId, name, index);
      
      op.release();

      return res;
   }

   /**
    * Get all <code>Double</code> attribute values for specified node ID. If
    * wrong parameter (node ID, attribute name) are passed or attribute type
    * is not <code>Double</code> then <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public Double[] getNodeDoubles(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();
      if (attr == null)
      {
         return null;
      }
      return (Double[]) attr.toArray(new Double[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeInteger(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeInteger(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public Integer getNodeInteger(String nodeId, String name)
   {
      return getNodeInteger(nodeId, name, 0);
   }

   /**
    * Get <code>Integer</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>Integer</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public Integer getNodeInteger(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);
      Integer res = null;
      if (op.lock())
      {
         res = op.remapper.getNodeInteger(nodeId, name, index);
      }
      op.release();
      return res;
   }

   /**
    * Get all <code>Integer</code> attribute values for specified node ID.
    * If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>Integer</code> then <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public Integer[] getNodeIntegers(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();
      if (attr == null)
      {
         return null;
      }
      return (Integer[]) attr.toArray(new Integer[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeString(String, String, int)</code> method. It just
    * invokes that method assuming that last parameter is 0.
    * 
    * @see     #getNodeString(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public String getNodeString(String nodeId, String name)
   {
      return getNodeString(nodeId, name, 0);
   }

   /**
    * Get <code>String</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>String</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public String getNodeString(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);
      String res = null;
      if (op.lock())
      {
         res = op.remapper.getNodeString(nodeId, name, index);
      }
      op.release();
      return res;
   }

   /**
    * Get all <code>String</code> attribute values for specified node ID. If
    * wrong parameter (node ID, attribute name) are passed or attribute type
    * is not <code>String</code> then <code>null</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public String[] getNodeStrings(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();
      if (attr == null)
      {
         return null;
      }
      return (String[]) attr.toArray(new String[0]);
   }

   /**
    * This is a convenience wrapper for the
    * <code>getNodeTime(String, String, int)</code> method. It just invokes
    * that method assuming that last parameter is 0.
    * 
    * @see     #getNodeTime(String, String, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the specified attribute of specified node.
    */
   public TimeValue getNodeTime(String nodeId, String name)
   {
      return getNodeTime(nodeId, name, 0);
   }

   /**
    * Get <code>TimeValue</code> attribute value for specified node ID and
    * index. If wrong parameter (node ID, attribute name or index) are passed
    * or attribute type is not <code>TimeValue</code> then <code>null</code>
    * is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    *
    * @return  Value of the attribute for the specified attribute name and
    *          index.
    */
   public TimeValue getNodeTime(String nodeId, String name, int index)
   {
      AttributeGetter op = new AttributeGetter(nodeId, name);
      TimeValue res = null;
      if (op.lock())
      {
         res = op.remapper.getNodeTime(nodeId, name, index);
      }
      op.release();
      return res;
   }

   /**
    * Get all <code>TimeValue</code> attribute values for specified node ID.
    * If wrong parameter (node ID, attribute name) are passed or attribute
    * type is not <code>TimeValue</code> then <code>null</code> is
    * returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has R permission;
    * <li>this attribute has R permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    *
    * @return  Value of the attribute for the specified node ID and attribute
    *          name.
    */
   public TimeValue[] getNodeTimes(String nodeId, String name)
   {
      Attribute attr = (new AttributeGetter(nodeId, name)).getAttribute();
      if (attr == null)
      {
         return null;
      }
      return (TimeValue[]) attr.toArray(new TimeValue[0]);
   }

   /**
    * Checks to see if a batch editing is in progress.
    * 
    * @return  <code>true</code> if there is an open editing batch
    */
   public boolean isEditing()
   {
      SecurityManager sm = SecurityManager.getInstance();
      if (sm == null)
      {
         return false;
      }
      if (clwa.get().bound != null)
      {
         return false;
      }
      if (clwa.get().activeBatch != null)
      {
         return true;
      }
      // check security (R/O) batch
      if (sm.isEditing())
      {
         return true;
      }
      return false;
   }

   /**
    * Renames the existing object, specified by nodeId, or moves it to a
    * different location. The parent object of the newId should exist and
    * allow for children creation.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>the source parent node has W permission;
    * <li>the source node has D permission;
    * <li>the target parent node has W permission;
    * <li>the target node has C permission;
    * </ul>
    * 
    * @param   nodeId
    *          An ID of the node to move.
    * @param   newId
    *          The desired new location of the node.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean moveNode(String nodeId, String newId)
   {
      nodeId = IdUtils.normalize(nodeId);
      if (nodeId == null)
      {
         return false;
      }

      newId = IdUtils.normalize(newId);
      if (newId == null)
      {
         return false;
      }

      // access rights check
      if (!canAccessNode(nodeId, DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_DELETE_ACCESS))
      {
         return false;
      }
      if (!canAccessNode(newId,  DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_CREATE_ACCESS))
      {
         return false;
      }

      Remapper remapper = synchBatch(nodeId, true);
      Remapper remapper2 = synchBatch(newId, true);

      //Make sure that both ID's do not cross batch boundary
      if (remapper == null || remapper2 == null || remapper != remapper2)
      {
         return false;
      }

      Object lock = null;
      if (remapper == backend)
      {
         lock = lockManager.enterLock(nodeId, LockManager.LOCK_RO);
      }

      boolean res = remapper.moveNode(nodeId, newId);
      if (remapper == backend)
      {
         lockManager.releaseLock(lock);
      }
      return res;
   }
   
   /**
    * Copies source node (and optionally recursively all its child nodes) to
    * given destination.
    * 
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>the source parent node has W permission;
    * <li>the source node has D permission;
    * <li>the target parent node has W permission;
    * <li>the target node has C permission;
    * </ul>
    * 
    * @param   nodeId
    *          An ID of the node to move.
    * @param   newId
    *          The desired new location of the node.
    * @param   recursive
    *          <code>true</code> means copying recursively all child nodes.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean copyNode(String nodeId, String newId, boolean recursive)
   {
      nodeId = IdUtils.normalize(nodeId);
      if (nodeId == null)
      {
         return false;
      }

      newId = IdUtils.normalize(newId);
      if (newId == null)
      {
         return false;
      }
      
      // access rights check
      if (!canAccessNode(nodeId, DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_DELETE_ACCESS))
      {
         return false;
      }
      if (!canAccessNode(newId,  DirectoryResource.DIR_WRITE_ACCESS,
                                 DirectoryResource.DIR_CREATE_ACCESS))
      {
         return false;
      }

      Remapper remapper  = synchBatch(nodeId, true);
      Remapper remapper2 = synchBatch(newId, true);

      //Make sure that both ID's do not cross batch boundary
      if (remapper == null || remapper2 == null || remapper != remapper2)
      {
         return false;
      }

      Object lock = null;
      if (remapper == backend)
      {
         lock = lockManager.enterLock(nodeId, LockManager.LOCK_RO);
      }

      boolean res = copySubtree(remapper, nodeId, remapper2, newId, recursive);
      if (remapper == backend)
      {
         lockManager.releaseLock(lock);
      }
      return res;
   }
   
   /**
    * Open batch editing session.
    * <p>
    * <code>openBatch</code> call made against "/security" node or anything
    * under it, is subject to Security Manager notification.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * </ul>
    * 
    * @param   nodeId
    *          Subtree which will be edited.
    *
    * @return  <code>true</code> if operation was successful and application
    *          can start updating tree.
    */
   public boolean openBatch(String nodeId)
   {
      if (!isBound())
      {
         return false;
      }

      String batchId = IdUtils.normalize(nodeId);
      if (batchId == null)
      {
         return false;
      }
      if (getActiveBatch() != null)
      {
         //Batch editing for this session is in progress
         return false;
      }
      if (backend.getNodeClassName(nodeId) == null)
      {
         //Node does not exists
         return false;
      }
      // access rights check
      if (!canAccessNode(batchId, DirectoryResource.DIR_WRITE_ACCESS))
      {
         return false;
      }
      // until this point there is no need for synchronization
      Object lock = lockManager.enterLock(batchId, LockManager.LOCK_RX);
      if (lock == null)
      {
         return false;
      }
      // serialize access
      synchronized(this)
      {
         SecurityManager sm = SecurityManager.getInstance();

         int secRc = 0;
         if (sm != null)
         {
            secRc = sm.openBatch(batchId);
            if (secRc == -1)
            {
               lockManager.releaseLock(lock);
               return false;
            }
         }
         setActiveBatch(new BatchRef(lock, batchId, new ShadowRemapper(backend)));
      }
      return true;
   }

   /**
    * Signals the end of security environment initialization. Sets its own
    * <code>initialized</code> flag so the access check methods can do some
    * real work.
    * <p>
    * <u>Access control considerations: </u> <br>
    * Normally this call should be issued by SecurityCache class only.
    */
   public void securityIsUp()
   {
      initialized = true;
   }

   /**
    * Replace all node attributes with provided ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>all existing attributes have D permission under /meta/class/
    * <i>node-class </i>/ <i>attribute-name </i>;
    * <li>all new attributes have C permission under /meta/class/
    * <i>node-class </i>/ <i>attribute-name </i>;
    * </ul>
    * 
    * @param   nodeId
    *          Node name.
    * @param   data
    *          An array of new attributes.
    *
    * @return  <code>true</code> if operation was successful and all
    *          attributes are assigned to the node.
    */
   public boolean setNodeAttributes(String nodeId, Attribute[] data)
   {
      NodeLocker op = new NodeLocker(nodeId);
      
      // check existing attributes
      boolean access = true;
      boolean res = false;
      
      do
      {
         if (!op.lockRW())
         {
            break;
         }
         
         Attribute[] xdata = getNodeAttributes(op.remapper, op.nodeId);
         if (xdata != null)
         {
            for (int i = 0; i < xdata.length; i ++)
            {
               if (!canAccessAttribute(op.nodeId, xdata[i].getName(),
                                       DirectoryResource.DIR_DELETE_ACCESS))
               {
                  access = false;
                  break;
               }
            }
         }
         
         if (!access)
            break;

         // check new attributes
         if (data != null)
         {
            for (int i = 0; i < data.length; i ++)
            {
               if (!canAccessAttribute(op.nodeId, data[i].getName(),
                                       DirectoryResource.DIR_DELETE_ACCESS))
               {
                  access = false;
                  break;
               }
               
               if (!verifyValues(data[i].getValues()))
               {
                  access = false;
                  break;
               }
            }
         }
         if (!access)
         {
            break;
         }
         res = setNodeAttributes(op.remapper, op.nodeId, data);
      }
      while (false);
      
      op.release();

      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeBitField(String, String, int, BitFiled)</code> method
    * which is invoked assuming that index is 0.
    * 
    * @see     #setNodeBitSelector(String, String, int, BitSelector)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitField(String nodeId, String name, BitField value)
   {
      return setNodeBitField(nodeId, name, 0, value);
   }

   /**
    * Set <code>BitField</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitField(String nodeId, String name, int index,
                                  BitField value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeBitField(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>BitField</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitFields(String nodeId, String name,
                                   BitField[] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeBitField(nodeId, name, start,
                                             value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeBitField(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeBitSelector(String, String, int, BitSelector)</code>
    * method which is invoked assuming that index is 0.
    * 
    * @see     #setNodeBitSelector(String, String, int, BitSelector)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitSelector(String nodeId, String name,
                                     BitSelector value)
   {
      return setNodeBitSelector(nodeId, name, 0, value);
   }

   /**
    * Set <code>BitSelector</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitSelector(String nodeId, String name, int index,
                                     BitSelector value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeBitSelector(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>BitSelector</code> node attribute values with new
    * ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBitSelectors(String nodeId, String name,
                                      BitSelector[] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeBitSelector(nodeId, name, start,
                                             value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeBitSelector(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeBoolean(String, String, int, boolean)</code> method which
    * is invoked assuming that index is 0.
    * 
    * @see     #setNodeBoolean(String, String, int, boolean)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBoolean(String nodeId, String name, boolean value)
   {
      return setNodeBoolean(nodeId, name, 0, value);
   }

   /**
    * Set <code>boolean</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBoolean(String nodeId, String name, int index,
                                 boolean value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeBoolean(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>Boolean</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeBooleans(String nodeId, String name, boolean[] value)
   {
      if (value == null || value.length == 0 || !verifyBooleanValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();

      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeBoolean(nodeId, name, start,
                                             value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeBoolean(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeByteArray(String, String, int, byte[])</code> method
    * which is invoked assuming that index is 0.
    * 
    * @see     #setNodeByteArray(String, String, int, byte[])
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeByteArray(String nodeId, String name, byte[] value)
   {
      return setNodeByteArray(nodeId, name, 0, value);
   }

   /**
    * Set <code>byte[]</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeByteArray(String nodeId, String name, int index,
                                   byte[] value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeByteArray(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>byte[]</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeByteArrays(String nodeId, String name, byte[][] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeByteArray(nodeId, name, start,
                                             value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeByteArray(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeDate(String, String, int, DateValue)</code> method which
    * is invoked assuming that index is 0.
    * 
    * @see     #setNodeDate(String, String, int, DateValue)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDate(String nodeId, String name, DateValue value)
   {
      return setNodeDate(nodeId, name, 0, value);
   }

   /**
    * Set <code>DateValue</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDate(String nodeId, String name, int index,
                              DateValue value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeDate(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>DateValue</code> node attribute values with new
    * ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDates(String nodeId, String name, DateValue[] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeDate(nodeId, name, start, value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeDate(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeDouble(String, String, int, double)</code> method which
    * is invoked assuming that index is 0.
    * 
    * @see     #setNodeDouble(String, String, int, double)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDouble(String nodeId, String name, double value)
   {
      return setNodeDouble(nodeId, name, 0, value);
   }

   /**
    * Set <code>double</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDouble(String nodeId, String name, int index,
                                double value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeDouble(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>Double</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeDoubles(String nodeId, String name, double[] value)
   {
      if (value == null || value.length == 0 || !verifyDoubleValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeDouble(nodeId, name, start, value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeDouble(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeInteger(String, String, int, int)</code> method which is
    * invoked assuming that index is 0.
    * 
    * @see     #setNodeInteger(String, String, int, int)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeInteger(String nodeId, String name, int value)
   {
      return setNodeInteger(nodeId, name, 0, value);
   }

   /**
    * Set <code>int</code> attribute value for specified node ID, attribute
    * name and index. If wrong parameter is passed or attribute read-only then
    * <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeInteger(String nodeId, String name, int index,
                                 int value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeInteger(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * Replace all <code>Integer</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeIntegers(String nodeId, String name, int[] value)
   {
      if (value == null || value.length == 0 || !verifyIntegerValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeInteger(nodeId, name, start, value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeInteger(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * Set <code>String</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeString(String nodeId, String name, int index,
                                String value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeString(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeString(String, String, int, String)</code> method which
    * is invoked assuming that index is 0.
    * 
    * @see     #setNodeString(String, String, int, String)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeString(String nodeId, String name, String value)
   {
      return setNodeString(nodeId, name, 0, value);
   }

   /**
    * Replace all <code>String</code> node attribute values with new ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeStrings(String nodeId, String name, String[] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeString(nodeId, name, start, value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeString(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * Set <code>TimeValue</code> attribute value for specified node ID,
    * attribute name and index. If wrong parameter is passed or attribute
    * read-only then <code>false</code> is returned.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   index
    *          Index of the value to retrieve.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeTime(String nodeId, String name, int index,
                              TimeValue value)
   {
      AttributeSetter op = new AttributeSetter(nodeId, name);
      
      boolean res = op.lock();
      if (res)
      {
         res = verifyAttribute(op.remapper, nodeId, name, value, index);
         if (res)
         {
            res = op.remapper.setNodeTime(nodeId, name, index, value);
         }
      }
      op.release();

      return res;
   }

   /**
    * A convenience wrapper for
    * <code>setNodeTime(String, String, int, TimeValue)</code> method which
    * is invoked assuming that index is 0.
    * 
    * @see     #setNodeTime(String, String, int, TimeValue)
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          New value for the attribute variable.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeTime(String nodeId, String name, TimeValue value)
   {
      return setNodeTime(nodeId, name, 0, value);
   }

   /**
    * Replace all <code>TimeValue</code> node attribute values with new
    * ones.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>this node has W permission;
    * <li>this attribute has W permission under /meta/class/ <i>node-class
    * </i>/ <i>attribute-name </i>
    * </ul>
    * 
    * @param   nodeId
    *          Node identifier.
    * @param   name
    *          Attribute name.
    * @param   value
    *          An array of new attribute values.
    *
    * @return  <code>true</code> is operation is successful and
    *          <code>false</code> otherwise.
    */
   public boolean setNodeTimes(String nodeId, String name, TimeValue[] value)
   {
      if (value == null || value.length == 0 || !verifyValues(value))
      {
         return false;
      }

      AttributeSetter op = new AttributeSetter(nodeId, name);

      boolean res = false;

      int rc = op.preMult();
      do
      {
         if (rc == 0) //error, bail out
         {
            break;
         }
         int start = 0;
         if (rc == 1) //Mandatory attribute
         {
            if (!op.remapper.setNodeTime(nodeId, name, start, value[start++]))
            {
               break;
            }
         }
         for (; start < value.length; start++)
         {
            if (!op.remapper.addNodeTime(nodeId, name, value[start]))
            {
               break;
            }
         }
         if (start == value.length)
         {
            res = true;
         }
      }
      while (false);

      op.release();
      
      return res;
   }

   /**
    * Close directory service for current <code>SecurityManager</code> session.
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @return  <code>true</code> if operation was successful.
    */
   public boolean unbind()
   {
      return unbind(clwa.get());
   }

   /**
    * Checks the subject's permissions to perform the specified access to a
    * directory node.
    *
    * @param   oId
    *          Directory node being accessed
    * @param   mode
    *          Requested access mode
    *
    * @return  <code>true</code> if access is granted.
    *          <code>false otherwise.</code>
    */
   public boolean canAccessNode(String oId, int mode)
   {
      if (!initialized)
      {
         return true;
      }
      SecurityManager sm = SecurityManager.getInstance();
      DirectoryResource ar = null;
      if (sm != null)
      {
         ar = (DirectoryResource)sm.getPluginInstance("directory");
      }
      if (ar == null)
      {
         return true;
      }
      return ar.checkAccess(oId, mode);
   }

   /**
    * Checks the subject's permissions to perform the specified access to a
    * directory node which requires a different access to its parent node.
    *
    * @param   oId
    *          Directory node being accessed
    * @param   parentMode
    *          Requested access mode to the node's parent node
    * @param   mode
    *          Requested access mode
    *
    * @return  <code>true</code> if access is granted.
    *          <code>false otherwise.</code>
    */
   public boolean canAccessNode(String oId, int parentMode, int mode)
   {
      if (!initialized)
      {
         return true;
      }
      if (oId == "")
      {
         return false;          // no parent exists for the root object
      }
      SecurityManager sm = SecurityManager.getInstance();
      DirectoryResource ar = null;
      if (sm != null)
      {
         ar = (DirectoryResource)sm.getPluginInstance("directory");
      }
      if (ar == null)
      {
         return true;
      }
      String parent = oId.substring(0, oId.lastIndexOf("/"));
      if (ar.checkAccess(parent, parentMode) == false)
      {
         return false;
      }
      return ar.checkAccess(oId, mode);
   }

   /**
    * Worker for unbind() method that closes directory service for current 
    * <code>SecurityManager</code> session. It is context-free so it will not 
    * use any context-local variables. Instead, the this method receives as 
    * parameter a <code>WorkArea</code> that contains BindRef and BatchRef 
    * instances to unbiund. This is because the <code>Cleanable.cleanup()
    * </code> cannot use context-local variables as they were already removed
    * during the cleanup procedure.   
    * 
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @param   wa
    *          <code>WorkArea</code> that contains the active batch and bound 
    *          reference to be unbind
    * 
    * @return  <code>true</code> if operation was successful.
    */
   private boolean unbind(WorkArea wa)
   {
      logTrace("Entering unbind(WorkArea)");
      
      // no special permissions required for this API
      if (!initialized)
      {
         return delRef();
      }

      boolean forceDelRef = false;
      boolean rc = false;
      
      if (wa.activeBatch != null)
      {
         logTrace("Active batch found. Closing it");
         rc = closeBatchInt(wa.activeBatch, false, true);
         //cannot call here closeBatch() or getActiveBatch().
         logTrace("Active batch close " + (rc ? "succeed" : "failed"));
         
         forceDelRef = true; //keep bound; it will be removed after 
                             //finishing the active batch
         rc = false;
      }

      do
      {
         if (wa.bound == null && !forceDelRef)
         {
            break;
         }
         if (wa.bound != null && --wa.bound.count == 0)
         {
            wa.bound = null;
         }
         rc = delRef();
      }
      while (false);
      
      return rc;
   }

   /**
    * Check if session is bound to the directory service.
    * 
    * <p>
    * <u>Required permissions: </u> <br>
    * <ul>
    * <li>none
    * </ul>
    * 
    * @return  <code>true</code> if session is bound properly.
    */
   public boolean isBound()
   {
      if (!initialized)
      {
         return true;
      }
      return (clwa.get().bound != null);
   }
   
   /**
    * Save directory into new file.
    * <p>
    * Note that backend may not support this functionality.
    * 
    * @param    newFile
    *           Name of the file to store directory.
    *           
    * @return   return code.
    */
   public int backupDirectory(String newFile)
   {
      if (backend instanceof XmlRemapper)
      {
         // reject refreshing if editing session is active
         if (isEditing())
         {
            return ERR_EDITING_IS_ACTIVE;
         }

         Object lock = lockManager.enterLock("", LockManager.LOCK_RX);
         if (lock == null)
         {
            return ERR_CANT_LOCK_DIRECTORY;
         }
         
         try
         {
            if (lockManager.upgradeLock(lock))
            {
               return ((XmlRemapper) backend).save(newFile);
            }
            return ERR_CANT_LOCK_DIRECTORY;
         }
         catch (Throwable e)
         {
            return ERR_FATAL;
         }
         finally
         {
            lockManager.releaseLock(lock);
         }
      }
      return ERR_SAVE_NOT_SUPPORTED;
   }

   /**
    * Checks the subject's permissions to perform the specified access to a
    * directory node and a different access to its attribute.
    * 
    * @param   oId
    *          Directory node being accessed
    * @param   nodeMode
    *          Requested access mode for directory node
    * @param   name
    *          Attribute name
    * @param   attrMode
    *          Requested access mode for attribute
    *
    * @return  <code>true</code> if access is granted.
    *          <code>false otherwise.</code>
    */
   protected boolean canAccessNodeAndAttribute(String oId,  int nodeMode,
                                               String name, int attrMode)
   {
      if (!initialized)
      {
         return true;
      }

      Remapper remapper = synchBatch(oId, false);
      if (remapper == null)
      {
         return false;
      }
      SecurityManager sm = SecurityManager.getInstance();
      DirectoryResource ar = null;
      if (sm != null)
      {
         ar = (DirectoryResource)sm.getPluginInstance("directory");
      }
      if (ar == null)
      {
         return true;
      }
      if (!ar.checkAccess(oId, nodeMode))
      {
         return false;
      }

      String metaId = "/meta/class/" + remapper.getNodeClassName(oId) + "/" + name;
      return ar.checkAccess(metaId, attrMode);
   }

   /**
    * Close batch editing session main worker routine.
    * 
    * @param   ref
    *          A batch to close which should be closed.
    * @param   disposition
    *          If this parameter is <code>true</code> then changes need to
    *          be committed. Otherwise they are just thrown away and lock is
    *          removed.
    * @param   refresh
    *          when committing the batch, tells whether to refresh the
    *          security cache or not
    *
    * @return  <code>true</code> if operation was successful and tree was
    *          successfully updated.
    */
   protected boolean closeBatchInt(BatchRef ref, boolean disposition,
                                   boolean refresh)
   {
      SecurityManager sm = SecurityManager.getInstance();

      if (ref == null)
      {
         // no regular batch but there may be a security (R/O) batch
         if (sm != null)
         {
            return sm.closeBatch(disposition, refresh);
         }
         return true;
      }

      if (disposition == false) //Roll-back.
      {
         //Just drop everything
         lockManager.releaseLock(ref.getLock());
         removeActiveBatch();
         if (sm != null)
         {
            sm.closeBatch(false);
         }
         return true;
      }
      
      boolean upgrade = lockManager.upgradeLock(ref.getLock());
      if (!upgrade)
      {
         logWarning("Lock manager signals failure during lock upgrade.");
      }

      ShadowRemapper subTree = ref.getBackend();
      boolean rc = false;

      logTrace("Commit start");

      String backupId = null;
      String batchId = ref.getBaseId();

      while (true)
      {
         backupId = "/backup/" + new Random().nextInt(Integer.MAX_VALUE);
         if (backend.getNodeClassName(backupId) == null)
         {
            break;
         }
      }

      //Preliminary step
      //Ignore result code here for case when subtree already exists
      try
      {
         if (backend.getNodeClassName("/backup") == null)
         {
            rc = backend.addNode("/backup", "container", null, null);
            if (!rc)
            {
               // unable to create /backup node
               logWarning("Unable to create backup node.");
               return false;
            }
         }
         rc = subTree.commit(batchId, backupId);
         if (!rc)
         {
            logWarning("Failed to commit changes in the batch '" + batchId + "'.");
         }
      }
      finally
      {
         lockManager.releaseLock(ref.getLock());
         removeActiveBatch();

         logTrace("Commit end (" + (rc ? "success":"failure") + ")");
         
         if (sm != null)
         {
            sm.closeBatch(true, refresh);
         }
      }
      
      return rc;
   }

   /**
    * Choose back-end on which operation will be performed.
    * 
    * @param   id
    *          Node ID for which operation is planned.
    * @param   rw
    *          Access type: if parameter is <code>true</code> then
    *          Read/Write access is planned.
    *
    * @return  Reference to the back-end.
    */
   protected Remapper synchBatch(String id, boolean rw)
   {
      if (!isBound())
      {
         return null;
      }
      
      synchronized (batchSynch)
      {
         //Check for "meta" subtree:
         if (id.startsWith("/meta"))
         {
            return (rw) ? null : metaData; //Meta data is read-only
         }

         BatchRef ref = getActiveBatch();
         if (ref == null && rw)
         {
            return null;
         }
         
         if (ref == null || !id.startsWith(ref.getBaseId()))
         {
            return backend;
         }

         return ref.getBackend();
      }
   }
   
   /**
    * Checks the subject's permissions to perform the specified access to a
    * directory node's attribute with the given node class name.
    * 
    * @param   name
    *          Attribute name
    * @param   mode
    *          Requested access mode
    * @param   nodeClass
    *          Directory node class name
    *
    * @return  <code>true</code> if access is granted.
    *          <code>false otherwise.</code>
    */
   private boolean canAccessAttribute(String name, int mode, String nodeClass)
   {
      if (!initialized)
      {
         return true;
      }

      SecurityManager sm = SecurityManager.getInstance();
      DirectoryResource ar = null;
      if (sm != null)
      {
         ar = (DirectoryResource)sm.getPluginInstance("directory");
      }
      if (ar == null)
      {
         return true;
      }

      String metaId = "/meta/class/" + nodeClass + "/" + name;
      return ar.checkAccess(metaId, mode);
   }
 
   /**
    * Checks the subject's permissions to perform the specified access to a
    * directory node's attribute.
    * 
    * @param   oId
    *          Directory node being accessed
    * @param   name
    *          Attribute name
    * @param   mode
    *          Requested access mode
    * @return  <code>true</code> if access is granted.
    *          <code>false otherwise.</code>
    */
   private boolean canAccessAttribute(String oId, String name, int mode)
   {
      if (!initialized)
      {
         return true;
      }

      Remapper remapper = synchBatch(oId, false);
      if (remapper == null)
      {
         return false;
      }

      SecurityManager sm = SecurityManager.getInstance();
      DirectoryResource ar = null;
      if (sm != null)
      {
         ar = (DirectoryResource)sm.getPluginInstance("directory");
      }
      if (ar == null)
      {
         return true;
      }

      String metaId = "/meta/class/" + remapper.getNodeClassName(oId) + "/" + name;
      return ar.checkAccess(metaId, mode);
   }

   /**
    * Complementary method for the <code>newRef()</code>. If counter
    * reaches 0 then then back-end is stopped by calling its
    * <code>unbind()</code> method.
    * 
    * @return  <code>true</code> if operation was successful.
    */
   private boolean delRef()
   {
      synchronized (bindSync)
      {
         if (refCount == 0)
         {
            return false;  //repeated unbind
         }
            
         refCount--;
         if (refCount == 0)
         {
            return backend.unbind();
         }
      }
      return true;
   }

   /**
    * Get active batch for current session.
    * 
    * @return  Reference to active batch.
    */
   private BatchRef getActiveBatch()
   {
      if (!initialized)
      {
         return tempActiveBatch;
      }
      return clwa.get().activeBatch;
   }

   /**
    * Common initialisation code,
    */
   private void init()
   {
      lockManager = new LockManager();
      backend = null;
   }

   /**
    * This method fills "/meta" subtree with the information about supported
    * classes provided by the backend.
    */
   private void initMetaInfo()
   {
      if (backend == null)
      {
         return;
      }

      metaData = new RamRemapper();
      metaData.bind();
      //Prepare tree
      if (!metaData.addNode("/meta", "container", null, null))
      {
         return;
      }
      if (!metaData.addNode("/meta/class", "container", null, null))
      {
         return;
      }

      //Get information about classes
      String[] classes = backend.getClassNames();
      if (classes == null)
      {
         classes = new String[] { new String("metaclass") };
      }
      else
      {
         String[] tmp = new String[classes.length + 1];

         System.arraycopy(classes, 0, tmp, 0, classes.length);
         tmp[classes.length] = new String("metaclass");

         classes = tmp;
      }

      for (int i = 0; i < classes.length; i++)
      {
         AttributeDefinition[] attrs;

         if (classes[i].equals("metaclass"))
         {
            attrs = metaData.getClassDefinition(classes[i]);
         }
         else
         {
            attrs = backend.getClassDefinition(classes[i]);
         }

         int j;
         boolean rc;
         String node = "/meta/class/" + classes[i];

         ArrayList<String> names = new ArrayList<String>();
         ArrayList<Comparable<?>> values = new ArrayList<Comparable<?>>();

         //Class description
         names.add("class");
         values.add(classes[i]);

         names.add("terminal");
         values.add(Boolean.valueOf(backend.isClassLeaf(classes[i])));

         //Attributes description
         for (j = 0; attrs != null && j < attrs.length; j++)
         {
            names.add("attrname");
            values.add(attrs[j].getName());

            names.add("attrtype");
            values.add(Integer.valueOf(attrs[j].getType()));

            names.add("attropt");
            values.add(Boolean.valueOf(!attrs[j].isMandatory()));

            names.add("attrmult");
            values.add(Boolean.valueOf(attrs[j].isMultiple()));
         }

         rc = metaData.addNode(node, "metaclass",
                               names.toArray(new String[names.size()]),
                               values.toArray());
         if (!rc)
         {
            return;
         }

         for (j = 0; attrs != null && j < attrs.length; j++)
         {
            node = "/meta/class/" + classes[i] + "/" + attrs[j].getName();
            rc = metaData.addNode(node, "terminal", null, null);
            if (!rc)
            {
               return;
            }
         }
      }
   }

   /**
    * Bind request counter. Complementary method for the <code>delRef()</code>.
    * If this is a first call to this method then back-end is started by
    * calling its <code>bind()</code> method.
    * 
    * @return  <code>true</code> if operation was successful.
    */
   private boolean newRef()
   {
      boolean rc = true;
      synchronized (bindSync)
      {
         if (refCount == 0)
         {
            rc = backend.bind();
         }
         refCount++;
      }
      return rc;
   }

   /**
    * Remove reference to active batch.
    */
   private void removeActiveBatch()
   {
      if (!initialized)
      {
         tempActiveBatch = null;
         return;
      }
      clwa.get().activeBatch = null;
   }

   /**
    * Roll back all transactions which are left from previous start. 
    */
   private boolean rollBack()
   {
      boolean rc = backend.bind();
      if (!rc)
      {
         logError("Unable to bind to underlying back-end");
         return false;
      }
      
      Rollback unroller = new Rollback();
      
      logList("Checking for uncommitted transactions start.");
      
      rc = (traverseTree(backend, "", unroller, true) == null);
      if (!rc)
      {
         logWarning("Checking for uncommitted transactions failed");
      }
      
      logList("Checking for uncommitted transactions end. (" + (rc ? "success" : "failure") + ").");
      
      if (rc && unroller.getUpdated() > 0)
      {
         // commit changes 
         rc = backend.update();
      }
      
      boolean rrc = backend.unbind();
      if (!rrc)
      {
         logError("Error during unbinding of underlying back-end");
      }
      
      return rc & rrc;
   }

   /**
    * Set reference to active batch.
    * 
    * @param   batch
    *          Reference to batch.
    */
   private void setActiveBatch(BatchRef batch)
   {
      if (!initialized)
      {
         tempActiveBatch = batch;
         return;
      }
      clwa.get().activeBatch = batch;
   }

   /**
    * Check attribute for existence of specified value.
    * 
    * @param   remapper
    *          Back-end instance.
    * @param   nodeId
    *          Node ID.
    * @param   name
    *          Attribute name.
    * @param   value
    *          Value to compare with.
    * @param   ordinal
    *          Value ordinal to check.
    *
    * @return  <code>true</code> if value does not exists and new value can
    *          be added safely.
    */
   private boolean verifyAttribute(Remapper remapper, String nodeId, String name,
                                  Object value, int ordinal)
   {
      Attribute attr = getNodeAttribute(remapper, nodeId, name);
      if (attr == null) //No such attribute
      {
         return true;
      }

      for (int i = 0; i < attr.getCount(); i++)
      {
         if (attr.getValue(i).equals(value))
         {
            return (ordinal == i) ? true:false;
         }
      }

      return true;
   }
   
   /**
    * Helper class for the adding values to the attributes.
    */
   class AttributeAdder
   extends LockHelper
   {
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID.
       * @param   name
       *          Attribute name.
       */
      AttributeAdder(String nodeId, String name)
      {
         super(nodeId, name);
      }

      /**
       * {@inheritDoc}
       */
      protected boolean lock()
      {
         return pre(true, LockManager.LOCK_RW, 
                    DirectoryResource.DIR_WRITE_ACCESS,
                    DirectoryResource.DIR_ADD_ACCESS);
      }
   }

   /**
    * Helper class for the getting value from the attribute.
    */
   class AttributeGetter
   extends LockHelper
   {
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID for which operation will be performed.
       * @param   name
       *          Attribute name.
       */
      AttributeGetter(String nodeId, String name)
      {
         super(nodeId, name);
      }

      /**
       * Perform preliminary steps.
       * 
       * @return  <code>true</code> if preliminary steps were successful.
       */
      protected boolean lock()
      {
         return pre(false, LockManager.LOCK_RO,
                    DirectoryResource.DIR_READ_ACCESS,
                    DirectoryResource.DIR_READ_ACCESS);
      }

      /**
       * Perform all necessary steps and return attribute value.
       * 
       * @return  Reference to requested attribute or <code>null</code> in
       *          case of error.
       */
      Attribute getAttribute()
      {
         Attribute res = (lock()) ? getNodeAttribute(remapper, nodeId, name) 
                                 : null;
         
         release();
         
         return res;
      }
   }
   
   /**
    * Helper class for the getting value from the attribute.
    */
   class AttributeRemover
   extends LockHelper
   {
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID for which operation will be performed.
       * @param   name
       *          Attribute name.
       */
      AttributeRemover(String nodeId, String name)
      {
         super(nodeId, name);
      }

      /**
       * Perform preliminary steps.
       * 
       * @return  <code>true</code> if preliminary steps were successful.
       */
      protected boolean lock()
      {
         return pre(true, LockManager.LOCK_RW,
                    DirectoryResource.DIR_WRITE_ACCESS,
                    DirectoryResource.DIR_DELETE_ACCESS);

      }
   }
   
   /**
    * Helper class for setting attribute values.
    */
   class AttributeSetter
   extends LockHelper
   {
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID for which operation will be performed.
       * @param   name
       *          Attribute name.
       */
      AttributeSetter(String nodeId, String name)
      {
         super(nodeId, name);
      }

      /**
       * {@inheritDoc}
       */
      protected boolean lock()
      {
         return pre(true, LockManager.LOCK_RW,
                    DirectoryResource.DIR_WRITE_ACCESS,
                    DirectoryResource.DIR_WRITE_ACCESS);
      }
      
      /**
       * Perform steps necessary for assigning array of attribute values to
       * attribute.
       * 
       * @return  <code>0</code> in case of error, <code>1</code>- if
       *          attribute is mandatory, <code>2</code> if attribute is
       *          ordinary.
       */
      int preMult()
      {
         if(!lock())
         {
            return 0;
         }
         
         return prepareNodeAttribute0(remapper, nodeId, name);
      }
   }

   /**
    * Class BatchRef is a container for the batch editing variables.
    */
   static class BatchRef
   {
      /** Back-end which handles batch operations until commit. */
      private ShadowRemapper backend;

      /** ID of the base node of the subtree. */
      private String   baseId;

      /** Object returned by the <code>LockManager.enterLock()</code> */
      private Object   lock;

      /**
       * Create an instance for given lock, base ID and back-end reference.
       * 
       * @param   lock
       *          Reference to the lock returned by the
       *          <code>LockManager.enterLock()</code>.
       * @param   baseId
       *          Node ID for which lock is placed.
       * @param   backend
       *          Reference to Remapper which will be used for batch
       *          operations.
       */
      BatchRef(Object lock, String baseId, ShadowRemapper backend)
      {
         this.lock = lock;
         this.baseId = baseId;
         this.backend = backend;
      }
      
      /**
       * Does the cleanup for bound sessions.
       *
       * @param  wa
       *         The context-local <code>WorkArea</code> that contains the  
       *         reference to active batch to be cleaned.
       */
      public void cleanup(WorkArea wa)
      {
         logTrace("Closing batch abnormally: session terminated");
         getInstance().closeBatchInt(this, false, false);
      }

      /**
       * Return reference to backend which holds changed tree.
       * 
       * @return  Returns the backend.
       */
      ShadowRemapper getBackend()
      {
         return backend;
      }

      /**
       * Get ID of the root node of subtree.
       * 
       * @return  Returns the baseId.
       */
      String getBaseId()
      {
         return baseId;
      }

      /**
       * Get reference to the lock obtained during batch initialisation.
       * 
       * @return  Returns the lock.
       */
      Object getLock()
      {
         return lock;
      }
   }

   /**
    * This is a helper class used to clean up forgotten or improperly closed
    * sessions.
    */
   static class BindRef
   {
      /** Reference count of binds in this security context */
      private int count = 0;
      
      /**
       * Does the cleanup for bound sessions.
       *
       * @param  wa
       *         The context-local <code>WorkArea</code> that contains the  
       *         bound reference to be cleaned.
       */
      public void cleanup(WorkArea wa)
      {
         logTrace("Unbinding abnormally: session terminated");
         
         DirectoryService ds = getInstance();
         while (count > 0)
         {
            ds.unbind(wa);
         }
      }
   }

   /**
    * Base class for all attribute helper classes. 
    */
   abstract class LockHelper
   {
      
      /** Intermediate lock */
      protected Object lock = null;
      
      /** Attribute name */
      protected String name;
      
      /** Node ID */
      protected String nodeId;
      /** Reference to remapper */
      Remapper remapper = null;
      
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID for which operation will be performed.
       * @param   name
       *          Attribute name.
       */
      LockHelper(String nodeId, String name)
      {
         this.nodeId = nodeId;
         this.name = name;
      }

      /**
       * Perform preliminary steps required to access node and attribute.
       * 
       * @return  <code>true</code> if preliminary steps were successful.
       */
      abstract protected boolean lock();
      
      /**
       * Lock node.
       * 
       * @param   batchWr
       *          Flag for <code>synchBatch()</code>.
       * @param   lockOp
       *          Locking mode for <code>LockManager</code>.
       *
       * @return  <code>true</code> if preliminary steps were successful.
       */
      protected boolean pre(boolean batchWr, int lockOp)
      {
         nodeId = IdUtils.normalize(nodeId);
         if (nodeId == null)
         {
            return false;
         }

         remapper = synchBatch(nodeId, batchWr);
         if (remapper == null)
         {
            return false;
         }

         if (remapper == backend)
         {
            lock = lockManager.enterLock(nodeId, lockOp);
         }
         return true;
      }

      /**
       * Lock node and check access rights.
       * 
       * @param   batchWr
       *          Flag for <code>synchBatch()</code>.
       * @param   lockOp
       *          Locking mode for <code>LockManager</code>.
       * @param   nodeOp
       *          Node access mode.
       *
       * @return  <code>true</code> if preliminary steps were successful.
       */
      protected boolean pre(boolean batchWr, int lockOp, int nodeOp)
      {
         if (!pre(batchWr, lockOp))
         {
            return false;
         }

         return canAccessNode(nodeId, nodeOp);
      }
      
      /**
       * Lock node and check access rights to node and attribute.
       * 
       * @param   batchWr
       *          Flag for <code>synchBatch()</code>.
       * @param   lockOp
       *          Locking mode for <code>LockManager</code>.
       * @param   nodeOp
       *          Node access mode.
       * @param   attrOp
       *          Attribute access mode.
       *
       * @return  <code>true</code> if preliminary steps were successful.
       */
      protected boolean pre(boolean batchWr, int lockOp, int nodeOp,
                            int attrOp)
      {
         if (!pre(batchWr, lockOp))
         {
            return false;
         }
         
         return canAccessNodeAndAttribute(nodeId, nodeOp, name, attrOp);
      }

      /**
       * Perform cleanup.
       */
      void release()
      {
         if (lock != null && remapper == backend)
         {
            lockManager.releaseLock(lock);
         }
      }
   }

   /**
    * Helper class for accessing nodes.
    */
   class NodeLocker
   extends LockHelper
   {
      /**
       * Construct an instance for specified node ID and attribute name.
       * 
       * @param   nodeId
       *          Node ID for which operation will be performed.
       */
      NodeLocker(String nodeId)
      {
         super(nodeId, null);
      }

      /**
       * {@inheritDoc}
       */
      protected boolean lock()
      {
         throw new RuntimeException("Should not happen!!!");
      }

      /**
       * Lock node for Read-Only access.
       * 
       * @return  <code>true</code> if operation was successful.
       */
      boolean lockRO()
      {
         return pre(false, LockManager.LOCK_RO, 
                    DirectoryResource.DIR_READ_ACCESS);
      }
      
      /**
       * Lock node for Read/Write access.
       * 
       * @return  <code>true</code> if operation was successful.
       */
      boolean lockRW()
      {
         return pre(true, LockManager.LOCK_RW, 
                    DirectoryResource.DIR_WRITE_ACCESS);
      }
   }
   
   /**
    * Utility class which is used to copy subtree into different place.
    */
   private static class NodeCopier
   implements NodeProcessor
   {
      /** Back-end where subtree will be copied */
      private final Remapper destination;

      /** Base ID for the destination back-end */
      private final String dstBase;

      /** Base ID for the source back-end */
      private final String srcBase;

      /** Recursive copying mode. */
      private final boolean recursive;

      /**
       * Construct an instance of NodeCopier for specified source ID,
       * destination ID and back-end.
       * 
       * @param   srcBase
       *          Node ID for the root of the source subtree.
       * @param   destination
       *          Reference to the destination back-end.
       * @param   dstBase
       *          Node ID for the root of the new subtree
       * @param   recursive
       *          <code>true</code> enables recursive copying. Otherwise 
       *          copying will be stopped after first copy operations. 
       */
      NodeCopier(String srcBase, 
                 Remapper destination, 
                 String dstBase, 
                 boolean recursive)
      {
         this.srcBase = srcBase;
         this.dstBase = dstBase;
         this.destination = destination;
         this.recursive   = recursive;
      }

      /**
       * Implementation of the <code>NodeProcessor</code> interface. This
       * method copies node specified by the <code>nodeId</code> from
       * back-end referenced by <code>service</code> parameter into
       * <code>destination</code> back-end.
       * 
       * @param   service
       *          Remapper instance which is traversed
       * @param   nodeId
       *          Node Id
       *
       * @return  An object instance if tree traversal should be stopped or
       *          <code>null</code> to continue operation.
       */
      public Object visit(Remapper service, String nodeId)
      {
         //Copy node
         if (nodeId.startsWith("/backup")) //Avoid copying "/backup" subtree
         {
            return (recursive) ? Boolean.FALSE : null;
         }

         String offsetId = nodeId.substring(srcBase.length());

         Attribute[] list = getNodeAttributes(service, nodeId);
         String nodeClass = service.getNodeClassName(nodeId);

         if (!addNode(destination, dstBase + offsetId, nodeClass, list, true,
                      false))
         {
            return Boolean.FALSE;
         }

         return (recursive) ? null : Boolean.TRUE;
      }
   }

   /**
    * Utility class which is used for the cleanups after various operations.
    */
   private static class NodeRemover
   implements NodeProcessor
   {
      /**
       * Implementation of the <code>NodeProcessor</code> interface. This
       * method removes node specified by the <code>nodeId</code> from
       * back-end referenced by <code>service</code> parameter.
       * 
       * @param   service
       *          Remapper instance which is traversed
       * @param   nodeId
       *          Node Id
       * @return  <code>null</code>.
       */
      public Object visit(Remapper service, String nodeId)
      {
         //Remove node
         
         //Ignore error here. We need to delete everything which can be 
         //deleted, if that can't be deleted then we don't care - it also
         //can't be deleted by application.
         service.deleteNode(nodeId);

         return null;
      }
   }
   
   /**
    * Utility class which is used for the rolling back of the transactions.
    */
   private static class Rollback
   implements NodeProcessor
   {
      private int updated = 0;

      /**
       * Implementation of the <code>NodeProcessor</code> interface. This
       * method checks for presence of the <code>lock object</code> and if
       * it is found then transaction is rolled back. If
       * <code>lock object</code> describes backed up transaction then all
       * changes are reversed. If <code>lock object</code> describes
       * transaction which is not backed up then no changes were done so it is
       * enough to just remove <code>lock object</code>.
       * 
       * @param   service
       *          Remapper instance which is traversed
       * @param   nodeId
       *          Node Id
       * @return  An object instance if tree traversal should be stopped or
       *          <code>null</code> to continue operation.
       */
      public Object visit(Remapper service, String nodeId)
      {
         String nodeClass = service.getNodeClassName(nodeId + "/lock");
         boolean rc = "lock".equals(nodeClass);

         logTrace("Analyzing {" + nodeId + "} for lock object.");

         if (!rc)
         {
            return null;
         }
         
         updated++;

         logList("Found lock object as {" + nodeId + "/lock}");

         //Check attributes
         Boolean res = service.getNodeBoolean(nodeId + "/lock", "backed-up", 0);
         String backupId = service.getNodeString(nodeId + "/lock", "backupId", 0);

         logTrace("Lock object attributes: {backed-up = " + res + ", backupId = " + backupId + "}");

         do
         {
            if (res == null || !res.booleanValue() || backupId == null)
            {
               //No such attribute or backup was not finished
               logTrace("Main tree was not changed, nothing to roll back");
               break;
            }

            logTrace("Rolling back changes start");

            ShadowRemapper shadow = new ShadowRemapper(service);
            
            rc = shadow.rollback(nodeId, backupId);
            
            logTrace("Rolling back changes rc = " + rc);

            if (rc)
            {
               //Ignore errors here since restoring of the tree was successful
               pruneSubtree(service, backupId, false);

               logTrace("Removing backup data rc = " + rc);

               service.deleteNode(backupId);

               logTrace("Removing backup root rc = " + rc);
            }
         }
         while (false);

         // do not try to remove lock, if rollback failed
         if (rc)
         {
            //Finally remove lock
            rc = service.deleteNode(nodeId + "/lock");

            logTrace("Removing lock object {" + nodeId + "/lock" + "} rc = " + rc);

            // "/lock" node might be missing at this point, so it is safe to
            // ignore the error.
            rc = true;
            
            logList("End of processing of lock object {" + nodeId + "/lock}");
         }

         return (rc ? null : new Object());
      }

      /**
       * Get number of updated nodes.
       * 
       * @return   number of updated nodes.
       */
      public int getUpdated()
      {
         return updated;
      }
   }
   
   private class ExpirableLock
   implements Runnable
   {
      /** Directory lock. */
      private final Object lock;

      /** Milliseconds to wait before expiration. */
      private final long millis;

      /** Initial milliseconds count. */
      private final long start;

      /** Expiration flag. */
      private boolean expired = false; 

      /**
       * @param lock
       * @param seconds
       */
      public ExpirableLock(Object lock, int seconds)
      {
         this.lock = lock;
         this.millis = seconds*1000;
         
         Thread t = new Thread(this);
         t.setDaemon(true);
         
         this.start = System.currentTimeMillis();
         t.start();
      }

      /**
       * @see java.lang.Runnable#run()
       */
      @Override
      public void run()
      {
         try
         {
            for(;;)
            {
               Thread.sleep(500);

               if (System.currentTimeMillis() - start >= millis)
                  break;
            }
         }
         catch (InterruptedException e)
         {
            // just ignore it and handle as timeout
         }
         finally
         {
            synchronized (this)
            {
               cancelRefresh();
               expired = true;
            }
         }
      }

      /**
       * 
       */
      public void unlock()
      {
         lockManager.releaseLock(lock);
      }

      /**
       * Check if lock is expired.
       * 
       * @return  <code>true</code> if lock is expired and false otherwise.
       */
      public boolean isExpired()
      {
         return expired;
      }
   }
   
   /**
    * Wraps old <code>ContextLocal&lt;BatchRef&gt; activeBatch</code> and  
    * <code>ContextLocal&lt;BindRef&gt; bound</code> and has the purpose of 
    * cleaning them up when needed. 
    * Mainly this will happen when <code>DirectoryService.unbind()</code> is 
    * called from <code>BindRef.cleanup()</code>.
    * <p>
    * The reason of this class is to eliminate a small bug slipped in when 
    * fixing a session deadlock in P2J server virtual by clearing the tokenMap 
    * at the start of the SecurityContext.cleanup() method.<br>
    * As a consequence, any <code>Cleanable.cleanup()</code> code must not 
    * rely on context-local variables, as at the time of this call, the 
    * context-local variables are already deleted from the 
    * <code>SecurityContext</code> (by the 
    * <code>SecurityContext.cleanup()</code> method). 
    */
   private static class WorkArea 
   implements Cleanable 
   {
      /** active batch variable */
      private BatchRef activeBatch = null;
      
      /** bind reference variable */
      private BindRef bound = null;
      
      /**
       * Do the cleanup when it is time to finalize.
       */
      @Override
      public void cleanup() 
      {
         if (activeBatch != null) 
         {
            //always cleanup activeBatch before cleaning bound
            activeBatch.cleanup(this);
         }
         if (bound != null) 
         {
            bound.cleanup(this);
         }
      }
   }
}