XEntityImpl.java
/*
** Module : XEntityImpl
** Abstract : DOM and SAX common 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 EVL 20130214 Adding the base inheritance from XCommonImpl class to place the XML related
** common features.
** 003 CA 20130221 Added UNIQUE-ID and NAMESPACE-URI support.
** 004 EVL 20130222 Adding SUBTYPE attribute and INSERT-BEFORE method support.
** 005 OM 20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
** 006 OM 20130320 Fixed getChild() method, added Override annotations, cosmetics & typo fixes.
** 007 EVK 20130527 Adding support CHILD-NUM, NAMESPACE-PREFIX attributes and REPLACE-CHILD
** method.
** Add implementation for SUBTYPE, NUM-CHILDREN, CHILD-NUM, UNIQUE-ID,
** NAMESPACE-URI, NAME, NAMESPACE-PREFIX attributes and IMPORT-NODE,
** APPEND-CHILD, INSERT-BEFORE,REMOVE-CHILD, REPLACE-CHILD, GET-CHILD,
** REMOVE-ATTRIBUTE methods.
** Remove map Element->handle, because we don't need to store handle
** reference for children.
** xnode variable is now Node instead of Element.
** 008 CA 20130927 Removed unknown() API, as by default the resource is known.
** 009 SVL 20131203 Changes caused by renaming of Nameable.get/setName to Nameable.name.
** 010 HC 20131215 Implemented UNIQUE-ID attribute.
** 011 EVK 20131216 Moved up to hierarchy next methods:
** invalidArgumentError, invalidArgumentAssignError.
** 012 EVK 20140113 Removed method valid(moved to XCommonImpl class) and implemented resourceDelete
** method to properly remove the object.
** 013 CA 20140106 Made setNode public.
** 014 OM 20201120 Renamed attribute accessors to fit buffer attributes naming convention.
** EVL 20210218 Changed integer to int64 to match MathOps changes.
** OM 20210309 Removed deprecation warnings.
** ME 20210325 Throw error instead of show for invalid xml.
** CA 20211227 GetChild must show an error if the index is not a valid child, and not raise an ERROR
** condition.
** CA 20221006 UNIQUE-ID is kept as Java type instead of BDT. Refs #6827
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 015 CA 20230720 The NAMESPACE-URI setter with an unknown value must raise error 4083.
** 016 ICP 20250123 Used integer and logical constants to leverage caches 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 common features for DOM.
*/
public abstract class XEntityImpl
extends XCommonImpl
implements XEntity
{
/** Constant to specify uninitialized state for node if owner of this node has been deleted. */
static final String IS_INVALID_NODE = "IS_INVALID_NODE";
/** Element associates with current XEntity class. */
private Node xnode = null;
/** Corresponds to UNIQUE-ID attribute. */
private Long uniqueID;
/** The kind of this entity. */
private IdKind idKind;
/**
* Default constructor.
*
* @param idKind
* The discriminator for unique id allocation.
*/
protected XEntityImpl(IdKind idKind)
{
this.idKind = idKind;
}
/**
* Special constructor accepting a X-Document or X-Node-Reference object.
*
* @param node
* The DOM XML node object to be associated with the given
* handle object.
*
* @param idKind
* The discriminator for unique id allocation.
*/
protected XEntityImpl(Node node, IdKind idKind)
{
this(idKind);
xnode = node;
}
/**
* Utility method to set up node value into handle. If handle is not XNodeRef or XDocument
* type then do nothing.
*
* @param handle
* The handle class instance to set new node value.
* @param node
* The node value to set.
*/
static void setNodeToHandle(handle handle, Node node)
{
if (isXEntityHandle(handle))
{
((XEntityImpl)handle.getResource()).setNode(node);
}
}
/**
* Check the type of resource in handle. Return <code>true</code> if and only if handle
* contains resource of type XNodeRef.
*
* @param handle
* The handle class instance to check type.
*
* @return See above.
*/
static boolean isXNodeHandle(handle handle)
{
if (handle == null || !handle._isValid() || handle.isUnknown())
{
return false;
}
WrappedResource resource = handle.getResource();
return resource instanceof XNodeRefImpl;
}
/**
* Check the type of resource in handle. Return <code>true</code> if and only if handle
* contains resource of type XNodeRef or XDocument.
*
* @param handle
* The handle class instance to check type.
*
* @return See above.
*/
static boolean isXEntityHandle(handle handle)
{
if (handle == null || !handle._isValid() || handle.isUnknown())
{
return false;
}
WrappedResource resource = handle.getResource();
return resource instanceof XEntityImpl;
}
/**
* Check the type of resource in handle. Return <code>true</code> if and only if handle
* contains resource of type XNodeRef.
*
* @param handle
* The handle class instance to check type.
*
* @return See above.
*/
static boolean isXNodeHandleInitialized(handle handle)
{
if (!isXNodeHandle(handle))
{
return false;
}
WrappedResource resource = handle.getResource();
return ((XEntityImpl) resource).isInitialized();
}
/**
* Getting the Element backend object for this XEntity.
*
* @return The Element associated with object.
*/
public Node getNode()
{
return xnode;
}
/**
* Delete the resource.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
xnode = null;
isValid = false;
return true;
}
/**
* Gets the the subtype of the object.
*
* @return The character value of the subtype attribute.
*/
@Override
public character getSubType()
{
if (!isInitialized())
{
invalidNodeError("SUBTYPE");
return new character();
}
Node node = getNode();
return new character(XmlFactory.convertNodeToAblNode(node.getNodeType()));
}
/**
* Sets the the subtype of the object.
*
* @param value
* The new value of the subtype object attribute.
*/
@Override
public void setSubType(character value)
{
readOnlyError("SUBTYPE", handle.UNKNOWN_ARGUMENT);
}
/**
* Sets the the subtype of the object.
*
* @param value
* The new value of the subtype object attribute.
*/
@Override
public void setSubType(String value)
{
setSubType(new character(value));
}
/**
* Inserts a node as a child of this document before another node or last if other node is
* unknown.
*
* @param handle1
* The handle representing the child to insert.
* @param handle2
* The handle representing the XML node to be inserted before.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical insertBefore(handle handle1, handle handle2)
{
if (!isInitialized())
{
invalidNodeError("INSERT-BEFORE");
return new logical(false);
}
if (!isXNodeHandle(handle1) || !handle2.isUnknown() && !isXNodeHandle(handle2))
{
invalidArgumentError("INSERT-BEFORE");
return new logical(false);
}
if (handle1.isUnknown() || !handle2.isUnknown() && !isXNodeHandleInitialized(handle2))
{
invalidNodeError("INSERT-BEFORE");
return new logical(false);
}
if (handle2.isUnknown())
{
return appendChild(handle1);
}
Node node1 = ((XEntityImpl) handle1.get()).getNode();
Node node2 = ((XEntityImpl) handle2.get()).getNode();
try
{
getNode().insertBefore(node1, node2);
}
catch (DOMException e)
{
if (e.code == DOMException.WRONG_DOCUMENT_ERR)
{
commonDomError("INSERT-BEFORE",
"A node was used in a different document than the one the created it");
}
else if (e.code == DOMException.HIERARCHY_REQUEST_ERR)
{
commonDomError("INSERT-BEFORE",
"An attempt was made to reference a node in a context where it does not exist");
}
else if (e.code == DOMException.NOT_FOUND_ERR)
{
commonDomError("INSERT-BEFORE", "A node was inserted where it doesn't belong");
}
return new logical(false);
}
return new logical(true);
}
/**
* Gets the the number of children objects for the current one.
*
* @return The integer number of the children.
*/
@Override
public integer getNumChildren()
{
if (!isInitialized())
{
invalidNodeError("NUM-CHILDREN");
return integer.UNKNOWN;
}
return integer.of(getNode().getChildNodes().getLength());
}
/**
* Removing the specified child from the node.
*
* @param newChildHandle
* The handle class instance that represents the node to insert in the tree.
* @param oldChildHandle
* The handle class instance that represents the node to remove from the tree.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical replaceChild(handle newChildHandle, handle oldChildHandle)
{
if (!isInitialized())
{
invalidNodeError("REPLACE-CHILD");
return new logical(false);
}
if (!newChildHandle._isValid())
{
invalidArgumentError("REPLACE-CHILD");
return new logical(false);
}
if (newChildHandle.isUnknown())
{
invalidNodeError("REPLACE-CHILD");
return new logical(false);
}
if (!oldChildHandle._isValid())
{
invalidArgumentError("REPLACE-CHILD");
return new logical(false);
}
if (oldChildHandle.isUnknown())
{
invalidNodeError("REPLACE-CHILD");
return new logical(false);
}
Node newChild = ((XEntityImpl) newChildHandle.get()).getNode();
Node oldChild = ((XEntityImpl) oldChildHandle.get()).getNode();
try
{
getNode().replaceChild(newChild, oldChild);
}
catch (DOMException e)
{
if (e.code == DOMException.WRONG_DOCUMENT_ERR)
{
commonDomError("REPLACE-CHILD",
"A node was used in a different document than the one " +
"the created it");
}
else if (e.code == DOMException.HIERARCHY_REQUEST_ERR)
{
commonDomError("REPLACE-CHILD", "A node was inserted where doesn't belong");
}
else if (e.code == DOMException.NOT_FOUND_ERR)
{
commonDomError("REPLACE-CHILD",
"An attempt was made to reference a node in a context where " +
"it does not exist");
}
return new logical(false);
}
return new logical(true);
}
/**
* Appending the new child to the current object.
*
* @param hChild
* The valid handle class instance of the child to be appended.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical appendChild(handle hChild)
{
if (!isInitialized() || !valid())
{
return new logical(false);
}
if (!isXNodeHandle(hChild))
{
invalidArgumentError("APPEND-CHILD");
return new logical(false);
}
if (!isXNodeHandleInitialized(hChild))
{
invalidNodeError("APPEND-CHILD");
return new logical(false);
}
Node node = getNode();
Node childNode = ((XEntityImpl) hChild.get()).getNode();
try
{
node.appendChild(childNode);
}
catch (DOMException e)
{
if (e.code == DOMException.WRONG_DOCUMENT_ERR)
{
commonDomError("APPEND-CHILD",
"A node was used in a different document than the one the " +
"created it");
}
else if (e.code == DOMException.HIERARCHY_REQUEST_ERR)
{
commonDomError("APPEND-CHILD", "A node was inserted where it doesn't belong");
}
else if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("APPEND-CHILD",
"An attempt was made to modify an object where modifications " +
"are not allowed");
}
return new logical(false);
}
return new logical(true);
}
/**
* Getting the handle object of the child node with specified index.
*
* @param hChild
* The valid handle class instance of the child to put
* the results.
* @param index
* 1-based index of the child node to be retrieved.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical getChild(handle hChild, int index)
{
return getChild(hChild, new int64(index));
}
/**
* Getting the handle object of the child node with specified index.
*
* @param hChild
* The valid handle class instance of the child to put the results.
* @param index
* 1-based index of the child node to be retrieved.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical getChild(handle hChild, int64 index)
{
if (!isInitialized() || !isXNodeHandle(hChild))
{
invalidNodeError("GET-CHILD");
return logical.FALSE;
}
if (!hChild._isValid() || index.isUnknown())
{
invalidArgumentError("GET-CHILD");
return logical.FALSE;
}
int ndx = index.intValue();
NodeList childNodes = getNode().getChildNodes();
if (ndx < 1 || ndx > childNodes.getLength())
{
commonDomError("GET-CHILD", "Invalid child index", false);
return logical.FALSE;
}
Node node = childNodes.item(ndx - 1);
setNodeToHandle(hChild, node);
return logical.TRUE;
}
/**
* Removing the specified child from the node.
*
* @param hChild
* The valid handle class instance of the child to be removed.
*
* @return The <code>true</code> in case of success
* <code>false</code> otherwise.
*/
@Override
public logical removeChild(handle hChild)
{
if (!isInitialized())
{
invalidNodeError("REMOVE-CHILD");
return logical.FALSE;
}
if (!hChild._isValid())
{
invalidArgumentError("REMOVE-CHILD");
return logical.FALSE;
}
Node elChild = ((XEntityImpl) hChild.get()).getNode();
try
{
getNode().removeChild(elChild);
}
catch (DOMException e)
{
if (e.code == DOMException.NOT_FOUND_ERR)
{
commonDomError("REMOVE-CHILD",
"An attempt was made to reference a node in a context where it " +
"does not exist");
}
else if (e.code == DOMException.NO_MODIFICATION_ALLOWED_ERR)
{
commonDomError("REMOVE-CHILD",
"An attempt was made to modify an object where modifications " +
"are not allowed");
}
return logical.FALSE;
}
return logical.TRUE;
}
/**
* Gets the the unique ID number associated to this object by the underlying system. Not the
* same as the handle value itself. For X-noderef it's only unique for current xml Document.
*
* @return The integer unique number of node inside document.
*/
@Override
public integer getUniqueID()
{
if (uniqueID == null)
{
// Although the reference says "The UNIQUE-ID attribute for an x-noderef handle is only
// unique within a given XML document.", the implementation extends the uniqueness up
// to the current user context.
this.uniqueID = UniqueIdGenerator.getUniqueId(idKind);
}
return new integer(this.uniqueID);
}
/**
* Returns the value of the the NAMESPACE-URI attribute.
*
* @return See above.
*/
@Override
public character namespaceURI()
{
if (!isInitialized())
{
invalidNodeError("NAMESPACE-URI");
return new character();
}
String namespaceUri = getNode().getNamespaceURI();
if (namespaceUri != null)
{
return new character(namespaceUri);
}
else
{
return new character("");
}
}
/**
* Setter for NAMESPACE-URI attribute. Calls {@link #readOnlyError}, as this attribute is r/o
* for this resource.
*
* @param uri
* Not used.
*/
@Override
public void namespaceURI(String uri)
{
readOnlyError("NAMESPACE-URI", handle.UNKNOWN_ARGUMENT);
}
/**
* Setter for NAMESPACE-URI attribute. Calls {@link #readOnlyError}, as this attribute is r/o
* for this resource.
*
* @param uri
* Not used.
*/
@Override
public void namespaceURI(character uri)
{
if (uri.isUnknown())
{
invalidArgumentAssignError("NAMESPACE-URI");
return;
}
readOnlyError("NAMESPACE-URI", handle.UNKNOWN_ARGUMENT);
}
/**
* Removes the specified attribute.
*
* @param name
* The name of the attribute to remove.
* @param uri
* The URI of the attribute to remove.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical removeAttribute(character name, character uri)
{
if (name.isUnknown())
{
invalidArgumentError("REMOVE-ATTRIBUTE");
return logical.FALSE;
}
if (!isInitialized())
{
invalidNodeError("REMOVE-ATTRIBUTE");
return logical.FALSE;
}
Node node = getNode();
if (node.getNodeType() != Node.ELEMENT_NODE)
{
commonDomError("REMOVE-ATTRIBUTE", "Invalid node type");
return logical.FALSE;
}
Element elementNode = (Element) node;
String nameStr = name.toStringMessage();
if (uri.isUnknown())
{
elementNode.removeAttribute(nameStr);
}
else
{
elementNode.removeAttributeNS(uri.toStringMessage(), nameStr);
}
return logical.TRUE;
}
/**
* Removes the specified attribute. This method is not applicable for Dom.
*
* @param index
* The 1-based index of the attribute to remove.
* @param uri
* The URI of the attribute to remove.
*
* @return The <code>true</code> in case of success <code>false</code> otherwise.
*/
@Override
public logical removeAttribute(NumberType index, character uri)
{
return new logical(false); //only for SAX
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(String prefix)
{
namespacePrefix(new character(prefix));
}
/**
* Setter for NAMESPACE-PREFIX attribute.
*
* @param prefix
* The new value of the NAMESPACE-PREFIX attribute.
*/
@Override
public void namespacePrefix(character prefix)
{
readOnlyError("NAMESPACE-PREFIX", handle.UNKNOWN_ARGUMENT);
}
/**
* Returns the value of the the NAMESPACE-PREFIX attribute. Only for ELEMENT or ATTRIBUTE,
* for other attributes return unknown value.
*
* @return See above.
*/
@Override
public character namespacePrefix()
{
if (!isInitialized() || LegacyResource.X_DOCUMENT.equalsIgnoreCase(type()))
{
invalidNodeError("NAMESPACE-PREFIX");
return new character();
}
Node currentNode = getNode();
final String prefix;
if (currentNode.getNodeType() == Node.ELEMENT_NODE)
{
Element elementNode = (Element) currentNode;
prefix = elementNode.getPrefix();
}
else if (currentNode.getNodeType() == Node.ATTRIBUTE_NODE)
{
Attr attributeNode = (Attr) currentNode;
prefix = attributeNode.getPrefix();
}
else
{
prefix = null;
}
return new character(prefix == null ? "" : prefix);
}
/**
* Get the <code>name</code> attribute of handle.
*
* @return See above.
*/
@Override
public character name()
{
if (!isInitialized())
{
invalidNodeError("NAME");
return new character();
}
Node node = getNode();
return new character(node.getNodeName());
}
/**
* Get the <code>name</code> attribute of handle as a string.
*
* @return See above.
*/
@Override
public String _name()
{
return name().toJavaType();
}
/**
* Setting the Element backend object for this XEntity.
*
* @param xnode
* The node value to set.
*/
public void setNode(Node xnode)
{
this.xnode = xnode;
}
/**
* Process and display invalid node error. It displays appropriate error message.
*
* @param attribute
* The read-only attribute.
*/
protected void invalidNodeError(String attribute)
{
if (attribute == null)
{
throw new IllegalArgumentException("The attribute is null !");
}
String msg = "X-NODEREF must be associated with a valid X-DOCUMENT in order " +
"to use it in method %s";
String err = String.format(msg, attribute.toUpperCase());
ErrorManager.recordOrShowError(9102, err, false, false, false);
}
/**
* Process and display unknown node error. It displays appropriate error message.
*
* @param attribute
* The read-only attribute.
* @param message
* Error message to display.
*/
protected void commonDomError(String attribute, String message)
{
commonDomError(attribute, message, true);
}
/**
* Process and display unknown node error. It displays appropriate error message.
*
* @param attribute
* The read-only attribute.
* @param message
* Error message to display.
* @param raiseError
* Flag indicating if an ERROR condition must be raised.
*/
protected void commonDomError(String attribute, String message, boolean raiseError)
{
if (attribute == null)
{
throw new IllegalArgumentException("The attribute is null !");
}
String msg = "X-NODEREF or X-DOCUMENT %s got an error: %s";
String err = String.format(msg, attribute.toUpperCase(), message);
if (raiseError)
{
ErrorManager.recordOrThrowError(9082, err, false, false);
}
else
{
ErrorManager.recordOrShowError(9802, err, true, false, false);
}
}
/**
* Process and display unsupported xml sub-type error. It displays appropriate error message.
*
* @param type
* The read-only attribute.
*/
protected void unsupportedDomError(String type)
{
String msg = "Unsupported XML SUB-TYPE %s";
String err = String.format(msg, type);
ErrorManager.recordOrShowError(9171, err, false, true, false);
}
/**
* Reports if this object is not initialized. Initialized means that object is created but
* doesn't have any xml node. If the owner of node has been already deleted then node becomes
* uninitialized.
*
* @return <code>true</code> if object is initialized.
*/
boolean isInitialized()
{
Node node = getNode();
if (node == null)
{
return false;
}
if (node.getOwnerDocument() == null)
{
return true;
}
Object data = node.getOwnerDocument().getUserData(IS_INVALID_NODE);
if (data != null && Boolean.valueOf(data.toString()))
{
xnode = null;
return false;
}
return true;
}
}