SOAPHeaderEntryImpl.java

/*
** Module   : SOAPHeaderEntryImpl.java
** Abstract : Implementation for the SOAP-HEADER-ENTRY resource.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA  20130216 Created initial version.
** 002 CA  20130221 Added ADM-DATA, LOCAL-NAME, UNIQUE-ID and NAMESPACE-URI support. Fixed resource type.
** 003 OM  20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
** 004 CA  20130603 resourceDelete must return a boolean value.
** 005 CA  20130927 Removed setResourceType, as the type is determined from LegacyResource annotation.
** 006 HC  20131215 Implemented ADM-DATA and UNIQUE-ID attribute.
** 007 CA  20140107 Added runtime support.
** 008 OM  20201120 Changed method names to fit the buffer no-bean prefix convention.
**     OM  20210309 Removed deprecation warnings.
**     CA  20221006 UNIQUE-ID is kept as Java type instead of BDT.  Refs #6827
**     CA  20220605 Fixed 'getLocalName' to return the node name if it is not namespace-aware.
**     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 009 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 010 CA  20240918 Removed default methods from interfaces and moved them as concrete implementations, as
**                  default methods cause the Java metaspace to spike one order of magnitude.
*/

/*
** 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.util;

import java.io.*;
import java.util.logging.*;
import javax.xml.parsers.*;

import com.goldencode.p2j.util.logging.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import com.goldencode.p2j.xml.*;
import com.goldencode.util.*;
import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;

/**
 * A class for implementing the SOAP-HEADER-ENTRYREF methods and attributes.
 */
