SOAPHeaderImpl.java
/*
** Module : SOAPHeaderImpl.java
** Abstract : Implementation for the SOAP-HEADER resource.
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 CA 20130216 Created initial version.
** 002 CA 20130221 Added ADM-DATA, UNIQUE-ID 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 VVT 20220306 Uniform toString() support added.
** 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.
*/
/*
** 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.*;
import javax.xml.parsers.*;
import javax.xml.soap.*;
import org.w3c.dom.*;
import org.w3c.dom.Node;
import com.goldencode.util.*;
import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;
/**
* A class for implementing the SOAP-HEADER methods and attributes.
*/
public class SOAPHeaderImpl
extends HandleChain
implements SOAPHeader,
CommonHandleChain
{
/** Corresponds to ADM-DATA attribute. */
private String admData = null;
/** Corresponds to UNIQUE-ID attribute. */
private Long uniqueID = null;
/** All SOAP Header Entries linked with this SOAP Header. */
private final Set<SOAPHeaderEntryImpl> headers = new HashSet<>();
/**
* Mapping of each key of a header entry to a DOM XML Node. This is needed because, when a
* SOAP Header Entry resource changes its inner DOM XML node, all linked SOAP Header Entries
* change it too.
*/
private final Map<Object, Node> entries = new HashMap<>();
/** List allowing fast indexed access to the header entry keys. */
private final List<Object> entryKeys = new ArrayList<>();
/** Flag indicating if this resource was deleted or not. */
private boolean deleted = false;
/**
* Implicit c'tor.
*/
public SOAPHeaderImpl()
{
super(true, false);
}
/**
* Returns the number of SOAP-HEADER-ENTRYREF object entries attached to the SOAP-HEADER
* object.
*
* @return See above.
*/
@Override
public integer getNumHeaderEntries()
{
return new integer(entries.size());
}
/**
* Creates a new entry in a SOAP-header object's list of entries and associates the new entry
* with the given SOAP-HEADER-ENTRY-REF object.
*
* @param entryRef
* The SOAP Header Entry.
* @return <code>true</code> if the operation was possible.
*/
@Override
public logical addHeaderEntry(handle entryRef)
{
if (entryRef._isValid() && (entryRef.getResource() instanceof SOAPHeaderEntryImpl))
{
SOAPHeaderEntryImpl entry = (SOAPHeaderEntryImpl) entryRef.getResource();
addHeaderEntry(entry);
return new logical(true);
}
SOAPFactory.throwError("ADD-HEADER-ENTRY", "SOAP-HEADER-ENTRY");
return new logical(false);
}
/**
* Get the header entry at the given index and assign 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 target
* The handle where the SOAP-HEADER-ENTRY instance will be saved.
* @param idx
* The index of the SOAP-HEADER-ENTRY instance to retrieve. 4GL allows only integer
* and int64 values to be passed to this parameter.
*
* @return <code>true</code> if the operation was possible.
*/
@Override
public logical getHeaderEntry(handle target, long idx)
{
return getHeaderEntry(target, new int64(idx));
}
/**
* Get the header entry at the given index and assign 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 target
* The handle where the SOAP-HEADER-ENTRY instance will be saved.
* @param idx
* The index of the SOAP-HEADER-ENTRY instance to retrieve. 4GL allows only integer
* and int64 values to be passed to this parameter.
*
* @return <code>true</code> if the operation was possible.
*/
@Override
public logical getHeaderEntry(handle target, integer idx)
{
return getHeaderEntry(target, new int64(idx));
}
/**
* Get the header entry at the given index and assign 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 target
* The handle where the SOAP-HEADER-ENTRY instance will be saved.
* @param idx
* The index of the SOAP-HEADER-ENTRY instance to retrieve. 4GL allows only integer
* and int64 values to be passed to this parameter.
*
* @return <code>true</code> if the operation was possible.
*/
@Override
public logical getHeaderEntry(handle target, int64 idx)
{
if (target._isValid() && (target.getResource() instanceof SOAPHeaderEntryImpl))
{
if (idx.isUnknown() || idx.intValue() < 1 || idx.intValue() > entries.size())
{
// no error is shown or registered, and true is returned.
return new logical(true);
}
// locate the key
Object key = entryKeys.get(idx.intValue() - 1);
// this returns really a new resource... it has distinct ADM-DATA/UNIQUE-ID/PRIVATE-DATA,
// but they share the inner X-NODEREF resource.
SOAPHeaderEntryImpl targetEntry = (SOAPHeaderEntryImpl) target.getResource();
targetEntry.setParent(this, key);
// register this header, so it can be detached if the soap-header gets deleted
this.headers.add(targetEntry);
return new logical(true);
}
SOAPFactory.throwError("GET-HEADER-ENTRY", "SOAP-HEADER-ENTRY");
return new logical(false);
}
/**
* Reports if this object is valid for use.
*
* @return <code>true</code> if we are valid (can be used).
*/
@Override
public boolean valid()
{
return !deleted;
}
/**
* 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);
}
/**
* Delete the resource.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
// do not delete the soap header entries, but unlink them.
for (SOAPHeaderEntryImpl header : headers)
{
header.setParent(null, null);
}
// clear all references
entries.clear();
entryKeys.clear();
headers.clear();
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;
}
/**
* Removes the specified SOAP-Header object from the list of header entries.
*
* @param entry
* The SOAP Header Entry.
* @param key
* The key to the DOM XML Node referenced by this header entry.
*/
void removeHeaderEntry(SOAPHeaderEntryImpl entry, Object key)
{
// cleanup after this key
entries.remove(key);
entryKeys.remove(key);
headers.remove(entry);
Iterator<SOAPHeaderEntryImpl> itr = headers.iterator();
while (itr.hasNext())
{
SOAPHeaderEntryImpl header = itr.next();
if (header.getHeaderKey() == key)
{
// detach and remove them
header.setParent(null, null);
itr.remove();
}
}
}
/**
* Creates a new entry in a SOAP-header object's list of entries and associates the new entry
* with the given SOAP-HEADER-ENTRY-REF object.
*
* @param entry
* The SOAP Header Entry.
*/
void addHeaderEntry(SOAPHeaderEntryImpl entry)
{
Object key = new Object();
entry.setParent(this, key);
entries.put(key, null);
entryKeys.add(key);
headers.add(entry);
}
/**
* Get the string representation of the XML payload associated with this soap header.
* <p>
* If there are no header entries, return {@code null}.
*
* @return See above.
*/
@Override
public String asString()
{
try
{
Document doc = XmlHelper.newDocument();
// create a HEADER element node, linked with the SOAP 1.1. namespace; to this, each
// header entry DOM XML node will be added.
Element root = doc.createElementNS(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "header");
doc.appendChild(root);
boolean hadHeaders = false;
for (Object key : entryKeys)
{
Node entry = entries.get(key);
if (entry == null)
{
// TODO: what if a key has no DOM XML node? this is possible, because the DOM XML
// node is not created until SOAP-HEADER-ENTRYREF:SET-NODE or SET-SERIALIZED is
// called
continue;
}
Element clone = (Element) doc.importNode(entry, true);
String actor = (String) entry.getUserData(SOAPHeaderEntryImpl.ACTOR);
if (actor != null)
{
clone.setAttributeNS(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "actor", actor);
}
Boolean mustUnderstand =
(Boolean) entry.getUserData(SOAPHeaderEntryImpl.MUST_UNDERSTAND);
if (mustUnderstand != null && mustUnderstand)
{
clone.setAttributeNS(SOAPConstants.URI_NS_SOAP_1_1_ENVELOPE, "mustUnderstand", "1");
}
root.appendChild(clone);
hadHeaders = true;
}
String headers = null;
if (hadHeaders)
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
XmlHelper.write(doc, output, false, true);
headers = output.toString().trim();
}
return headers;
}
catch (ParserConfigurationException | IOException e)
{
throw new RuntimeException("Problems serializing SOAP Headers:", e);
}
}
/**
* Get the DOM XML node associated with the given key.
*
* @param key
* The key to an entry in the {@link #entries} map.
*
* @return The DOM XML node for the given key.
*
* @throws IllegalArgumentException
* If the received key is {@code null} or is not part of the {@link #entries} map's
* keys.
*/
Node getHeaderNode(Object key)
{
if (key != null && entries.containsKey(key))
{
return entries.get(key);
}
else
{
// this exception should never be thrown; if this code is reached, there is a problem in
// the SOAP Header resource implementation.
// was added as a means to mark SOAP Header related problems, to ease debugging.
throw new IllegalArgumentException("Received an unknown SOAP Header Entry key!");
}
}
/**
* Associated a new DOM XML node with the given header entry key.
*
* @param key
* The key to an entry in the {@link #entries} map.
* @param node
* The new DOM XML node for the given key.
*
* @throws IllegalArgumentException
* If the received key is {@code null} or is not part of the {@link #entries} map's
* keys.
*/
void setHeaderNode(Object key, Node node)
{
if (key != null && entries.containsKey(key))
{
this.entries.put(key, node);
}
else
{
// this exception should never be thrown; if this code is reached, there is a problem in
// the SOAP Header resource implementation.
// was added as a means to mark SOAP Header related problems, to ease debugging.
throw new IllegalArgumentException("Received an unknown SOAP Header Entry key!");
}
}
}