WsdlConfig.java
/*
** Module : WsdlConfig.java
** Abstract : Helper to manage a WSDL document.
**
** Copyright (c) 2020-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20200518 First version.
** CA 20200528 Fixes for extent parameters, BEFORE-TABLE and error management.
** 002 CA 20211012 Fixed generated WSDL and reworked SOAP support to rely on namespace, when resolving an
** operation.
** CA 20211013 DATASET parameters have the table table indexes, too, at the WSDL schema.
** CA 20211015 Avoid a NPE for TABLE/DATASET-HANDLE params until the runtime is in place.
** CA 20211112 Refactored to allow transfer of the DATASET or TABLE via XML. Added support for
** DATASET-HANDLE and TABLE-HANDLE.
** 003 SBI 20230216 Fixed BaseSchemaType.buildParameter(..) because defaultValue() was added to LegacyServiceParameter.
** 004 TJD 20240214 Removed dependency on castor
** 005 RNC 20241030 Fixed a wrong variable used in the inner loop from readIndexes().
*/
/*
** 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.soap;
import java.lang.annotation.*;
import java.util.*;
import javax.wsdl.*;
import javax.wsdl.extensions.*;
import javax.wsdl.extensions.schema.*;
import javax.xml.namespace.*;
//import org.exolab.castor.xml.*;
import org.w3c.dom.*;
import com.goldencode.p2j.persist.annotation.*;
import com.goldencode.p2j.util.*;
/**
* Provide helper APIs to access the SOAP operations and others, from a WSDL definition.
*/
class WsdlConfig
{
/** The WSDL definition. */
private final Definition definition;
/** All the defined schemas in the WSDL document. */
private final Map<QName, WSDLSchema> schemas = new HashMap<>();
/**
* Create new instance. This will load all the schemas from the WSDL document into {@link #schemas}.
*
* @param definition
* The WSDL definition.
*/
public WsdlConfig(Definition definition)
{
this.definition = definition;
// create a mapping of all complex types
List<ExtensibilityElement> lext = definition.getTypes().getExtensibilityElements();
Iterator<ExtensibilityElement> iter = lext.iterator();
while (iter.hasNext())
{
ExtensibilityElement exEl = iter.next();
if (exEl instanceof Schema)
{
Schema schema = (Schema) exEl;
WSDLSchema wsdlSchema = new WSDLSchema(schema);
schemas.put(wsdlSchema.qname, wsdlSchema);
}
}
}
/**
* Remove the namespace from the specified name.
*
* @param name
* The qualified name.
*
* @return The local part in this qname.
*/
private static String removeNamespace(String name)
{
if (name.indexOf(":") >= 0)
{
name = name.substring(name.lastIndexOf(":") + 1);
}
return name;
}
/**
* Get the WSDL definition.
*
* @return The {@link #definition}.
*/
public Definition getDefinition()
{
return definition;
}
/**
* Resolve the input and output parameters for the specified operation.
*
* @param op
* The SOAP operation.
*
* @return The parameter mappings.
*/
public Map<String, BaseSchemaType> resolveParameters(Operation op)
{
Map<String, BaseSchemaType> res = new HashMap<>();
Input input = op.getInput();
Output output = op.getOutput();
if (input != null && input.getMessage() != null)
{
res.putAll(resolveParameters(input.getMessage()));
}
if (output != null && output.getMessage() != null)
{
res.putAll(resolveParameters(output.getMessage()));
}
return res;
}
/**
* From the given legacy signature, resolve the service parameters.
*
* @param ls
* The legacy signature associated with the 4GL target (internal entry, program, class, method).
* @param op
* The WSDL operation.
*
* @return The list of service parameters.
*/
public LegacyServiceParameter[] resolveParameters(LegacySignature ls, Operation op)
{
Map<String, Integer> parOrdinals = new HashMap<>();
Map<String, String> parTypes = new HashMap<>();
Map<String, String> parModes = new HashMap<>();
int ordinal = 1;
for (LegacyParameter lsp : ls.parameters())
{
String key = lsp.name();
if (key.isEmpty())
{
if (lsp.type().equalsIgnoreCase("TABLE"))
{
key = lsp.table();
}
else if (lsp.type().equalsIgnoreCase("DATASET"))
{
key = lsp.dataset();
}
}
key = key.toLowerCase();
parOrdinals.put(key, ordinal);
parTypes.put(key, lsp.type());
parModes.put(key, lsp.mode()); // string representation, INPUT/OUTPUT/INPUT-OUTPUT
ordinal = ordinal + 1;
}
Input input = op.getInput();
Output output = op.getOutput();
List<LegacyServiceParameter> params = new ArrayList<>();
if (input != null && input.getMessage() != null)
{
params.addAll(resolveParameters(input.getMessage(), true, parOrdinals, parTypes, parModes));
}
if (output != null && output.getMessage() != null)
{
params.addAll(resolveParameters(output.getMessage(), false, parOrdinals, parTypes, parModes));
}
Collections.sort(params, new Comparator<LegacyServiceParameter>()
{
@Override
public int compare(LegacyServiceParameter lsp1, LegacyServiceParameter lsp2)
{
return lsp1.ordinal() - lsp2.ordinal();
}
});
return params.toArray(new LegacyServiceParameter[params.size()]);
}
/**
* Resolve the WSDL parameters for the specified WSDL message.
*
* @param msg
* The message (input or output, for an operation). Must have a <code>parameters</code> attribute.
*
* @return The parameter mapping for this message.
*/
public Map<String, BaseSchemaType> resolveParameters(Message msg)
{
WSDLSchema schema = resolveSchema(msg, "parameters");
Part part = msg.getPart("parameters");
String typeName = part.getElementName().getLocalPart();
ComplexSchemaType type = schema.types.get(typeName);
if (type == null)
{
return Collections.emptyMap();
}
Map<String, BaseSchemaType> res = new HashMap<>();
for (BaseSchemaType bst : type.params)
{
res.put(bst.name, bst);
}
return res;
}
/**
* Resolve the schema for the specified message.
*
* @param msg
* The message (input or output, for an operation). Must have a <code>parameters</code> attribute.
* @param partName
* The part defining the parameters.
*
* @return The schema configuration for this message's parameters.
*/
public WSDLSchema resolveSchema(Message msg, String partName)
{
Part part = msg.getPart(partName);
String schemaNS = part.getElementName().getNamespaceURI();
String schemaLocal = removeNamespace(schemaNS);
schemaNS = schemaNS.substring(0, schemaNS.lastIndexOf(":"));
QName schemaQName = new QName(schemaNS, schemaLocal);
return schemas.get(schemaQName);
}
/**
* For the specified message, resolve the service's input or output parameters.
*
* @param msg
* The WSDL message. Must have a <code>parameters</code> part.
* @param input
* Flag indicating if this is needed for input or output parameters.
* @param ordinals
* The parameter's mapping to their position at the 4GL definition.
* @param types
* The parameter's mapping to their legacy type.
* @param modes
* The parameter's mapping to their mode (INPUT/OUTPUT/INPUT-OUTPUT).
*
* @return The service parameters for this message.
*/
private List<LegacyServiceParameter> resolveParameters(Message msg,
boolean input,
Map<String, Integer> ordinals,
Map<String, String> types,
Map<String, String> modes)
{
WSDLSchema schema = resolveSchema(msg, "parameters");
Part part = msg.getPart("parameters");
String typeName = part.getElementName().getLocalPart();
ComplexSchemaType type = schema.types.get(typeName);
if (type == null)
{
return Collections.emptyList();
}
List<LegacyServiceParameter> res = new ArrayList<>();
res.addAll(type.buildParameters(input, "result", ordinals, types, modes));
return res;
}
/**
* Base definition for a custom type defined in the WSDL schema.
*/
static class BaseSchemaType
{
/** The type name. */
protected final String name;
/** The XML name for this type. */
protected final String xmlName;
/** Flag indicating if the null values must be emitted. */
protected final boolean nillable;
/** The associated XST type. */
protected final String xsdType;
/** The associated legacy type. */
protected final String proDataType;
/** The parameter's extent. */
protected final int extent;
/**
* Create a new instance.
*
* @param name
* The table name.
* @param xmlName
* The XML node name.
* @param nillable
* Flag indicating if the elements can be <code>null</code>.
* @param xsdType
* The associated XST type.
* @param proDataType
* The associated legacy type.
* @param extent
* The parameter's extent.
*/
public BaseSchemaType(String name, String xmlName, boolean nillable, String xsdType, String proDataType, int extent)
{
this.name = name;
this.xmlName = xmlName;
this.nillable = nillable;
this.xsdType = xsdType;
this.proDataType = proDataType;
this.extent = extent;
}
/**
* Build the associated service parameter for this type.
*
* @return See above.
*/
protected LegacyServiceParameter buildParameter()
{
return buildParameter(-1, false, false, false, null, SourceNameMapper.NO_EXTENT, false);
}
/**
* Build the associated service parameter for this type.
*
* @param ordinal
* The parameter's index at the definition.
* @param input
* Flag indicating this is an INPUT parameter.
* @param output
* Flag indicating this is an OUTPUT parameter.
* @param returnValue
* Flag indicating this parameter is for the return type.
* @param type
* The parameter's legacy type.
* @param extent
* The parameter's extent.
* @param useB4Image
* Flag indicating if the before-image must be included.
*
* @return See above.
*/
protected final LegacyServiceParameter buildParameter(int ordinal,
boolean input,
boolean output,
boolean returnValue,
String type,
int extent,
boolean useB4Image)
{
return new LegacyServiceParameter()
{
@Override
public Class<? extends Annotation> annotationType()
{
return LegacyServiceParameter.class;
}
public int ordinal()
{
return ordinal;
};
@Override
public String name()
{
return name;
}
@Override
public String type()
{
return type == null ? resolveType() : type.toLowerCase();
};
@Override
public boolean allowUnknown()
{
return nillable;
}
@Override
public boolean returnValue()
{
return returnValue;
}
@Override
public boolean output()
{
return output;
}
@Override
public boolean input()
{
return input;
}
@Override
public String target()
{
return output ? "SOAP:" + name : null; // this is for OUTPUT
}
@Override
public String source()
{
return input ? "SOAP:" + name : null; // this is for INPUT
}
@Override
public int extent()
{
return extent;
}
@Override
public String defaultValue()
{
return "";
}
};
}
/**
* Resolve the {@link BaseDataType} type name for the {@link #xsdType}.
*
* @return The resolved type.
*/
protected String resolveType()
{
switch (xsdType)
{
case "xsd:string":
return "prodata:clob".equals(proDataType) ? "clob" : "character";
case "xsd:int":
return "integer";
case "xsd:long":
return "int64";
case "xsd:decimal":
return "decimal";
case "xsd:date":
return "date";
case "xsd:dateTime":
return "datetime";
case "xsd:boolean":
return "logical";
case "xsd:base64Binary":
return "prodata:blob".equals(proDataType) ? "blob" : "memptr";
}
throw new RuntimeException("Unsupported XSD type: " + xsdType);
}
}
/**
* Definition of a type associated with a DATASET.
*/
static class DataSetSchemaType
extends BaseSchemaType
{
/** The list of tables in this dataset. */
protected final List<TableSchemaType> tables;
/** Flag indicating the 'before-image' must be written. */
protected boolean useBeforeImage = false;
/**
* Create a new instance.
*
* @param name
* The dataset name.
* @param xmlName
* The XML node name.
* @param tables
* The table list.
* @param useBeforeImage
* Flag indicating the 'before-image' must be written.
*/
public DataSetSchemaType(String name, String xmlName, List<TableSchemaType> tables, boolean useBeforeImage)
{
super(name, xmlName, false, "DATASET", null, SourceNameMapper.NO_EXTENT);
this.tables = tables;
this.useBeforeImage = useBeforeImage;
}
/**
* Get the service parameter type.
*
* @return Always <code>DATASET</code>.
*/
protected String resolveType()
{
return "DATASET";
}
}
/**
* Definition of a type associated with a TABLE.
*/
static class TableSchemaType
extends BaseSchemaType
{
/** The table fields, as defined in the WSDL. */
protected final List<BaseSchemaType> fields;
/** The list of indexes for this table. */
protected final List<Index> indexes;
/** The before-table name. */
protected final String beforeTable;
/**
* Create a new table schema with the specified name.
*
* @param name
* The table name.
*/
public TableSchemaType(String name)
{
this(name, name, false, null, null, null);
}
/**
* Create a new instance.
*
* @param name
* The table name.
* @param xmlName
* The XML node name.
* @param nillable
* Flag indicating if the elements can be <code>null</code>.
* @param fields
* The table fields, as defined at the schema.
*/
public TableSchemaType(String name,
String xmlName,
boolean nillable,
List<BaseSchemaType> fields,
List<Index> indexes,
String beforeTable)
{
super(name, xmlName, nillable, "TABLE", null, SourceNameMapper.NO_EXTENT);
this.fields = fields;
this.indexes = indexes;
this.beforeTable = beforeTable;
}
/**
* Get the service parameter type.
*
* @return Always <code>DATASET</code>.
*/
protected String resolveType()
{
return "TABLE";
}
/**
* Get a string representation of the indexes in this table.
*
* @return The string representation of the indexes.
*/
public String getIndexes()
{
String res = "";
// "[primeUniqueFlag,primeFld1[,primeFldn]...:primeIdxName.][uniqueIdxfld1[,uniqueIdxfldn]...:uniqueIdxName.]..."
for (Index index : indexes)
{
if (index.primary())
{
res = res + (index.unique() ? "1" : "0") + ",";
}
for (int i = 0; i < index.components().length; i++)
{
IndexComponent comp = index.components()[i];
res = res + comp.legacy() + (i == index.components().length - 1 ? "" : ",");
}
res = res + ":" + index.legacy() + ".";
}
return res;
}
}
/**
* A loaded schema configuration from a WSDL file.
*/
static class WSDLSchema
{
/** A mapping of all the schema elements defining a type, by their name. */
private final Map<String, Element> allTypes = new HashMap<>();
/** A mapping of all the schema complexType nodes, by their name. */
private final Map<String, Element> allComplexTypes = new HashMap<>();
/** A mapping of all loaded schema types, by their name. */
private final Map<String, ComplexSchemaType> types = new HashMap<>();
/** The name of this schema. */
private final QName qname;
/** The target namespace. */
private final String targetNamespace;
/**
* Load and initialize this instance from the specified schema.
*
* @param schema
* The schema definition.
*/
public WSDLSchema(Schema schema)
{
Element schemaEl = schema.getElement();
this.targetNamespace = schemaEl.getAttribute("targetNamespace");
String localName = removeNamespace(targetNamespace);
String tns = targetNamespace.substring(0, targetNamespace.lastIndexOf(":"));
this.qname = new QName(tns, localName);
List<Element> schemaElements = new ArrayList<>();
List<Element> schemaComplexElements = new ArrayList<>();
NodeList schemanl = schemaEl.getChildNodes();
for (int i = 0; i < schemanl.getLength(); i++)
{
Node n = schemanl.item(i);
if (n.getNodeType() == Node.ELEMENT_NODE)
{
if ("element".equals(n.getLocalName()))
{
schemaElements.add((Element) n);
}
else if ("complexType".equals(n.getLocalName()))
{
schemaComplexElements.add((Element) n);
}
}
}
// save all types first, to solve references later
schemaElements.forEach(el -> allTypes.put(el.getAttribute("name"), el));
schemaComplexElements.forEach(el -> allComplexTypes.put(el.getAttribute("name"), el));
schemaElements.forEach(el ->
{
NodeList snl = el.getElementsByTagName("element");
if (snl.getLength() == 0 || el.getElementsByTagName("complexType").getLength() > 1)
{
return;
}
String elName = removeNamespace(el.getAttribute("name"));
if (elName.endsWith("Response"))
{
elName = elName.substring(0, elName.length() - "Response".length());
}
List<BaseSchemaType> params = new ArrayList<>();
for (int j = 0; j < snl.getLength(); j++)
{
Element childEl = (Element) snl.item(j);
if (!childEl.getAttribute("ref").isEmpty())
{
params.add(buildDataSetType(childEl));
}
else if (!childEl.getAttribute("type").startsWith("xsd"))
{
String type = removeNamespace(childEl.getAttribute("type"));
if (allComplexTypes.containsKey(type))
{
if ("TableHandleParam".equals(type))
{
String tableName = childEl.getAttribute("name");
TableSchemaType dsHandleType = new TableSchemaType(tableName);
params.add(dsHandleType);
}
else if ("DataSetHandleParam".equals(type))
{
String dsName = childEl.getAttribute("name");
DataSetSchemaType dsHandleType = new DataSetSchemaType(dsName, dsName, null, false);
params.add(dsHandleType);
}
else if (type.startsWith(elName))
{
// table types are prefixed with the operation name
TableSchemaType tableType = buildTableType(childEl);
params.add(tableType);
}
else
{
// this is a dataset...
DataSetSchemaType dstype = buildDataSetType(childEl);
params.add(dstype);
}
}
else
{
params.add(buildTableType(childEl));
}
}
else
{
params.add(buildBaseType(childEl, null));
}
}
if (!params.isEmpty())
{
types.put(el.getAttribute("name"), new ComplexSchemaType(el.getAttribute("name"), params));
}
});
}
/**
* Get the target namespace from this schema.
*
* @return See above.
*/
public String getTargetNamespace()
{
return targetNamespace;
}
/**
* Read all indexes from this dataset schema.
*
* @param dsType
* The dataset schema type element.
* @param indexTag
* The element tag for the indexes.
* @param tableTag
* The element tag for the index table.
* @param fieldTag
* The element tag for the index components (fields).
* @param nameAttr
* The attribute holding the name at the table and field element.
*
* @return The map of indexes, per table.
*/
private Map<String, List<Index>> readIndexes(Element dsType,
String indexTag,
String tableTag,
String fieldTag,
String nameAttr)
{
Map<String, List<Index>> indexes = new HashMap<>();
NodeList primarynl = dsType.getElementsByTagName(indexTag);
for (int i = 0; i < primarynl.getLength(); i++)
{
Element elIndex = (Element) primarynl.item(i);
String indexName = elIndex.getAttribute("name");
Element elTable = (Element) elIndex.getElementsByTagName(tableTag).item(0);
String tableName = removeNamespace(elTable.getAttribute(nameAttr));
boolean primary = "true".equals(elIndex.getAttribute("prodata:primaryIndex"));
NodeList fieldnl = elIndex.getElementsByTagName(fieldTag);
IndexComponent[] indexFields = new IndexComponent[fieldnl.getLength()];
for (int j = 0; j < fieldnl.getLength(); j++)
{
Element elField = (Element) fieldnl.item(j);
String fieldName = removeNamespace(elField.getAttribute(nameAttr));
IndexComponent field = new IndexComponent()
{
@Override
public Class<? extends Annotation> annotationType()
{
return IndexComponent.class;
}
@Override
public String name()
{
return fieldName;
}
@Override
public String legacy()
{
return fieldName;
}
@Override
public int extent()
{
return SourceNameMapper.NO_EXTENT;
}
@Override
public boolean descending()
{
return false; // the WSDL does not record ascending/descending state.
}
};
indexFields[j] = field;
}
Index index = new Index()
{
@Override
public Class<? extends Annotation> annotationType()
{
return Index.class;
}
@Override
public String name()
{
return indexName;
}
@Override
public String legacy()
{
return indexName;
}
@Override
public String wordtablename()
{
return null;
}
@Override
public boolean primary()
{
return primary;
}
@Override
public boolean unique()
{
return true;
}
@Override
public boolean word()
{
return false;
}
@Override
public IndexComponent[] components()
{
return indexFields;
}
};
List<Index> tableIndexes = indexes.computeIfAbsent(tableName, (n) -> new ArrayList<>());
tableIndexes.add(index);
}
return indexes;
}
/**
* Build a {@link DataSetSchemaType} from the specified schema element.
*
* @param el
* The schema element.
*
* @return A dataset type.
*/
private DataSetSchemaType buildDataSetType(Element el)
{
List<TableSchemaType> tables = new ArrayList<>();
String type = removeNamespace(el.getAttribute("type"));
Element dsType = allComplexTypes.get(type);
boolean useBeforeImage = dsType != null;
String dsXmlName;
if (useBeforeImage)
{
dsXmlName = type.substring(0, type.length() - "Changes".length());
}
else
{
dsXmlName = removeNamespace(el.getAttribute("ref"));
}
dsType = allTypes.get(dsXmlName);
String dsName = dsXmlName;
if (dsType.hasAttribute("prodata:datasetName"))
{
dsName = dsType.getAttribute("prodata:datasetName");
}
// gather all indexes for this dataset and save them by the table
Map<String, List<Index>> indexes = new HashMap<>();
// the primary indexes first (so they are first in the list)
indexes.putAll(readIndexes(dsType, "prodata:index", "prodata:table", "prodata:field", "name"));
// the unique indexes
indexes.putAll(readIndexes(dsType, "unique", "selector", "field", "xpath"));
Element elDsSeq = (Element) dsType.getElementsByTagName("sequence").item(0);
NodeList children = elDsSeq.getChildNodes();
for (int k = 0; k < children.getLength(); k++)
{
Node n = children.item(k);
if (!(n instanceof Element))
{
continue;
}
Element elTables = (Element) n;
if (!elTables.getNodeName().equals("element"))
{
continue;
}
NodeList tablenl = elTables.getElementsByTagName("complexType");
for (int i = 0; i < tablenl.getLength(); i++)
{
Element elTableType = (Element) tablenl.item(i);
Element elTable = (Element) elTableType.getParentNode();
List<BaseSchemaType> fields = new ArrayList<>();
// sequence children
NodeList rownl = elTableType.getElementsByTagName("sequence").item(0).getChildNodes();
for (int j = 0; j < rownl.getLength(); j++)
{
if (rownl.item(j) instanceof Element)
{
Element childEl = (Element) rownl.item(j);
if (childEl.getElementsByTagName("complexType").getLength() == 0)
{
fields.add(buildBaseType(childEl, "prodata:fieldName"));
}
}
}
String beforeTable = elTable.getAttribute("prodata:beforeTable");
String tableName = elTable.getAttribute("name");
List<Index> tableIndexes = indexes.get(tableName);
String xmlName = tableName;
if (elTable.hasAttribute("prodata:tableName"))
{
tableName = elTable.getAttribute("prodata:tableName");
}
tables.add(new TableSchemaType(tableName, xmlName, false, fields, tableIndexes, beforeTable));
}
}
// the DATASET schema at the WSDL is no longer used to parse the requester's DATASET, the payload
// is transferred directly as XML to the appserver, which will parse it via READ-XML.
return new DataSetSchemaType(dsName, dsXmlName, tables, useBeforeImage);
}
/**
* Build a {@link TableSchemaType} from the specified schema element.
*
* @param el
* The schema element.
*
* @return A table type.
*/
private TableSchemaType buildTableType(Element el)
{
List<BaseSchemaType> fields = new ArrayList<>();
String tableType = removeNamespace(el.getAttribute("type"));
Element elTableParam = allComplexTypes.get(tableType);
Element elSeqParam = (Element) elTableParam.getElementsByTagName("element").item(0);
String tableRowName = elSeqParam.getAttribute("name");
String tableRowType = removeNamespace(elSeqParam.getAttribute("type"));
Element elTableRow = allComplexTypes.get(tableRowType);
NodeList rownl = elTableRow.getElementsByTagName("element");
for (int j = 0; j < rownl.getLength(); j++)
{
Element childEl = (Element) rownl.item(j);
fields.add(buildBaseType(childEl, "prodata:fieldName"));
}
String tableName = el.getAttribute("name");
return new TableSchemaType(tableName,
tableName,
"true".equals(el.getAttribute("nillable")),
fields,
null, // no indexes are in the WSDL for non-dataset tables
null);
}
/**
* Build a base type from the specified schema element.
*
* @param el
* The schema element.
* @param legacyAttr
* An attribute from which to resolve the legacy name.
*
* @return A base type.
*/
private BaseSchemaType buildBaseType(Element el, String legacyAttr)
{
boolean isExtent = el.getAttribute("minOccurs").length() > 0 &&
el.getAttribute("maxOccurs").length() > 0;
int extent = SourceNameMapper.NO_EXTENT;
if (isExtent)
{
if ("0".equals(el.getAttribute("minOccurs")))
{
extent = SourceNameMapper.DYNAMIC_EXTENT;
}
else
{
extent = Integer.parseInt(el.getAttribute("maxOccurs"));
}
}
String xmlName = el.getAttribute("name");
String legacyName = xmlName;
if (legacyAttr != null && el.hasAttribute(legacyAttr))
{
legacyName = el.getAttribute(legacyAttr);
}
return new BaseSchemaType(legacyName,
xmlName,
"true".equals(el.getAttribute("nillable")),
el.getAttribute("type"),
el.getAttribute("prodata:dataType"),
extent);
}
}
/**
* Defines a complex schema type.
*/
private static class ComplexSchemaType
extends BaseSchemaType
{
/** The parameters for this complex type. */
protected final List<BaseSchemaType> params;
/**
* Create a new instance.
*
* @param name
* The complex type name.
* @param params
* The list of parameters.
*/
public ComplexSchemaType(String name, List<BaseSchemaType> params)
{
super(name, name, false, null, null, SourceNameMapper.NO_EXTENT);
this.params = params;
}
/**
* Resolve the complex type's parameters as service parameters.
*
* @param input
* Flag indicating if this is needed for input or output parameters.
* @param retName
* The name of the <code>return</code> parameter.
* @param ordinals
* The parameter's mapping to their position at the 4GL definition.
* @param types
* The parameter's mapping to their legacy type.
* @param modes
* The parameter's mapping to their mode (INPUT/OUTPUT/INPUT-OUTPUT).
*
* @return The service parameters for this message.
*/
public List<LegacyServiceParameter> buildParameters(boolean input,
String retName,
Map<String, Integer> ordinals,
Map<String, String> types,
Map<String, String> modes)
{
List<LegacyServiceParameter> res = new ArrayList<>();
int idx = 0;
for (BaseSchemaType ptype : params)
{
boolean returnValue = !input && retName.equalsIgnoreCase(ptype.name);
int ordinal = returnValue ? 0 : ordinals.get(ptype.name.toLowerCase());
String type = types.get(ptype.name.toLowerCase());
String mode = modes.get(ptype.name.toLowerCase());
if ("INPUT-OUTPUT".equals(mode) && !input)
{
// these are reported for the INPUT params
continue;
}
boolean pinput = input || "INPUT-OUTPUT".equals(mode);
boolean poutput = !input || "INPUT-OUTPUT".equals(mode);
boolean useBeforeImage = ptype instanceof DataSetSchemaType &&
((DataSetSchemaType) ptype).useBeforeImage;
if ("HANDLE".equalsIgnoreCase(type) && ptype instanceof DataSetSchemaType)
{
type = "DATASET-HANDLE";
}
else if ("HANDLE".equalsIgnoreCase(type) && ptype instanceof TableSchemaType)
{
type = "TABLE-HANDLE";
}
LegacyServiceParameter lsp = ptype.buildParameter(ordinal,
pinput,
poutput,
returnValue,
type,
ptype.extent,
useBeforeImage);
if (returnValue)
{
res.add(0, lsp);
}
else
{
res.add(lsp);
}
idx = idx + 1;
}
return res;
}
}
}