public class SOAPHeaderEntryImpl
extends HandleChain
implements SOAPHeaderEntry,
           SOAPEntity
{
   /** Key identifying the ACTOR of this SOAP Header Entry, set at the DOM Node. */
   static final String ACTOR = "ACTOR";
   
   /**
    * Key identifying the MUST-UNDERSTAND attribute of this SOAP Header Entry, set at the DOM 
    * Node.
    */
   static final String MUST_UNDERSTAND = "MUST-UNDERSTAND";

   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(SOAPHeaderEntryImpl.class.getName());
   
   /** Corresponds to ADM-DATA attribute. */
   private String admData = null;
   
   /** Corresponds to UNIQUE-ID attribute. */
   private Long uniqueID = null;

   /** Flag indicating if this resource was deleted or not. */
   private boolean deleted = false;
   
   /**
    * After it is attached to a SOAP Header parent, this will be the key to the DOM Node for this
    * header entry.
    */
   private Object headerKey = null;
   
   /** The parent SOAP Header. */
   private SOAPHeaderImpl parent = null;
   
   /**
    * Implicit c'tor.
    */
   public SOAPHeaderEntryImpl()
   {
      super(true, false);
   }
   
   /**
    * Gets the X-NODEREF object that refers to the XML underlying a SOAP-HEADER-ENTRYREF object or
    * SOAP-FAULT entry and sets it to the given handle..
    * <p>
    * This method returns a logical even though the Progress 4GL documentation explicitly states 
    * otherwise. Actual working code has proven that the original documentation is incorrect. 
    * This implementation matches the actual runtime behavior of the 4GL.
    * 
    * @param    xNodeRef
    *           The handle where the resource will be set.
    *           
    * @return   <code>true</code> if the operation was possible.
    */
   public logical getNode(handle xNodeRef)
   {
      if (xNodeRef._isValid() && (xNodeRef.getResource() instanceof XNodeRefImpl))
      {
         Node node = getHeaderNode();
         
         if (node == null)
         {
            // if not attached, return false;
            return new logical(false);
         }
         
         XNodeRefImpl nodeRef = (XNodeRefImpl) xNodeRef.getResource();
         nodeRef.setNode(node);
         
         return new logical(true);
      }
      
      SOAPFactory.throwError("GET-NODE", "X-NODEREF");

      return new logical(false);
   }

   /**
    * Returns a LONGCHAR that contains the serialized form of the XML underlying the 
    * SOAP-HEADER-ENTRYREF or SOAP-FAULT entry.
    * 
    * @return   See above.
    */
   public longchar getSerialized()
   {
      longchar lc = new longchar();
      Node node = getHeaderNode();
      
      if (node == null)
      {
         // return unknown if node is not set
         return lc;
      }
      
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      try
      {
         XmlHelper.write(node.getOwnerDocument(), output, false, true);
      }
      catch (IOException e)
      {
         throw new RuntimeException("Could not serialize SOAP Header Entry!", e);
      }

      lc.assign(output.toString());
      
      return lc;
   }

   /**
    * Returns the value of the actor attribute for the SOAP-HEADER-ENTRYREF object as a URL. 
    * Identifies the recipient of a header element.
    * 
    * @return   See above.
    */
   public character getActor()
   {
      Node node = getHeaderNode();

      if (node == null)
      {
         return new character("");
      }
      
      return (node == null || node.getUserData(ACTOR) == null)
                ? new character("") 
                : new character((character) node.getUserData(ACTOR)); 
   }

   /**
    * Indicates whether a SOAP-HEADER-ENTRYREF object is mandatory (TRUE) or optional (FALSE) for
    * the recipient to process.
    * 
    * @return   See above.
    */
   public logical isMustUnderstand()
   {
      Node node = getHeaderNode();
      
      if (node == null)
      {
         return new logical(false);
      }

      return new logical(node != null && Boolean.TRUE.equals(node.getUserData(MUST_UNDERSTAND)));
   }

   /**
    * Deletes the XML underlying a SOAP-HEADER-ENTRYREF object, without deleting the object.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical deleteHeaderEntry()
   {
      if (parent == null)
      {
         return new logical(false);
      }

      // remove the node from the parent and detach the headerKey from all references
      parent.removeHeaderEntry(this, headerKey);
      
      // detach references
      parent = null;
      
      return new logical(true);
   }

   /**
    * Sets the ACTOR attribute for this SOAP-HEADER-ENTRY.
    *
    * @param    uri
    *           The URI of the SOAP actor.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setActor(character uri)
   {
      Node node = getHeaderNode();

      if (!isPopulated("SET-ACTOR"))
      {
         return new logical(false);
      }
      
      node.setUserData(ACTOR, new character(uri), null);
      
      return new logical(true);
   }

   /**
    * Sets the ACTOR attribute for this SOAP-HEADER-ENTRY.
    * <p>
    * This attribute is kept as a user-data at the DOM Node, to be shared by all resources 
    * referencing this SOAP Header Entry.
    * <p>
    * The {@code mustUnderstand} and {@code actor} attributes are not emitted by 
    * {@link #getSerialized()}.
    *
    * @param    uri
    *           The URI of the SOAP actor.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setActor(String uri)
   {
      return setActor(new character(uri));
   }

   /**
    * Sets the MUST-UNDERSTAND attribute for this SOAP-HEADER-ENTRY.
    * <p>
    * This attribute is kept as a user-data at the DOM Node, to be shared by all resources 
    * referencing this SOAP Header Entry.
    * <p>
    * The {@code mustUnderstand} and {@code actor} attributes are not emitted by 
    * {@link #getSerialized()}.
    * 
    * @param    val
    *           The new value of this attribute.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setMustUnderstand(logical val)
   {
      Node node = getHeaderNode();

      if (!isPopulated("SET-MUST-UNDERSTAND") || node == null)
      {
         return new logical(false);
      }

      node.setUserData(MUST_UNDERSTAND, val.booleanValue(), null);

      return new logical(true);
   }

   /**
    * Sets the MUST-UNDERSTAND attribute for this SOAP-HEADER-ENTRY.
    *
    * @param    val
    *           The new value of this attribute.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setMustUnderstand(boolean val)
   {
      return setMustUnderstand(new logical(val));
   }

   /**
    * Replaces the underlying XML of a SOAP-HEADER-ENTRYREF object with the DOM XML sub-tree 
    * rooted by the X-NODEREF object.
    *
    * @param    xNoderef
    *           A X-NODEREF variable that refers to the XML that will be set for the 
    *           SOAP-HEADER-ENTRYREF object.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setNode(handle xNoderef)
   {
      if (xNoderef._isValid() && (xNoderef.getResource() instanceof XNodeRefImpl))
      {
         XNodeRefImpl nodeRef = (XNodeRefImpl) xNoderef.getResource();
         
         // check if there is a valid created DOM node
         Node domNode = nodeRef.getNode();
         
         if (domNode == null)
         {
            final String msg1 = "Invalid X-NODEREF handle passed to SET-NODE";
            final int code1 = 11577;

            final String msg2 = 
              "**The SET-NODE attribute on the SOAP-HEADER-ENTRYREF widget has invalid arguments";
            final int code2 = 4065;

            ErrorManager.recordOrShowError(new int[] {code1, code2}, new String[]{msg1, msg2},
                                           false, false, false);
            
            return new logical(false);
         }
         
         // check if it is attached to a SOAP Header
         if (parent == null)
         {
            final String msg = "SET-NODE called on an unattached SOAP-HEADER-ENTRYREF";
            ErrorManager.recordOrShowError(13877, msg, false, false, false);
            
            return new logical(false);
         }
         
         // create a deep clone of the target node
         parent.setHeaderNode(headerKey, domNode.cloneNode(true));
         
         return new logical(true);
      }

      // no error is shown
      
      return new logical(false);
   }

   /**
    * Sets the SOAP-header-entryref object's underlying XML from serialized XML.
    *
    * @param    payload
    *           A LONGCHAR variable that contains the XML that will become this 
    *           SOAP-HEADER-ENTRYREF object. This parameter can be only a longchar, 4GL doesn't
    *           allow character variables.
    * 
    * @return   <code>true</code> if the operation was possible.
    */
   public logical setSerialized(longchar payload)
   {
      // check if it is attached to a SOAP Header
      if (parent == null)
      {
         final String msg = "SET-SERIALIZED called on an unattached SOAP-HEADER-ENTRYREF";
         ErrorManager.recordOrShowError(13877, msg, false, false, false);
         
         return new logical(false);
      }

      String xml = payload.toStringMessage();

      try
      {
         Document doc = XmlHelper.parse(new ByteArrayInputStream(xml.getBytes()),
                                        false, null, true);
         
         parent.setHeaderNode(headerKey, doc.getDocumentElement());
         doc.setUserData(XNodeRefImpl.RESTRICTED_DOCUMENT, true, null);
         
         return new logical(true);
      }
      catch (ParserConfigurationException | IOException | SAXException e)
      {
         if (LOG.isLoggable(Level.FINE))
         {
            LOG.log(Level.FINE, "Problems parsing the XML payload: " + xml, e);
         }
         
         final String msg1 = "Error in SET-SERIALIZED:";
         final String msg2 =
            "**The SET-SERIALIZED attribute on the SOAP-HEADER-ENTRYREF widget has invalid " +
            "arguments. (4065)";
         
         ErrorManager.recordOrShowError(new int[] {0, 4065}, new String[] {msg1, msg2},
                                        false, false, false, true);
         
         return new logical(false);
      }
   }
   
   /**
    * Reports if this object is valid for use.  
    *
    * @return   <code>true</code> if we are valid (can be used).
    */
   public boolean valid()
   {
      return !deleted;
   }

   /**
    * Get the value of the LOCAL-NAME attribute.
    * 
    * @return   See above.
    */
   @Override
   public character getLocalName()
   {
      Node node = getHeaderNode();
      
      if (node == null)
      {
         return new character("");
      }

      String localName = node.getLocalName();
      String nodeName = node.getNodeName();
      int nodeType = node.getNodeType();
      if (localName != null)
      {
         return new character(localName);
      }
      else if (node.getNamespaceURI() == null && nodeName != null && 
               (nodeType == Node.ELEMENT_NODE || nodeType == Node.ATTRIBUTE_NODE))
      {
         return new character(nodeName);
      }
      {
         return new character("");
      }
   }

   /**
    * Get the value of the ADM-DATA attribute.
    * 
    * @return   See above.
    */
   @Override
   public character getADMData()
   {
      return new character(this.admData);
   }

   /**
    * Set the value of the ADM-DATA attribute.
    * 
    * @param    value
    *           The new value.
    */
   @Override
   public void setADMData(String value)
   {
      this.admData = value;
   }

   /**
    * Set the value of the ADM-DATA attribute.
    * 
    * @param    value
    *           The new value.
    */
   @Override
   public void setADMData(character value)
   {
      this.admData = value.getValue();
   }

   /**
    * Gets the the unique ID number associated to this object by the underlying system. Not the 
    * same as the handle value itself.
    * 
    * @return   The integer number of the children.
    */
   @Override
   public integer getUniqueID()
   {
      if (uniqueID == null)
      {
         uniqueID = UniqueIdGenerator.getUniqueId(IdKind.SOAP_HEADER);
      }
      return new integer(this.uniqueID);
   }

   /**
    * 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()
   {
      return new character();
   }
   
   /**
    * Setter for NAMESPACE-PREFIX attribute.
    *
    * @param    prefix
    *           The new value of the NAMESPACE-PREFIX attribute.
    */
   @Override
   public void namespacePrefix(String prefix)
   {
   }
   
   /**
    * Setter for NAMESPACE-PREFIX attribute.
    *
    * @param    prefix
    *           The new value of the NAMESPACE-PREFIX attribute.
    */
   @Override
   public void namespacePrefix(character prefix)
   {
   }

   /**
    * Returns the value of the the NAMESPACE-URI attribute.
    *
    * @return    See above.
    */
   @Override
   public character namespaceURI()
   {
      Node node = getHeaderNode();
      
      if (node == null)
      {
         return new character("");
      }
      
      String uri = node.getNamespaceURI();
      return new character(uri == null ? "" : uri);
   }

   /**
    * 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)
   {
      readOnlyError("NAMESPACE-URI", handle.UNKNOWN_ARGUMENT);
   }
   
   /**
    * Get the <code>name</code> attribute of handle.
    * 
    * @return   See above.
    */
   public character name()
   {
      return getLocalName();
   }
   
   /**
    * Get the <code>name</code> attribute of handle as a string.
    *
    * @return See above.
    */
   @Override
   public String _name()
   {
      return name().toJavaType();
   }
   
   /**
    * Delete the resource.
    * 
    * @return   <code>true</code> if the resource was deleted.
    */
   @Override
   protected boolean resourceDelete()
   {
      // the inner X-NODEREF does not get deleted. just mark the resource.
      
      this.deleted = true;
      
      return true;
   }
   
   /**
    * Check if this resource supports the NEXT-SIBLING attribute.
    * 
    * @return   Always <code>false</code>.
    */
   @Override
   protected boolean hasNextSibling()
   {
      return false;
   }
   
   /**
    * Check if this resource supports the PREV-SIBLING attribute.
    * 
    * @return   Always <code>false</code>.
    */
   @Override
   protected boolean hasPrevSibling()
   {
      return false;
   }
   
   /**
    * Check if this resource supports the NAME attribute in read-only mode.
    * 
    * @return   Always <code>true</code>.
    */
   @Override
   protected boolean hasNameReadOnly()
   {
      return true;
   }

   /**
    * Set the parent SOAP HEADER for this entry.
    * 
    * @param    parent
    *           The parent SOAP Header.
    * @param    headerKey
    *           The key to the DOM XML Node referenced by this header entry.
    */
   void setParent(SOAPHeaderImpl parent, Object headerKey)
   {
      this.parent = parent;
      this.headerKey = headerKey;
   }
   
   /**
    * Get the {@link #headerKey} for this SOAP header entry.
    * 
    * @return   See above.
    */
   Object getHeaderKey()
   {
      return headerKey;
   }
   
   /**
    * Check if this SOAP-HEADER-ENTRY is populated. If not, show an appropriate error.
    * 
    * @param    caller
    *           The name of the caller of this method (will be shown in the error).
    *           
    * @return   See above.
    */
   private boolean isPopulated(String caller)
   {
      Node node = getHeaderNode();
      
      if (node == null)
      {
         final String msg = "%s called on an unpopulated SOAP-HEADER-ENTRYREF";
         
         ErrorManager.recordOrShowError(11571, String.format(msg, caller), false, false, false);
      }
      
      return node != null;
   }
   
   /**
    * Get the {@link Node DOM XML node} associated with this SOAP Header entry.
    * 
    * @return   See above.
    */
   private Node getHeaderNode()
   {
      return parent == null ? null : parent.getHeaderNode(headerKey);
   }
}