RamRemapper.java
/*
** Module : RamRemapper.java
** Abstract : Memory-only back-end implementation.
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 SIY 20050406 @21026 Created initial version by extracting
** common code from the XmlRemapper.
** 002 SIY 20050506 @21200 Moved splitId() to IdUtils, updated documentation.
** 003 SIY 20050524 @21262 Fixed renaming nodes.
** 004 SIY 20090514 @42182 Added refresh() method.
** 005 SIY 20090712 @43140 Used isLeaf() method instead of chained call.
** 006 NVS 20090928 @44046 Logging exceptions instead of printing stack trace
** to stdout.
** 007 GES 20101119 Better exception logging to provide specific detail
** on the directory path to a syntax failure.
** 008 TJD 20220504 Java 11 compatibility minor changes
** 009 SP 20250417 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;
/**
* Memory-only back-end implementation. The memory-only mode is used by
* various other package classes to represent P2J Directory in the memory and
* other purposes.
*
* @author SIY
* @version 1.0
*/
class RamRemapper
implements Remapper
{
/** Object which is used synchronize access to tree root */
protected final Object mutex = new Object();
/** Root node for the in memory representation */
protected RamNode root = null;
/** Track bind/unbind calls */
private boolean bound = false;
/** Schema container */
private SchemaStorage schema;
/**
* Construct an instance of the XmlRemapper for a given file.
*/
RamRemapper()
{
schema = SchemaStorage.getSchema();
setRoot(null);
}
/**
* {@inheritDoc}
*/
public boolean addNode(String nodeId, String nodeClass, String[] names,
Object[] values)
{
if (!isBound())
{
return false;
}
String[] path = IdUtils.splitId(nodeId);
if (path == null) //Invalid path or something
{
return false;
}
synchronized (mutex)
{
RamNode parent = locateNode(path[0]);
if (parent == null)
{
return false;
}
//This check now is performed in the DirectoryService class
//if (parent.getNodeClass().isClassLeaf())
// return false;
if (parent.getChild(path[1]) != null) //Child already exists
{
return false;
}
ObjectClass objClass = getObjClass(nodeClass);
if (objClass == null)
{
return false;
}
RamNode node = new RamNode(objClass, path[1]);
if (names != null && values != null)
{
if (names.length != values.length)
{
return false;
}
for (int i = 0; i < names.length; i++)
{
boolean rc = node.addAttributeValue(names[i], values[i], true);
if (!rc)
{
return false;
}
}
}
if (!node.isValid())
{
return false;
}
return addNodeExt(parent, node);
}
}
/**
* {@inheritDoc}
*/
public boolean addNodeBitField(String nodeId, String name, BitField value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeBitSelector(String nodeId, String name,
BitSelector value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeBoolean(String nodeId, String name, boolean value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeByteArray(String nodeId, String name, byte[] value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeDate(String nodeId, String name, DateValue value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeDouble(String nodeId, String name, double value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeInteger(String nodeId, String name, int value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeString(String nodeId, String name, String value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean addNodeTime(String nodeId, String name, TimeValue value)
{
return addNodeValue(nodeId, name, value);
}
/**
* {@inheritDoc}
*/
public boolean bind()
{
try
{
synchronized (mutex)
{
if (isBound())
{
return false;
}
load();
bound = true;
}
}
catch (Exception e)
{
DirectoryService.logError(e.getMessage(), e);
setRoot(null);
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean deleteNode(String nodeId)
{
if (!isBound())
{
return false;
}
String[] path = IdUtils.splitId(nodeId);
if (path == null) //Invalid path or something
{
return false;
}
synchronized (mutex)
{
//RamNode node = locateNode(nodeId);
RamNode parent = locateNode(path[0]);
if (parent == null)
{
return false;
}
RamNode node = parent.getChild(path[1]);
if (node == null)
{
return false;
}
if (node.getChildCount() > 0)
{
return false;
}
return deleteNodeExt(parent, path[1]);
}
}
/**
* {@inheritDoc}
*/
public boolean deleteNodeAttribute(String nodeId, String name)
{
if (!isBound())
{
return false;
}
synchronized (mutex)
{
RamNode node = locateNode(nodeId);
if (node == null)
{
return false;
}
return deleteNodeAttributeExt(node, name);
}
}
/**
* {@inheritDoc}
*/
public boolean deleteNodeAttributeValue(String nodeId, String name,
int index)
{
if (!isBound())
{
return false;
}
synchronized (mutex)
{
RamNode node = locateNode(nodeId);
if (node == null)
{
return false;
}
return deleteNodeAttributeValueExt(node, name, index);
}
}
/**
* {@inheritDoc}
*/
public AttributeDefinition[] enumerateNodeAttributes(String nodeId)
{
if (!isBound())
{
return null;
}
synchronized (mutex)
{
RamNode node = locateNode(nodeId);
if (node == null)
{
return null;
}
AttributeDefinition[] src = node.getNodeClass().getClassDefinition();
if (src == null)
{
return src;
}
//Create a copy of the array but not elements.
//There is no need to copy elements since
//AttributeDefinition is "read only".
AttributeDefinition[] res = new AttributeDefinition[src.length];
for (int i = 0; i < src.length; i++)
{
res[i] = src[i];
}
return res;
}
}
/**
* {@inheritDoc}
*/
public String[] enumerateNodes(String nodeId)
{
if (!isBound())
{
return null;
}
synchronized (mutex)
{
RamNode node = locateNode0(nodeId);
if (node == null)
{
return null;
}
RamNode[] childList = node.getChildList();
if (childList == null)
{
return null;
}
String[] ret = new String[childList.length];
for (int i = 0; i < ret.length; i++)
{
ret[i] = new String(childList[i].getName());
}
return ret;
}
}
/**
* {@inheritDoc}
*/
public AttributeDefinition[] getClassDefinition(String className)
{
ObjectClass objClass = getObjClass(className);
if (objClass == null)
{
return null;
}
return objClass.getClassDefinition();
}
/**
* {@inheritDoc}
*/
public String[] getClassNames()
{
return schema.getClassNames();
}
/**
* {@inheritDoc}
*/
public BitField getNodeBitField(String nodeId, String name, int index)
{
return (BitField)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public BitSelector getNodeBitSelector(String nodeId, String name, int index)
{
return (BitSelector)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public Boolean getNodeBoolean(String nodeId, String name, int index)
{
return (Boolean)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public byte[] getNodeByteArray(String nodeId, String name, int index)
{
return (byte[])getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public String getNodeClassName(String nodeId)
{
if (!isBound())
{
return null;
}
synchronized (mutex)
{
RamNode node = locateNode(nodeId);
if (node == null)
{
return null;
}
return node.getNodeClass().getName();
}
}
/**
* {@inheritDoc}
*/
public DateValue getNodeDate(String nodeId, String name, int index)
{
return (DateValue)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public Double getNodeDouble(String nodeId, String name, int index)
{
return (Double)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public Integer getNodeInteger(String nodeId, String name, int index)
{
return (Integer)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public String getNodeString(String nodeId, String name, int index)
{
return (String)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public TimeValue getNodeTime(String nodeId, String name, int index)
{
return (TimeValue)getNodeValue(nodeId, name, index);
}
/**
* {@inheritDoc}
*/
public boolean isClassImmutable(String className)
{
ObjectClass objClass = getObjClass(className);
if (objClass == null)
{
return true;
}
return objClass.isClassImmutable();
}
/**
* {@inheritDoc}
*/
public boolean isClassLeaf(String className)
{
ObjectClass objClass = getObjClass(className);
if (objClass == null)
{
return false;
}
return objClass.isClassLeaf();
}
/**
* {@inheritDoc}
*/
public boolean moveNode(String nodeId, String newId)
{
if (!isBound())
{
return false;
}
String[] path = IdUtils.splitId(nodeId);
String[] newPath = IdUtils.splitId(newId);
if (path == null || newPath == null)
{
return false;
}
synchronized (mutex)
{
RamNode parent = locateNode(path[0]);
RamNode newParent = locateNode(newPath[0]);
if (parent == null || newParent == null)
{
return false;
}
if(!path[0].equals(newPath[0]))
{
if (parent.getChild(path[1]) == null ||
newParent.getChild(path[1]) != null)
{
return false;
}
}
if (newParent.isLeaf())
{
return false;
}
return moveNodeExt(parent, path[1], newParent, newPath[1]);
}
}
/**
* {@inheritDoc}
*/
public boolean setNodeBitField(String nodeId, String name, int index,
BitField value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeBitSelector(String nodeId, String name, int index,
BitSelector value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeBoolean(String nodeId, String name, int index,
boolean value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeByteArray(String nodeId, String name, int index,
byte[] value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeDate(String nodeId, String name, int index,
DateValue value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeDouble(String nodeId, String name, int index,
double value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeInteger(String nodeId, String name, int index,
int value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeString(String nodeId, String name, int index,
String value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean setNodeTime(String nodeId, String name, int index,
TimeValue value)
{
return setNodeValue(nodeId, name, index, value);
}
/**
* {@inheritDoc}
*/
public boolean unbind()
{
try
{
synchronized (mutex)
{
if (!isBound())
{
return false;
}
//No need to save it here.
//save();
bound = false;
}
}
catch (Exception e)
{
DirectoryService.logError(e.getMessage());
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean update()
{
try
{
save();
return true;
}
catch (Exception e)
{
DirectoryService.logError(e.getMessage());
}
return false;
}
/**
* Generic implementation.
*
* @see com.goldencode.p2j.directory.Remapper#refresh()
*/
public boolean refresh()
{
if (!isBound())
{
return true;
}
RamNode root = getRoot();
try
{
setRoot(null);
load();
}
catch (Exception e)
{
DirectoryService.logError(e.getMessage());
setRoot(root);
return false;
}
// by default we do nothing
return true;
}
/**
* This method is invoked by <code>addAttributeValue()</code> when most
* checks are passed and it is about to add new value to the attribute.
* Derived classes should override it if they need to handle this
* operation. The main reason of this method is that it is invoked inside
* synchronisation block and therefore derived class does not need to worry
* about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param node
* A node from which attribute value is requested.
* @param name
* Name of the attribute.
* @param val
* New value which will be added to the attribute.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean addAttributeValueExt(RamNode node, String name,
Object val)
{
return node.addAttributeValue(name, val, false);
}
/**
* This method is invoked by <code>addNode()</code> when all checks are
* passed and it is about to add new node to the tree. Derived classes
* should override it if they need to handle adding node somehow. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param parent
* A node to which <code>newNode</code> will be added.
* @param newNode
* New node which need to be added to <code>parent</code>.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean addNodeExt(RamNode parent, RamNode newNode)
{
return parent.addChild(newNode);
}
/**
* This method is invoked by <code>deleteNodeAttribute()</code> when most
* checks are passed and it is about to delete attribute. Derived classes
* should override it if they need to handle this operation. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param node
* A node from which attribute value is requested.
* @param name
* Name of the attribute.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeAttributeExt(RamNode node, String name)
{
return node.removeAttribute(name);
}
/**
* This method is invoked by <code>deleteNodeAttribute()</code> when most
* checks are passed and it is about to delete attribute value. Derived
* classes should override it if they need to handle this operation. The
* main reason of this method is that it is invoked inside synchronisation
* block and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
* <p>
* Note that according to specification attribute must be deleted if last
* value is removed.
*
* @param node
* A node from which attribute value will be deleted.
* @param name
* Name of the attribute.
* @param index
* Attribute value index.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeAttributeValueExt(RamNode node, String name,
int index)
{
Attribute attr = node.getAttribute(name);
if (attr == null)
{
return false;
}
if (!attr.deleteValue(index))
{
return false;
}
if (attr.getCount() == 0)
{
return node.removeAttribute(name);
}
return true;
}
/**
* This method is invoked by <code>deleteNode()</code> when all checks are
* passed and it is about to remove node from the tree. Derived classes
* should override it if they need to handle removing of the node somehow. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param parent
* A node to which <code>newNode</code> will be added.
* @param child
* An ID of the child node to remove.
*
* @return <code>true</code> if operation was successful and
* <code>false</code> otherwise.
*/
protected boolean deleteNodeExt(RamNode parent, String child)
{
return parent.removeChild(child);
}
/**
* This method is invoked by <code>getNodeValue()</code> when most checks
* are passed and it is about to retrieve attribute value. Derived classes
* should override it if they need to handle this operation. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param node
* A node from which attribute value is requested.
* @param name
* Name of the attribute.
* @param ndx
* Index of the attribute value.
*
* @return Attribute value.
*/
protected Object getNodeValueExt(RamNode node, String name, int ndx)
{
Attribute attr = node.getAttribute(name);
if (attr == null)
{
return null;
}
return attr.getValue(ndx);
}
/**
* Return ObjectClass for a given name.
*
* @param name
* Class name to find.
*
* @return ObjectClass if class with such a name exists and
* <code>null</code> otherwise.
*/
protected ObjectClass getObjClass(String name)
{
return schema.getObjectClass(name);
}
/**
* Verifies that calling thread is properly bound.
*
* @return <code>true</code> if thread is properly bound.
*/
protected boolean isBound()
{
return bound;
}
/**
* Load tree from external storage.
*
* @throws Exception
* In case of various errors. The use of Exception here is
* intentional: child classes must be allowed throw other
* exceptions without changing method signature.
*/
protected void load()
throws Exception
{
}
/**
* Locate node for given nodeId. If there is no such node then
* <code>null</code> is returned.
*
* @param id
* Node id to locate.
* @return Reference to node.
*/
protected RamNode locateNode(String id)
{
return locateNode0(id);
}
/**
* This method is invoked by <code>moveNode()</code> when most checks are
* passed and it is about to move node to the new location. Derived classes
* should override it if they need to handle this operation. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
* <p>
* Note that derived class can check if parent and newParent parameters
* point to the same node. In many cases such a situation can be performed
* in more efficient way.
*
* @param parent
* A parent node of the node which is about to be moved to new
* location.
* @param name
* Name of the node which will be moved.
* @param newParent
* A node which will be new parent node if operation will be
* successful.
* @param newName
* New name of the node.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean moveNodeExt(RamNode parent, String name,
RamNode newParent, String newName)
{
RamNode node = parent.getChild(name);
if (parent == newParent && name.equals(newName))
{
return true;
}
parent.removeChild(name);
node.setName(newName);
newParent.addChild(node);
return true;
}
/**
* Save current tree into external storage.
*
* @throws Exception
* In case of various errors. The use of Exception here is
* intentional: child classes must be allowed throw other
* exceptions without changing method signature.
*/
protected void save()
throws Exception
{
}
/**
* This method is invoked by <code>setNodeValue()</code> when most checks
* are passed and it is about to change attribute value. Derived classes
* should override it if they need to handle this operation. The main
* reason of this method is that it is invoked inside synchronisation block
* and therefore derived class does not need to worry about this.
* <p>
* Note that derived class must call this method before successful exit.
*
* @param node
* A node which contains the attribute to change.
* @param name
* Attribute name.
* @param index
* Index of the value to retrieve.
* @param val
* New value for the attribute variable.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
protected boolean setNodeValueExt(RamNode node, String name, Object val,
int index)
{
return node.setAttributeValue(name, val, index);
}
/**
* Return root of the three.
*
* @return Reference to root node.
*/
RamNode getRoot()
{
return root;
}
/**
* Set tree root.
*
* @param node
* The root node of the tree.
*/
void setRoot(RamNode node)
{
root = node;
if (root == null)
root = new RamNode(getObjClass("container"), "");
}
/**
* Add new value to the attribute.
*
* @param id
* Node identifier.
* @param name
* Attribute name.
* @param val
* New value for the attribute variable.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean addNodeValue(String id, String name, Object val)
{
if (!isBound())
{
return false;
}
synchronized (mutex)
{
RamNode node = locateNode(id);
if (node == null)
{
return false;
}
return addAttributeValueExt(node, name, val);
}
}
/**
* Return value of specified variable of node attribute.
*
* @param id
* Node identifier.
* @param name
* Attribute name.
* @param ndx
* Index of the value to retrieve.
*
* @return Value of the attribute for the specified attribute name and
* index or <code>null</code>.
*/
private Object getNodeValue(String id, String name, int ndx)
{
if (!isBound())
{
return null;
}
synchronized (mutex)
{
RamNode node = locateNode(id);
if (node == null)
{
return null;
}
return getNodeValueExt(node, name, ndx);
}
}
/**
* This is actual worker for the <code>locateNode()</code> method.
*
* @param id
* Node id to locate.
*
* @return Reference to node.
*/
private RamNode locateNode0(String id)
{
id = IdUtils.normalize(id);
if (id == null)
{
return null;
}
String[] res = id.split("/", 0);
RamNode node = getRoot();
for (int i = 1; i < res.length; i++)
{
node = node.getChild(res[i]);
if (node == null)
{
return null;
}
}
return node;
}
/**
* Set 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.
*
* @param id
* Node identifier.
* @param name
* Attribute name.
* @param index
* Index of the value to retrieve.
* @param val
* New value for the attribute variable.
*
* @return <code>true</code> is operation is successful and
* <code>false</code> otherwise.
*/
private boolean setNodeValue(String id, String name, int index, Object val)
{
if (!isBound())
{
return false;
}
synchronized (mutex)
{
RamNode node = locateNode(id);
if (node == null)
{
return false;
}
return setNodeValueExt(node, name, val, index);
}
}
}