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<BatchRef> activeBatch</code> and
* <code>ContextLocal<BindRef> 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);
}
}
}
}