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