SoapOperationParameter.java
/*
** Module : SoapOperationParameter.java
** Abstract : Definition of a parameter used for a SOAP service exported via .wsm files.
**
** Copyright (c) 2020-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20200514 First version.
** CA 20200528 The SOAP input are the .wsm files, not .xpxg (as these contain info configured when
** running ProxyGen).
** Fixes for extent parameters. Added BEFORE-TABLE support.
** 002 CA 20211012 Fixed generated WSDL and reworked SOAP support to rely on namespace, when resolving an
** operation.
** CA 20211013 Added DATASET-HANDLE and TABLE-HANDLE parameter support for WSDL.
** DATASET parameters have the table table indexes, too, at the WSDL schema.
** CA 20211015 Added the schema for dataset relations.
** CA 20211112 Fixed cases when the XML node name differences from the parameter name (XML-NODE-NAME and
** SERIALIZE-NAME at the dataset, table and field).
** 003 OM 20230115 Replaced absolutePath(), relativePath(), upPath() and downPath() with faster
** versions, based on node types rather on string paths.
*/
/*
** 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.pattern;
import java.lang.annotation.*;
import java.util.*;
import com.goldencode.ast.*;
import com.goldencode.p2j.persist.annotation.*;
import com.goldencode.p2j.schema.*;
import com.goldencode.p2j.util.*;
import com.goldencode.util.*;
import static com.goldencode.p2j.uast.ProgressParserTokenTypes.*;
/**
* Definition of a SOAP parameter exported via a .wsm file.
* <p>
* The data is read from the actual 4GL target (external program, internal entry, class or method).
*/
class SoapOperationParameter
{
/** The legacy parameter name. */
final String name;
/** The parameter token type. */
final long type;
/** The parameter mode (a token type). */
final long mode;
/** Flag indicating if this parameter will be returned by the SOAP request, if is unknown. */
final boolean nillable;
/** The extent value (-2 for no extent, -1 for dynamic extent, the extent value otherwise). */
final int extent;
/** Flag indicating the 'before-image' must be written. */
final boolean useBeforeImage;
/** The name to be used at the XML node (for datasets). */
String nodeName; // TODO: NAMESPACE-PREFIX, NAMESPACE-URI
/** List of table fields, if this is a TABLE parameter. */
Map<String, SoapOperationParameter> tableFields = null;
/** List of indexes for this table. */
List<Index> tableIndexes = null;
/** Relation list in case of datasets. */
List<DataSetRelation> relations = null;
/** List of table fields, if this is a DATASET parameter. */
List<SoapOperationParameter> datasetTables = null;
/** The before-table name. */
String beforeTable = null;
/**
* Create a new instance.
*
* @param name
* The legacy parameter name.
* @param type
* The parameter token type.
* @param mode
* The parameter mode (a token type).
* @param nillable
* Flag indicating if this parameter will be returned by the SOAP request, if is unknown.
*/
public SoapOperationParameter(String name, long type, long mode, boolean nillable)
{
this(name, type, mode, nillable, SourceNameMapper.NO_EXTENT);
}
/**
* Create a new instance.
*
* @param name
* The legacy parameter name.
* @param type
* The parameter token type.
* @param mode
* The parameter mode (a token type).
* @param nillable
* Flag indicating if this parameter will be returned by the SOAP request, if is unknown.
* @param useBeforeImage
* Flag indicating if before-image is used for a dataset.
*/
public SoapOperationParameter(String name, long type, long mode, boolean nillable, boolean useBeforeImage)
{
this.name = name;
this.type = type;
this.mode = mode;
this.nillable = nillable;
this.extent = SourceNameMapper.NO_EXTENT;
this.useBeforeImage = useBeforeImage;
}
/**
* Create a new instance.
*
* @param name
* The legacy parameter name.
* @param type
* The parameter token type.
* @param mode
* The parameter mode (a token type).
* @param nillable
* Flag indicating if this parameter will be returned by the SOAP request, if is unknown.
* @param extent
* The extent value (-2 for no extent, -1 for dynamic extent, the extent value otherwise).
*/
public SoapOperationParameter(String name, long type, long mode, boolean nillable, int extent)
{
this.name = name;
this.type = type;
this.mode = mode;
this.nillable = nillable;
this.extent = extent;
this.useBeforeImage = false;
}
/**
* For a table parameter, load its fields, as they appear defined in the schema dictionary.
*
* @param ref
* The parameter definition AST.
* @param dictionary
* The schema dictionary.
* @param forDataset
* Flag indicating that this table is for a dataset
*/
public void loadTable(Aast ref, SchemaDictionary dictionary, boolean forDataset)
{
tableFields = new LinkedHashMap<>();
ref = ref.getType() == TEMP_TABLE ? ref : ref.getImmediateChild(TEMP_TABLE, null);
// load the fields
String schema = (String) ref.getAnnotation("schemaname");
try
{
List<Aast> fields = dictionary.getAllFields(schema);
for (Aast field : fields)
{
String fname = (String) field.getAnnotation("historical");
int ftype = field.getType();
long extent = SourceNameMapper.NO_EXTENT;
if (field.downPath(KW_EXTENT))
{
Aast extAst = field.getImmediateChild(KW_EXTENT, null);
extAst = extAst.getImmediateChild(NUM_LITERAL, null);
extent = Integer.parseInt(extAst.getText());
}
SoapOperationParameter fparam = new SoapOperationParameter(fname, ftype, mode, true, (int) extent);
tableFields.put(fname.toLowerCase(), fparam);
if (forDataset)
{
String serializeName = readProperty(dictionary.getFieldProperty(name + "." + fname, KW_SERIALZN));
String xmlNodeName = readProperty(dictionary.getFieldProperty(name + "." + fname, KW_XML_NNAM));
fparam.setNodeName(serializeName, xmlNodeName);
}
}
Aast table = dictionary.getTable(schema);
if (forDataset)
{
String serializeName = readProperty(dictionary.getTableProperty(name, KW_SERIALZN));
String xmlNodeName = readProperty(dictionary.getTableProperty(name, KW_XML_NNAM));
setNodeName(serializeName, xmlNodeName);
}
this.beforeTable = (String) table.getAnnotation("before-table");
Iterator<Aast> iter = dictionary.indices(table);
while (iter.hasNext())
{
Aast next = iter.next();
boolean unique = false;
boolean primary = false;
Aast props = next.getImmediateChild(PROPERTIES, null);
while (props != null)
{
unique = unique || (props.getImmediateChild(KW_UNIQUE, null) != null);
primary = primary || (props.getImmediateChild(KW_PRIMARY, null) != null);
props = next.getImmediateChild(PROPERTIES, props);
}
if (!(unique || primary))
{
continue;
}
List<IndexComponent> indexFields = new ArrayList<>();
Aast field = null;
while ((field = next.getImmediateChild(INDEX_FIELD, field)) != null)
{
String fieldName = field.getText();
SoapOperationParameter pfield = tableFields.get(fieldName.toLowerCase());
boolean descending = field.downPath(KW_DESCEND);
IndexComponent component = new IndexComponent()
{
@Override
public Class<? extends Annotation> annotationType()
{
return IndexComponent.class;
}
@Override
public String name()
{
return fieldName;
}
@Override
public String legacy()
{
return pfield.nodeName == null ? fieldName : pfield.nodeName;
}
@Override
public int extent()
{
return SourceNameMapper.NO_EXTENT;
}
@Override
public boolean descending()
{
return descending;
}
};
indexFields.add(component);
}
String indexName = next.getText();
boolean indexUnique = unique;
boolean indexPrimary = primary;
Index index = new Index()
{
@Override
public Class<? extends Annotation> annotationType()
{
return Index.class;
}
@Override
public String name()
{
// here we hold the table name. Used by SoapWsdl.createDataSetParam
String tableName = SoapOperationParameter.this.name;
String xmlNodeName = SoapOperationParameter.this.nodeName;
return xmlNodeName == null ? tableName : xmlNodeName;
}
@Override
public String legacy()
{
return indexName;
}
@Override
public String wordtablename()
{
return null;
}
@Override
public boolean primary()
{
return indexPrimary;
}
@Override
public boolean unique()
{
return indexUnique;
}
@Override
public boolean word()
{
return false;
}
@Override
public IndexComponent[] components()
{
return indexFields.toArray(new IndexComponent[0]);
}
};
if (tableIndexes == null)
{
tableIndexes = new ArrayList<>();
}
tableIndexes.add(index);
}
}
catch (SchemaException e)
{
throw new RuntimeException(e);
}
}
/**
* Check if this table is nested (via the {@link #relations}) as a child for another table.
*
* @param table
* The table reference.
*
* @return See above.
*/
public boolean isNestedTable(SoapOperationParameter table)
{
if (relations == null)
{
return false;
}
for (DataSetRelation dsr : relations)
{
if (dsr.child.equalsIgnoreCase(table.name))
{
return true;
}
}
return false;
}
/**
* Get all the child tables for this parent table.
*
* @param parent
* The parent table.
*
* @return The child tables in the {@link #relations}, for this parent.
*/
public List<SoapOperationParameter> getChildTables(SoapOperationParameter parent)
{
if (relations == null)
{
return Collections.emptyList();
}
List<SoapOperationParameter> children = new ArrayList<>();
for (DataSetRelation dsr : relations)
{
if (dsr.parent.equalsIgnoreCase(parent.name))
{
for (SoapOperationParameter table : datasetTables)
{
if (table.name.equalsIgnoreCase(dsr.child))
{
children.add(table);
}
}
}
}
return children;
}
/**
* For a dataset parameter, load its tables, as they appear defined in the schema dictionary.
*
* @param ref
* The parameter definition AST.
* @param dictionary
* The schema dictionary.
*/
public void loadDataSet(Aast ref, SchemaDictionary dictionary)
throws SchemaException
{
datasetTables = new ArrayList<>();
AstSymbolResolver resolver = AstSymbolResolver.getResolver();
ref = ref.getImmediateChild(DATA_SET, null);
ref = resolver.getAst((Long) ref.getAnnotation("refid"));
ref = ref.getImmediateChild(KW_FOR, null);
Aast tast = ref.getImmediateChild(TEMP_TABLE, null);
while (tast != null)
{
SoapOperationParameter top = new SoapOperationParameter(tast.getText(),
KW_TABLE,
mode,
true);
top.loadTable(tast, dictionary, true);
datasetTables.add(top);
tast = ref.getImmediateChild(TEMP_TABLE, tast);
}
// TODO: PARENT-ID-RELATION
// add relations
ref = ref.getParent();
Aast rast = ref.getImmediateChild(KW_DATA_REL, null);
while (rast != null)
{
Aast parent = rast.getImmediateChild(TEMP_TABLE, null);
Aast child = rast.getImmediateChild(TEMP_TABLE, parent);
parent = dictionary.getTable((String) parent.getAnnotation("schemaname"));
child = dictionary.getTable((String) child.getAnnotation("schemaname"));
String name = (String) rast.getImmediateChild(SYMBOL, null).getText();
String parentName = (String) parent.getAnnotation("historical");
String childName = (String) child.getAnnotation("historical");
String pairs = "";
Aast relFields = rast.getImmediateChild(KW_REL_FLDS, null);
Aast fieldPair = relFields.getImmediateChild(PARENT_CHILD_RELATION, null);
while (fieldPair != null)
{
Aast f1 = fieldPair.getChildAt(0);
Aast f2 = fieldPair.getChildAt(1);
f1 = dictionary.getField(parentName + "." + f1.getAnnotation("name"));
f2 = dictionary.getField(parentName + "." + f2.getAnnotation("name"));
pairs = pairs + (pairs.isEmpty() ? "" : ",") +
f1.getAnnotation("historical") + "," +
f2.getAnnotation("historical");
fieldPair = relFields.getImmediateChild(PARENT_CHILD_RELATION, fieldPair);
}
boolean nested = rast.downPath(KW_NESTED);
boolean fkHidden = rast.downPath(KW_F_KEY_H);
if (relations == null)
{
relations = new ArrayList<>();
}
relations.add(new DataSetRelation(name, parentName, childName, pairs, nested, fkHidden));
rast = ref.getImmediateChild(KW_DATA_REL, rast);
}
}
/**
* Set the {@link #nodeName} for this parameter from the XML-NODE-NAME and SERIALIZE-NAME attributes.
*
* @param serializeName
* The SERIALIZE-NAME attribute.
* @param xmlNodeName
* The XML-NODE-NAME attribute.
*/
void setNodeName(String serializeName, String xmlNodeName)
{
if (xmlNodeName == null)
{
if (serializeName == null)
{
this.nodeName = null;
}
else
{
this.nodeName = serializeName;
}
}
else
{
this.nodeName = xmlNodeName;
}
}
/**
* Read a property from a schema dict AST.
*
* @param propAst
* The property AST.
*
* @return The property value.
*/
private String readProperty(Aast propAst)
{
if (propAst == null)
{
return null;
}
Aast ref = propAst.getImmediateChild(STRING, null);
if (ref == null)
{
return null;
}
String txt = ref.getText();
txt = StringHelper.stripEnclosing(txt, new char[] { '\"', '\'' });
return txt;
}
}