XNodeRefImpl.java
/*
** Module : XNodeRefImpl
** Abstract : DOM XML Node reference resource type implementation.
**
** Copyright (c) 2013-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 EVL 20130204 Created initial version. Major rework to use w3c backend.
** 002 CA 20130221 Added LOCAL-NAME support.
** 003 EVL 20130222 Adding NODE-VALUE attribute and SET-ATTRIBUTE-NODE method.
** 004 EVK 20130531 Adding NODE-VALUE attribute and GET-PARENT, GET-ATTRIBUTE-NODE, NORMALIZE
** LONGCHAR-TO-NODE-VALUE, NODE-VALUE-TO-LONGCHAR, CLONE-NODE methods.
** 005 CA 20130930 Changes related to determining resource type from LegacyResource annotation.
** 006 HC 20131215 Implemented UNIQUE-ID attribute.
** 007 CA 20140104 Changed getOwnerDocument to allow restricted access (required by SOAP Fault
** Detail resource).
** 008 CA 20160428 Fixed getOwnerDocument - must return a XDocumentImpl handle.
** 009 OM 20201120 Renamed NAMESPACE-PREFIX setter to fit buffer attributes naming convention.
** 010 CA 20220605 Fixed 'getLocalName' to return the node name if it is not namespace-aware.
** 011 CA 20230720 NAMESPACE-PREFIX setter with an unknown value must raise error 4083.
** LOCAL-NAME must return empty space if is null.
** 012 ICP 20250129 Used logical and character constants to leverage cached instances.
*/
/*
** 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.xml;
import org.w3c.dom.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;
/**
* Implementation of the X-Node-Reference specific features of the Progress
* DOM XML support objects.
*/
public class XNodeRefImpl
extends XEntityImpl
implements XNodeRef
{
/** The key for a restricted document mapping in the {@link Document}'s user-data. */
public static final String RESTRICTED_DOCUMENT = "RESTRICTED_DOCUMENT";
/**
* Default constructor.
*/
public XNodeRefImpl()
{
super(IdKind.X_NODEREF);
}
/**
* Special constructor accepting the namespace URI specification to make
* another node reference case.
*
* @param node
* The node associated with this node.
*/
public XNodeRefImpl(Node node)
{
super(node, IdKind.X_NODEREF);
}
// Attributes
/**
* Getting the attribute names available for current node reference.
*
* @return The comma separated character string of all available
* attributed.
*/
@Override
public character getAttributeNames()
{
if (!isInitialized())
{
invalidNodeError("ATTRIBUTE-NAMES");
return new character();
}
StringBuilder sb = new StringBuilder();
NamedNodeMap nnm = getNode().getAttributes();
if (nnm == null)
{
return new character("");
}
int iSize = nnm.getLength();
for (int i = 0; i < iSize; i++)
{
sb.append(((Attr)nnm.item(i)).getName());
if (i < iSize - 1)
{
sb.append(',');
}
}
return new character(sb.toString());
}
/**
* Getting the value of the XML node value attribute.
*
* @return The character value of the node reference.
*/
@Override
public character getNodeValue()
{
if (!isInitialized())
{
invalidNodeError("NODE-VALUE");
return new character();
}
try
{
String nodeValue = getNode().getNodeValue();
return new character(nodeValue == null ? "" : nodeValue);
}
catch (DOMException e)
{
if (e.code == DOMException.DOMSTRING_SIZE_ERR)
{
commonDomError("NODE-VALUE", "Too long node-value");
}
return new character();
}
}
/**
* Setting the value of the XML node value attribute.
*
* @param value
* The new value of the node value attribute.
*/
@Override
public void setNodeValue(character value)
{
if (value.isUnknown())
{
invalidArgumentAssignError("NODE-VALUE");
return;
}
if (!isInitialized())
{
String[] errMsg =
{
"X-NODEREF must be associated with a valid X-DOCUMENT in order to use it in " +
"method NODE-VALUE",
"Unable to set attribute NODE-VALUE in widget of type X-NODEREF"
};
int[] errorCodes = { 9102, 3131 };
ErrorManager.recordOrShowError(errorCodes, errMsg, false, false, true);
return;
}
Node node = getNode();
int nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE || nodeType == Node.ENTITY_REFERENCE_NODE)
{
String[] errMsg =
{
"X-NODEREF or X-DOCUMENT NODE-VALUE got an error: An " +
"attempt was made to modify an object where modifications are not allowed",
"Unable to set attribute NODE-VALUE in widget of type X-NODEREF"
};
int[] errorCodes = { 9082, 3131 };
ErrorManager.recordOrShowError(errorCodes, errMsg, false, false, true);
return;
}
try
{
node.setNodeValue(value.toStringMessage());
}
catch (DOMException e)
{
if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
String[] errMsg =
{
"X-NODEREF or X-DOCUMENT NODE-VALUE got an error: An " +
"attempt was made to modify an object where modifications are not allowed",
"Unable to set attribute NODE-VALUE in widget of type X-NODEREF"
};
int[] errorCodes = { 9082, 3131 };
ErrorManager.recordOrShowError(errorCodes, errMsg, false, false, true);
return;
}
}
}
/**
* Setting the value of the XML node value attribute.
*
* @param value
* The new value of the node value attribute.
*/
@Override
public void setNodeValue(String value)
{
setNodeValue(new character(value));
}
/**
* Creating the new node within the current Document object.
*
* @param xNodeRef The valid handle class instance to use for the cloned XML node.
* @param deep A logical that if <code>true</code> specifies that the whole sub-trees is to be
* copied. The default value is {@code false}.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical cloneNode(handle xNodeRef, logical deep)
{
if (!isInitialized())
{
invalidNodeError("CLONE-NODE");
return new logical(false);
}
if (!isXNodeHandle(xNodeRef))
{
invalidArgumentError("CLONE-NODE");
return new logical(false);
}
Node node = getNode();
Node clonedNode = node.cloneNode(deep.booleanValue());
setNodeToHandle(xNodeRef, clonedNode);
return xNodeRef.isValid();
}
/**
* Creating the new node within the current Document object.
*
* @param xNodeRef The valid handle class instance to use for the cloned XML node.
* @param deep A logical that if <code>true</code> specifies that the whole sub-trees is to
* be copied. The default value is {@code false}.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical cloneNode(handle xNodeRef, boolean deep)
{
return cloneNode(xNodeRef, new logical(deep));
}
/**
* Getting the Parent of current node.
*
* @param hNoderefParent
* A valid handle to assign parent XML node.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical getParent(handle hNoderefParent)
{
if (!isInitialized())
{
invalidNodeError("GET-PARENT");
return new logical(false);
}
if (!isXNodeHandle(hNoderefParent))
{
invalidArgumentError("GET-PARENT");
return new logical(false);
}
Node node = getNode().getParentNode();
setNodeToHandle(hNoderefParent, node);
return hNoderefParent.isValid();
}
// Methods
/**
* Deleting the internals of the current X-Node-Reference.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical deleteNode()
{
if (!isInitialized())
{
invalidNodeError("DELETE-NODE");
return new logical(false);
}
Node node = getNode();
Node parentNode = node.getParentNode();
if (parentNode == null)
{
invalidNodeError("DELETE-NODE");
return new logical(false);
}
try
{
parentNode.removeChild(node);
}
catch (DOMException e)
{
if (e.code == DOMException.NOT_FOUND_ERR)
{
invalidNodeError("DELETE-NODE");
}
else if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
invalidNodeError("DELETE-NODE");
}
return new logical(false);
}
return new logical(true);
}
/**
* Getting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to return.
*
* @return The comma separated character string of all available
* attributed.
*/
@Override
public character getAttribute(String name)
{
return getAttribute(new character(name));
}
/**
* Getting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to return.
*
* @return Value as a character string, or the empty string
* if that attribute does not have a specified or default value.
*/
@Override
public character getAttribute(character name)
{
if (!isInitialized())
{
invalidNodeError("GET-ATTRIBUTE");
return new character();
}
if (name.isUnknown())
{
invalidArgumentError("GET-ATTRIBUTE");
return new character();
}
Node node = getNode();
String result = null;
if (node.getNodeType() == Node.ELEMENT_NODE)
{
Element elementNode = (Element) node;
result = elementNode.getAttribute(name.toStringMessage());
}
return new character((result == null) ? "" : result);
}
/**
* Setting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to change.
* @param value
* The new value for the given attribute name.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical setAttribute(String name, String value)
{
return setAttribute(new character(name), new character(value));
}
/**
* Setting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to change.
* @param value
* The new value for the given attribute name.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical setAttribute(String name, character value)
{
return setAttribute(new character(name), value);
}
/**
* Setting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to change.
* @param value
* The new value for the given attribute name.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical setAttribute(character name, String value)
{
return setAttribute(name, new character(value));
}
/**
* Setting the attribute value for a given attribute name.
*
* @param name
* The name of the attribute to change.
* @param value
* The new value for the given attribute name.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical setAttribute(character name, character value)
{
if (!isInitialized())
{
invalidNodeError("SET-ATTRIBUTE");
return new logical(false);
}
if (name.isUnknown() || value.isUnknown())
{
invalidArgumentError("SET-ATTRIBUTE");
return new logical(false);
}
Node node = getNode();
final logical result;
if (node.getNodeType() == Node.ELEMENT_NODE)
{
Element elementNode = (Element) node;
try
{
elementNode.setAttribute(name.toStringMessage(), value.toStringMessage());
}
catch (DOMException e)
{
if (e.code == DOMException.INVALID_CHARACTER_ERR)
{
commonDomError("SET-ATTRIBUTE", "An invalid character was specified");
}
else if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("SET-ATTRIBUTE", "Read only node");
}
return new logical(false);
}
result = new logical(true);
}
else
{
commonDomError("SET-ATTRIBUTE", "Invalid node type");
result = new logical(false);
}
return result;
}
/**
* Associates XML attribute node with the given X-Noderef object handle.
*
* @param handle
* The handle that represents an XML attribute node created with create node or
* create node namespace.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical setAttributeNode(handle handle)
{
if (handle._isValid() && !handle.isUnknown() && handle.get() instanceof HandleResource)
{
HandleResource resource = (HandleResource) handle.get();
if (LegacyResource.X_DOCUMENT.equalsIgnoreCase(resource.type()))
{
invalidArgumentError("SET-ATTRIBUTE-NODE");
return new logical(false);
}
}
if (!isInitialized() || handle.isUnknown())
{
invalidNodeError("SET-ATTRIBUTE-NODE");
return new logical(false);
}
if (!isXNodeHandle(handle))
{
invalidArgumentError("SET-ATTRIBUTE-NODE");
return new logical(false);
}
Node currentNode = getNode();
Object resource = handle.get();
Node handleNode = ((XNodeRefImpl) resource).getNode();
final logical result;
if (currentNode.getNodeType() == Node.ELEMENT_NODE &&
handleNode.getNodeType() == Node.ATTRIBUTE_NODE)
{
Element elementNode = (Element) currentNode;
try
{
elementNode.setAttributeNode((Attr) handleNode);
}
catch (DOMException e)
{
if (e.code == DOMException.WRONG_DOCUMENT_ERR)
{
invalidNodeError("SET-ATTRIBUTE-NODE");
}
else if (e.code == DOMException.INUSE_ATTRIBUTE_ERR)
{
commonDomError("SET-ATTRIBUTE-NODE", "Attribute already exists");
}
else if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("SET-ATTRIBUTE-NODE",
"An attempt was made to modify an object " +
"where modifications are not allowed");
}
return new logical(false);
}
result = new logical(true);
}
else
{
commonDomError("SET-ATTRIBUTE-NODE", "Invalid node type");
result = new logical(false);
}
return result;
}
/**
* Associates XML attribute node with the specified name to the given X-Noderef object
* handle.
*
* @param handle
* The handle that represents an XML attribute node with the specified name.
* @param attributeName
* The string that represents an XML attribute node with the specified name.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical getAttributeNode(handle handle, String attributeName)
{
return getAttributeNode(handle, new character(attributeName));
}
/**
* Associates XML attribute node with the specified name to the given X-Noderef object
* handle.
*
* @param handle
* The handle that represents an XML attribute node with the specified name.
* @param attributeName
* The character that represents an XML attribute node with the specified name.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical getAttributeNode(handle handle, character attributeName)
{
if (handle._isValid() && !handle.isUnknown() && handle.get() instanceof HandleResource)
{
HandleResource resource = (HandleResource) handle.get();
if (LegacyResource.X_DOCUMENT.equalsIgnoreCase(resource.type()))
{
invalidArgumentError("GET-ATTRIBUTE-NODE");
return new logical(false);
}
}
if (!isXNodeHandle(handle) || attributeName.isUnknown())
{
invalidArgumentError("GET-ATTRIBUTE-NODE");
return new logical(false);
}
if (!isInitialized())
{
invalidNodeError("GET-ATTRIBUTE-NODE");
return new logical(false);
}
Node node = getNode();
final logical result;
if (node.getNodeType() == Node.ELEMENT_NODE)
{
Element elementNode = (Element) node;
Attr attrNode = elementNode.getAttributeNode(attributeName.toStringMessage());
if (attrNode != null)
{
setNodeToHandle(handle, attrNode);
}
result = handle.isValid();
}
else
{
commonDomError("GET-ATTRIBUTE-NODE", "Invalid node type");
result = new logical(false);
}
return result;
}
/**
* Setting the binary value of the XML node value attribute.
*
* @param value
* The new value of the node value attribute.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical longcharToNodeValue(longchar value)
{
if (!isInitialized())
{
invalidNodeError("LONGCHAR-TO-NODE-VALUE");
return new logical(false);
}
if (value == null || value.isUnknown())
{
invalidArgumentError("LONGCHAR-TO-NODE-VALUE");
return new logical(false);
}
Node node = getNode();
node.setTextContent(value.toStringMessage());
return new logical(true);
}
/**
* Setting the XML node value attribute to binary value of longchar.
*
* @param value
* To save the value on XML node to this parameter.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical nodeValueToLongchar(longchar value)
{
if (!isInitialized())
{
invalidNodeError("NODE-VALUE-TO-LONGCHAR");
return new logical(false);
}
value.setValue(getNode().getNodeValue());
return new logical(true);
}
/**
* Setting the binary value of the XML node value attribute.
*
* @param value
* The new value of the node value attribute.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical memptrToNodeValue(memptr value)
{
if (!isInitialized())
{
invalidNodeError("MEMPTR-TO-NODE-VALUE");
return new logical(false);
}
if (value == null || value.isUnknown())
{
invalidArgumentError("MEMPTR-TO-NODE-VALUE");
}
Node node = getNode();
try
{
node.setTextContent(new String(value.getByteArray()));
}
catch (DOMException e)
{
if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("MEMPTR-TO-NODE-VALUE", "Read only node");//TODO need correct message
return new logical(false);
}
}
return new logical(true);
}
/**
* Setting the XML node value attribute to binary value of memptr.
*
* @param value
* To save the value on XML node to this parameter.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical nodeValueToMemptr(memptr value)
{
if (!isInitialized())
{
invalidNodeError("NODE-VALUE-TO-MEMPTR");
return logical.FALSE;
}
value.setString(getNode().getNodeValue(), 1);
return logical.TRUE;
}
/**
* Normalizes TEXT and ATTRIBUTE nodes in the full depth of the sub-tree under this
* XML node.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical normalize()
{
if (!isInitialized())
{
invalidNodeError("NORMALIZE");
return logical.FALSE;
}
Node node = getNode();
short nodeType = node.getNodeType();
if (nodeType == Node.ELEMENT_NODE)
{
node.normalize();
}
else
{
commonDomError("NORMALIZE", "Invalid node type");
return logical.FALSE;
}
return logical.TRUE;
}
/**
* Get the value of the LOCAL-NAME attribute.
*
* @return See above.
*/
@Override
public character getLocalName()
{
if (!isInitialized())
{
invalidNodeError("LOCAL-NAME");
return character.UNKNOWN;
}
Node node = getNode();
String localName = node.getLocalName();
if (localName != null)
{
return new character(localName);
}
else
{
// otherwise, always return empty
return character.EMPTY_STRING;
}
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(character prefix)
{
if (!isInitialized())
{
String[] errMsg =
{
"X-NODEREF must be associated with a valid X-DOCUMENT in order " +
"to use it in method NAMESPACE-PREFIX",
"Unable to set attribute NAMESPACE-PREFIX in widget of type X-NODEREF"
};
int[] errorCodes = { 9102, 3131 };
ErrorManager.recordOrShowError(errorCodes, errMsg, false, false, true);
return;
}
if (prefix.isUnknown())
{
invalidArgumentAssignError("NAMESPACE-PREFIX");
return;
}
Node currentNode = getNode();
String prefixStr = prefix.getValue(); //null is ok here
try
{
if (currentNode.getNodeType() == Node.ELEMENT_NODE)
{
Element elementNode = (Element) currentNode;
elementNode.setPrefix(prefixStr);
}
else if (currentNode.getNodeType() == Node.ATTRIBUTE_NODE)
{
Attr attributeNode = (Attr) currentNode;
attributeNode.setPrefix(prefixStr);
}
}
catch (DOMException e)
{
if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("NAMESPACE-PREFIX",
"An attempt was made to modify an object where modifications are not allowed");
}
else if (e.code == DOMException.NAMESPACE_ERR ||
e.code == DOMException.INVALID_CHARACTER_ERR)
{
String[] errMsg =
{
"X-NODEREF or X-DOCUMENT NAMESPACE-PREFIX got an error: XML Parser not initialized",
"Unable to set attribute NAMESPACE-PREFIX in widget of type X-NODEREF"
};
int[] errorCodes = { 9082, 3131 };
ErrorManager.recordOrShowError(errorCodes, errMsg, false, false, true);
}
}
}
/**
* Gets the number of child for the current one. Only for XNodRef
*
* @return The integer number of the child, 1-based index of the child node.
*/
@Override
public integer getChildNum()
{
if (!isInitialized())
{
invalidNodeError("NUM-CHILDREN");
return new integer();
}
if (LegacyResource.X_DOCUMENT.equalsIgnoreCase(type()))
{
return new integer();
}
Node node = getNode();
Node parent = node.getParentNode();
if (parent == null)
{
return new integer(1);
}
NodeList childNodes = parent.getChildNodes();
for (int i = 0 ; i < childNodes.getLength(); i++)
{
if (node.equals(childNodes.item(i)))
{
return new integer(i + 1);
}
}
return new integer();
}
/**
* Getting the Document the current reference belongs to.
*
* @return The handle of the Document containing tne current reference.
*/
@Override
public handle getOwnerDocument()
{
if (!isInitialized())
{
invalidNodeError("OWNER-DOCUMENT");
return new handle();
}
Document doc = getNode().getOwnerDocument();
// check if access to the owner document is restricted
Object restricted = doc.getUserData(RESTRICTED_DOCUMENT);
if (restricted != null && (restricted instanceof Boolean) && ((Boolean) restricted))
{
final String msg = "X-NODEREF must be associated with a valid X-DOCUMENT in order to " +
"use it in method OWNER-DOCUMENT";
ErrorManager.recordOrShowError(9102, msg, false, false, false);
return new handle();
}
return new handle(new XDocumentImpl(doc));
}
}