@adobe/acc-js-sdk 1.1.15 → 1.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.js CHANGED
@@ -36,6 +36,7 @@ const request = require('./transport.js').request;
36
36
  const Application = require('./application.js').Application;
37
37
  const EntityAccessor = require('./entityAccessor.js').EntityAccessor;
38
38
  const { Util } = require('./util.js');
39
+ const { XtkJobInterface } = require('./xtkJob.js');
39
40
  const qsStringify = require('qs-stringify');
40
41
 
41
42
  /**
@@ -60,6 +61,13 @@ const qsStringify = require('qs-stringify');
60
61
  * @memberOf Campaign
61
62
  */
62
63
 
64
+ /**
65
+ * @typedef {Object} XtkMethodParam
66
+ * @property {string} name - the name of the parameter
67
+ * @property {string} type - the type of the parameter
68
+ * @property {*} value - the values of the parameter in the expected representation
69
+ * @memberOf Campaign
70
+ */
63
71
 
64
72
  /**
65
73
  * Java Script Proxy handler for an XTK object. An XTK object is one constructed with the following syntax:
@@ -173,16 +181,7 @@ const clientHandler = (representation, headers, pushDownOptions) => {
173
181
  return callContext;
174
182
 
175
183
  // get Schema id from namespace (find first upper case letter)
176
- var schemaId = "";
177
- for (var i=0; i<namespace.length; i++) {
178
- const c = namespace[i];
179
- if (c >='A' && c<='Z') {
180
- schemaId = schemaId + ":" + c.toLowerCase() + namespace.substr(i+1);
181
- break;
182
- }
183
- schemaId = schemaId + c;
184
- }
185
- callContext.schemaId = schemaId;
184
+ callContext.schemaId = Util.schemaIdFromNamespace(namespace);
186
185
 
187
186
  const caller = function(thisArg, argumentsList) {
188
187
  const callContext = thisArg["."];
@@ -927,17 +926,19 @@ class Client {
927
926
  * @private
928
927
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
929
928
  * @param {string} method is the method to call, for instance Logon
929
+ * @param {boolean} isStatic is a boolean indicating if the method is static or not
930
930
  * @param {boolean} internal is a boolean indicating whether the SOAP call is performed by the SDK (internal = true) or on behalf of a user
931
931
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
932
932
  * parameters should be set
933
933
  */
934
- _prepareSoapCall(urn, method, internal, extraHttpHeaders, pushDownOptions) {
934
+ _prepareSoapCall(urn, method, isStatic, internal, extraHttpHeaders, pushDownOptions) {
935
935
  const soapCall = new SoapMethodCall(this._transport, urn, method,
936
936
  this._sessionToken, this._securityToken,
937
937
  this._getUserAgentString(),
938
938
  Object.assign({}, this._connectionParameters._options, pushDownOptions),
939
939
  extraHttpHeaders);
940
940
  soapCall.internal = !!internal;
941
+ soapCall.isStatic = isStatic;
941
942
  return soapCall;
942
943
  }
943
944
 
@@ -988,6 +989,225 @@ class Client {
988
989
  _soapEndPoint() {
989
990
  return this._connectionParameters._endpoint + "/nl/jsp/soaprouter.jsp";
990
991
  }
992
+
993
+ // Serializes parameters for a SOAP call
994
+ // @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
995
+ // @param {DOMDocument} schema is the XML schema of the method call
996
+ // @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
997
+ // @param {Campaign.XtkMethodParam[]} inputParameters is the list of input parameters. The first paramater in the array is the "this" parameter for non-static method calls
998
+ // @param {string} representation is the representation to use to interpret the input parameters
999
+ async _writeSoapCallParameters(entitySchemaId, schema, soapCall, inputParameters, representation) {
1000
+ const methodName = soapCall.methodName;
1001
+ var isThisParam = !soapCall.isStatic;
1002
+
1003
+ for (const ip of inputParameters) {
1004
+ const type = ip.type;
1005
+ const paramName = ip.name;
1006
+ var paramValue = ip.value;
1007
+
1008
+ if (type == "string")
1009
+ soapCall.writeString(ip.name, XtkCaster.asString(ip.value));
1010
+ else if (type == "primarykey")
1011
+ soapCall.writeString(ip.name, XtkCaster.asString(ip.value));
1012
+ else if (type == "boolean")
1013
+ soapCall.writeBoolean(ip.name, XtkCaster.asBoolean(ip.value));
1014
+ else if (type == "byte")
1015
+ soapCall.writeByte(ip.name, XtkCaster.asByte(ip.value));
1016
+ else if (type == "short")
1017
+ soapCall.writeShort(ip.name, XtkCaster.asShort(ip.value));
1018
+ else if (type == "int")
1019
+ soapCall.writeLong(ip.name, XtkCaster.asLong(ip.value));
1020
+ else if (type == "long")
1021
+ soapCall.writeLong(ip.name, XtkCaster.asLong(ip.value));
1022
+ else if (type == "int64")
1023
+ soapCall.writeInt64(ip.name, XtkCaster.asInt64(ip.value));
1024
+ else if (type == "datetime")
1025
+ soapCall.writeTimestamp(ip.name, XtkCaster.asTimestamp(ip.value));
1026
+ else if (type == "date")
1027
+ soapCall.writeDate(ip.name, XtkCaster.asDate(ip.value));
1028
+ else if (type == "DOMDocument" || type == "DOMElement") {
1029
+ var docName = undefined;
1030
+ let paramRepresentation = representation;
1031
+ if (paramValue.__xtkProxy) {
1032
+ // A xtk proxy object is passed as a parameter. The call context contains the schema so we
1033
+ // can use it to determine the XML document root (docName)
1034
+ const paramValueContext = paramValue["."];
1035
+ paramValue = paramValueContext.object;
1036
+ const xtkschema = paramValueContext.schemaId;
1037
+ const index = xtkschema.indexOf(":");
1038
+ docName = xtkschema.substring(index+1);
1039
+ paramRepresentation = paramValueContext.representation; // xtk proxy may have it's own representation
1040
+ }
1041
+ else {
1042
+ // Hack for workflow API. The C++ code checks that the name of the XML element is <variables>. When
1043
+ // using xml representation at the SDK level, it's ok since the SDK caller will set that. But this does
1044
+ // not work when using "BadgerFish" representation where we do not know the root element name.
1045
+ if (entitySchemaId == "xtk:workflow" && methodName == "StartWithParameters" && paramName == "parameters")
1046
+ docName = "variables";
1047
+ if (entitySchemaId == "nms:rtEvent" && methodName == "PushEvent")
1048
+ docName = "rtEvent";
1049
+ // Try to guess the document name. This is usually available in the xtkschema attribute
1050
+ var xtkschema = EntityAccessor.getAttributeAsString(paramValue, "xtkschema");
1051
+ if (!xtkschema) xtkschema = paramValue["@xtkschema"];
1052
+ if (xtkschema) {
1053
+ const index = xtkschema.indexOf(":");
1054
+ docName = xtkschema.substring(index+1);
1055
+ }
1056
+ if (!docName) docName = paramName; // Use te parameter name as the XML root element
1057
+ }
1058
+ var xmlValue = this._fromRepresentation(docName, paramValue, paramRepresentation || representation);
1059
+ if (type == "DOMDocument") {
1060
+ if (isThisParam) {
1061
+ isThisParam = false;
1062
+ // The xtk:persist#NewInstance requires a xtkschema attribute which we can compute here
1063
+ // Actually, we're always adding it, for all non-static methods
1064
+ const xmlRoot = xmlValue.nodeType === 9 ? xmlValue.documentElement : xmlValue;
1065
+ if (!xmlRoot.hasAttribute("xtkschema"))
1066
+ xmlRoot.setAttribute("xtkschema", entitySchemaId);
1067
+ soapCall.writeDocument("document", xmlValue);
1068
+ }
1069
+ else
1070
+ soapCall.writeDocument(paramName, xmlValue);
1071
+ }
1072
+ else
1073
+ soapCall.writeElement(paramName, xmlValue);
1074
+ }
1075
+ else
1076
+ throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${entitySchemaId}`);
1077
+ }
1078
+ }
1079
+
1080
+ // Deserializes results for a SOAP call
1081
+ // @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
1082
+ // @param {DOMDocument} schema is the XML schema of the method call
1083
+ // @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
1084
+ // @param {Campaign.XtkMethodParam[]} outputParameters is the list of output parameters. The first paramater in the array is the "this" parameter for non-static method calls
1085
+ // @param {string} representation is the representation to use to interpret the parameters
1086
+ async _readSoapCallResult(entitySchemaId, schema, soapCall, outputParameters, representation) {
1087
+ const methodName = soapCall.methodName;
1088
+ var isThisParam = !soapCall.isStatic;
1089
+ for (const op of outputParameters) {
1090
+ const type = op.type;
1091
+ const paramName = op.name;
1092
+ var returnValue = op.value;
1093
+
1094
+ if (isThisParam) {
1095
+ isThisParam = false;
1096
+ // Non static methods, such as xtk:query#SelectAll return a element named "entity" which is the object itself on which
1097
+ // the method is called. This is the new version of the object (in XML form)
1098
+ const entity = soapCall.getEntity();
1099
+ if (entity)
1100
+ returnValue = this._toRepresentation(entity, representation);
1101
+ }
1102
+ else if (type == "string")
1103
+ returnValue = soapCall.getNextString();
1104
+ else if (type == "primarykey")
1105
+ returnValue = soapCall.getNextPrimaryKey();
1106
+ else if (type == "boolean")
1107
+ returnValue = soapCall.getNextBoolean();
1108
+ else if (type == "byte")
1109
+ returnValue = soapCall.getNextByte();
1110
+ else if (type == "short")
1111
+ returnValue = soapCall.getNextShort();
1112
+ else if (type == "long")
1113
+ returnValue = soapCall.getNextLong();
1114
+ else if (type == "int64")
1115
+ // int64 are represented as strings to make sure no precision is lost
1116
+ returnValue = soapCall.getNextInt64();
1117
+ else if (type == "datetime")
1118
+ returnValue = soapCall.getNextDateTime();
1119
+ else if (type == "date")
1120
+ returnValue = soapCall.getNextDate();
1121
+ else if (type == "DOMDocument") {
1122
+ returnValue = soapCall.getNextDocument();
1123
+ returnValue = this._toRepresentation(returnValue, representation);
1124
+ }
1125
+ else if (type == "DOMElement") {
1126
+ returnValue = soapCall.getNextElement();
1127
+ returnValue = this._toRepresentation(returnValue, representation);
1128
+ }
1129
+ else {
1130
+ const schemaName = entitySchemaId.substring(entitySchemaId.indexOf(':') + 1);
1131
+ // type can reference a schema element. The naming convension is that the type name
1132
+ // is {schemaName}{elementNameCamelCase}. For instance, the type "sessionUserInfo"
1133
+ // matches the "userInfo" element of the "xtkSession" schema
1134
+ let element;
1135
+ if (type.substr(0, schemaName.length) == schemaName) {
1136
+ const shortTypeName = type.substr(schemaName.length, 1).toLowerCase() + type.substr(schemaName.length + 1);
1137
+ element = DomUtil.getFirstChildElement(schema, "element");
1138
+ while (element) {
1139
+ if (element.getAttribute("name") == shortTypeName) {
1140
+ // Type found in schema: Process as a DOM element
1141
+ returnValue = soapCall.getNextElement();
1142
+ returnValue = this._toRepresentation(returnValue, representation);
1143
+ break;
1144
+ }
1145
+ element = DomUtil.getNextSiblingElement(element, "element");
1146
+ }
1147
+
1148
+ }
1149
+ if (!element)
1150
+ throw CampaignException.UNEXPECTED_SOAP_RESPONSE(soapCall, `Unsupported return type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${entitySchemaId}'`);
1151
+ }
1152
+ op.value = returnValue;
1153
+ }
1154
+ soapCall.checkNoMoreArgs();
1155
+ }
1156
+
1157
+ /**
1158
+ * Serialize input parameters, make a SOAP call and deserialize result parameters. Calls observer when necessary.
1159
+ *
1160
+ * The inputParams and outputParams arrays contain prepopulated parameter lists with the name, type (and value for
1161
+ * input params). This function does not return anything but will populate the outputParams array with the actual result.
1162
+ *
1163
+ * @private
1164
+ * @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
1165
+ * @param {DOMDocument} schema is the XML schema of the method call
1166
+ * @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
1167
+ * @param {Campaign.XtkMethodParam[]} inputParams is the list of input parameters. The first paramater in the array is the "this" parameter for non-static method calls
1168
+ * @param {Campaign.XtkMethodParam[]} outputParams is the list of output parameters. The first paramater in the array is the "this" parameter for non-static method calls
1169
+ * @param {string} representation is the representation to use to interpret the parameters
1170
+ */
1171
+ async _makeInterceptableSoapCall(entitySchemaId, schema, soapCall, inputParams, outputParams, representation) {
1172
+ // Call observers and give them a chance to modify the parameters before the call is actually made
1173
+ if (!soapCall.internal) {
1174
+ await this._beforeSoapCall({
1175
+ urn: soapCall.urn,
1176
+ name: soapCall.methodName,
1177
+ }, inputParams, representation);
1178
+ }
1179
+
1180
+ // Make SOAP call
1181
+ await this._writeSoapCallParameters(entitySchemaId, schema, soapCall, inputParams, representation);
1182
+ await this._makeSoapCall(soapCall);
1183
+ await this._readSoapCallResult(entitySchemaId, schema, soapCall, outputParams, representation);
1184
+
1185
+ // Specific handling of query results
1186
+ // https://github.com/adobe/acc-js-sdk/issues/3
1187
+ if (entitySchemaId === "xtk:queryDef" && soapCall.methodName === "ExecuteQuery") {
1188
+ const returnValue = outputParams[1].value; // first parameter is the "this", second one (index 1) is the query result
1189
+ const emptyResult = Object.keys(returnValue).length == 0;
1190
+ const object = inputParams[0].value; // first parmater is the "this"
1191
+ const operation = EntityAccessor.getAttributeAsString(object, "operation");
1192
+ if (operation == "getIfExists" && emptyResult)
1193
+ outputParams[1].value = null;
1194
+ else if (operation == "select" && emptyResult) {
1195
+ const querySchemaId = EntityAccessor.getAttributeAsString(object, "schema");
1196
+ const index = querySchemaId.indexOf(':');
1197
+ const querySchemaName = querySchemaId.substr(index + 1);
1198
+ outputParams[1].value[querySchemaName] = [];
1199
+ }
1200
+ }
1201
+
1202
+ // Call observers and give them a chance to modify the results
1203
+ if (!soapCall.internal) {
1204
+ await this._afterSoapCall({
1205
+ urn: soapCall.urn,
1206
+ name: soapCall.methodName
1207
+ }, inputParams, representation, outputParams);
1208
+ }
1209
+ }
1210
+
991
1211
  /**
992
1212
  * After a SOAP method call has been prepared with '_prepareSoapCall', and parameters have been added,
993
1213
  * this function actually executes the SOAP call
@@ -1110,7 +1330,7 @@ class Client {
1110
1330
  return Promise.resolve();
1111
1331
  }
1112
1332
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
1113
- const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", false, this._connectionParameters._options.extraHttpHeaders);
1333
+ const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", true, false, this._connectionParameters._options.extraHttpHeaders);
1114
1334
  // No retry for logon SOAP methods
1115
1335
  soapCall.retry = false;
1116
1336
  if (credentials._type == "UserPassword") {
@@ -1189,7 +1409,7 @@ class Client {
1189
1409
  this.stopRefreshCaches();
1190
1410
  const credentials = this._connectionParameters._credentials;
1191
1411
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
1192
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
1412
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", true, false, this._connectionParameters._options.extraHttpHeaders);
1193
1413
  return this._makeSoapCall(soapCall).then(function() {
1194
1414
  that._sessionToken = "";
1195
1415
  that._securityToken = "";
@@ -1378,17 +1598,17 @@ class Client {
1378
1598
  * @return {XML.XtkObject} A DOM or JSON representation of the entity, or null if the entity is not found
1379
1599
  */
1380
1600
  async getEntityIfMoreRecent(entityType, fullName, representation, internal) {
1381
- const that = this;
1382
- const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal, this._connectionParameters._options.extraHttpHeaders);
1383
- soapCall.writeString("pk", entityType + "|" + fullName);
1384
- soapCall.writeString("md5", "");
1385
- soapCall.writeBoolean("mustExist", false);
1386
- return this._makeSoapCall(soapCall).then(function() {
1387
- var doc = soapCall.getNextDocument();
1388
- soapCall.checkNoMoreArgs();
1389
- doc = that._toRepresentation(doc, representation);
1390
- return doc;
1391
- });
1601
+ const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", true, internal, this._connectionParameters._options.extraHttpHeaders);
1602
+ const inputParams = [
1603
+ { name: "pk", type: "string", value: entityType + "|" + fullName },
1604
+ { name: "md5", type: "string", value: "" },
1605
+ { name: "mustExist", type: "boolean", value: false },
1606
+ ];
1607
+ const outputParams = [
1608
+ { name: "doc", type: "DOMDocument" },
1609
+ ];
1610
+ await this._makeInterceptableSoapCall("xtk:session", undefined, soapCall, inputParams, outputParams, representation);
1611
+ return outputParams[0].value;
1392
1612
  }
1393
1613
 
1394
1614
  /**
@@ -1470,14 +1690,22 @@ class Client {
1470
1690
  */
1471
1691
  async _callMethod(methodName, callContext, parameters) {
1472
1692
  const that = this;
1473
- const result = [];
1474
1693
  const schemaId = callContext.schemaId;
1475
1694
 
1476
- var schema = await that.getSchema(schemaId, "xml", true);
1695
+ var entitySchemaId = schemaId;
1696
+ if (schemaId === 'xtk:jobInterface')
1697
+ entitySchemaId = callContext.entitySchemaId;
1698
+
1699
+ // Get the schema which contains the method call. Methods of the xtk:jobInterface interface are handled specificaaly
1700
+ // because xtk:jobInterface is not actually a schema, but just an interface. In practice, it's used as an xtk template
1701
+ // rather that an xtk inheritance mechanism
1702
+ const methodSchemaId = schemaId === 'xtk:jobInterface' ? 'xtk:job' : schemaId;
1703
+ var schema = await that.getSchema(methodSchemaId, "xml", true);
1477
1704
  if (!schema)
1478
1705
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Schema '${schemaId}' not found`);
1479
- var schemaName = schema.getAttribute("name");
1480
- var method = that._methodCache.get(schemaId, methodName);
1706
+
1707
+ // Lookup the method to call
1708
+ var method = that._methodCache.get(methodSchemaId, methodName);
1481
1709
  if (!method) {
1482
1710
  // first char of the method name may be lower case (ex: nms:seedMember.getAsModel) but the methodName
1483
1711
  // variable has been capitalized. Make an attempt to lookup method name without capitalisation
@@ -1487,14 +1715,18 @@ class Client {
1487
1715
  }
1488
1716
  if (!method) {
1489
1717
  this._methodCache.put(schema);
1490
- method = that._methodCache.get(schemaId, methodName);
1718
+ method = that._methodCache.get(methodSchemaId, methodName);
1491
1719
  }
1492
1720
  if (!method)
1493
1721
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Method '${methodName}' of schema '${schemaId}' not found`);
1494
- // console.log(method.toXMLString());
1495
1722
 
1496
- var urn = that._methodCache.getSoapUrn(schemaId, methodName);
1497
- var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers, callContext.pushDownOptions);
1723
+ // Compute the SOAP URN. Again, specically handle xtk:jobInterface as it's not a real schema. The actual entity schema
1724
+ // would be available as the entitySchemaId property of the callContext
1725
+ var urn = schemaId !== 'xtk:jobInterface' ? that._methodCache.getSoapUrn(schemaId, methodName)
1726
+ : `xtk:jobInterface|${entitySchemaId}`;
1727
+
1728
+ const isStatic = DomUtil.getAttributeAsBoolean(method, "static");
1729
+ var soapCall = that._prepareSoapCall(urn, methodName, isStatic, false, callContext.headers, callContext.pushDownOptions);
1498
1730
 
1499
1731
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1500
1732
  // the actual list of parameters
@@ -1504,189 +1736,76 @@ class Client {
1504
1736
  if (isfunc)
1505
1737
  parameters = parameters[0](method, callContext);
1506
1738
 
1507
- const isStatic = DomUtil.getAttributeAsBoolean(method, "static");
1508
- var object = callContext.object;
1739
+ // Create input and output parameters arrays. Each array will contain elements for the corresponding parameter name, type and value
1740
+ const inputParams = [];
1741
+ const outputParams = [];
1742
+
1743
+ // For non static methods, the first input and the first output parameters represent the entity itself. The name of the corresponding
1744
+ // parameter is set the the entity schema name.
1509
1745
  if (!isStatic) {
1510
- if (!object)
1746
+ var schemaName = entitySchemaId.substring(entitySchemaId.indexOf(':') + 1);
1747
+ if (!callContext.object)
1511
1748
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Cannot call non-static method '${methodName}' of schema '${schemaId}' : no object was specified`);
1512
-
1513
- const rootName = schemaId.substr(schemaId.indexOf(':') + 1);
1514
- object = that._fromRepresentation(rootName, object, callContext.representation);
1515
- // The xtk:persist#NewInstance requires a xtkschema attribute which we can compute here
1516
- // Actually, we're always adding it, for all non-static methods
1517
- const xmlRoot = object.nodeType === 9 ? object.documentElement : object;
1518
- if (!xmlRoot.hasAttribute("xtkschema"))
1519
- xmlRoot.setAttribute("xtkschema", schemaId);
1520
- soapCall.writeDocument("document", object);
1749
+ inputParams.push({
1750
+ name: schemaName,
1751
+ type: "DOMDocument",
1752
+ value: callContext.object
1753
+ });
1754
+ outputParams.push({
1755
+ name: schemaName,
1756
+ type: "DOMDocument"
1757
+ });
1521
1758
  }
1759
+
1760
+ // Traverse the <parameters> object and create the corresponding parameter objects
1522
1761
  const parametersIsArray = (typeof parameters == "object") && parameters.length;
1523
1762
  const params = DomUtil.getFirstChildElement(method, "parameters");
1524
1763
  if (params) {
1525
1764
  var param = DomUtil.getFirstChildElement(params, "param");
1526
1765
  var paramIndex = 0;
1527
1766
  while (param) {
1528
- const inout = DomUtil.getAttributeAsString(param, "inout");
1767
+ const inout = DomUtil.getAttributeAsString(param, "inout");
1768
+ const type = DomUtil.getAttributeAsString(param, "type");
1769
+ const paramName = DomUtil.getAttributeAsString(param, "name");
1529
1770
  if (!inout || inout=="in") {
1530
- const type = DomUtil.getAttributeAsString(param, "type");
1531
- const paramName = DomUtil.getAttributeAsString(param, "name");
1532
1771
  let paramValue = parametersIsArray ? parameters[paramIndex] : parameters;
1772
+ const inputParam = {
1773
+ name: paramName,
1774
+ type: type,
1775
+ value: paramValue
1776
+ };
1777
+ inputParams.push(inputParam);
1533
1778
  paramIndex = paramIndex + 1;
1534
- if (type == "string")
1535
- soapCall.writeString(paramName, XtkCaster.asString(paramValue));
1536
- else if (type == "primarykey")
1537
- soapCall.writeString(paramName, XtkCaster.asString(paramValue));
1538
- else if (type == "boolean")
1539
- soapCall.writeBoolean(paramName, XtkCaster.asBoolean(paramValue));
1540
- else if (type == "byte")
1541
- soapCall.writeByte(paramName, XtkCaster.asByte(paramValue));
1542
- else if (type == "short")
1543
- soapCall.writeShort(paramName, XtkCaster.asShort(paramValue));
1544
- else if (type == "int")
1545
- soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1546
- else if (type == "long")
1547
- soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1548
- else if (type == "int64")
1549
- soapCall.writeInt64(paramName, XtkCaster.asInt64(paramValue));
1550
- else if (type == "datetime")
1551
- soapCall.writeTimestamp(paramName, XtkCaster.asTimestamp(paramValue));
1552
- else if (type == "date")
1553
- soapCall.writeDate(paramName, XtkCaster.asDate(paramValue));
1554
- else if (type == "DOMDocument" || type == "DOMElement") {
1555
- var docName = undefined;
1556
- let representation = callContext.representation;
1557
- if (paramValue.__xtkProxy) {
1558
- // A xtk proxy object is passed as a parameter. The call context contains the schema so we
1559
- // can use it to determine the XML document root (docName)
1560
- const paramValueContext = paramValue["."];
1561
- paramValue = paramValueContext.object;
1562
- const xtkschema = paramValueContext.schemaId;
1563
- const index = xtkschema.indexOf(":");
1564
- docName = xtkschema.substring(index+1);
1565
- representation = paramValueContext.representation; // xtk proxy may have it's own representation
1566
- }
1567
- else {
1568
- // Hack for workflow API. The C++ code checks that the name of the XML element is <variables>. When
1569
- // using xml representation at the SDK level, it's ok since the SDK caller will set that. But this does
1570
- // not work when using "BadgerFish" representation where we do not know the root element name.
1571
- if (schemaId == "xtk:workflow" && methodName == "StartWithParameters" && paramName == "parameters")
1572
- docName = "variables";
1573
- if (schemaId == "nms:rtEvent" && methodName == "PushEvent")
1574
- docName = "rtEvent";
1575
- // Try to guess the document name. This is usually available in the xtkschema attribute
1576
- var xtkschema = EntityAccessor.getAttributeAsString(paramValue, "xtkschema");
1577
- if (!xtkschema) xtkschema = paramValue["@xtkschema"];
1578
- if (xtkschema) {
1579
- const index = xtkschema.indexOf(":");
1580
- docName = xtkschema.substring(index+1);
1581
- }
1582
- if (!docName) docName = paramName; // Use te parameter name as the XML root element
1583
- }
1584
- var xmlValue = that._fromRepresentation(docName, paramValue, representation);
1585
- if (type == "DOMDocument")
1586
- soapCall.writeDocument(paramName, xmlValue);
1587
- else
1588
- soapCall.writeElement(paramName, xmlValue);
1589
- }
1590
- else
1591
- throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}`);
1779
+ }
1780
+ else if (inout=="out") {
1781
+ outputParams.push({
1782
+ name: paramName,
1783
+ type: type,
1784
+ });
1785
+ }
1786
+ else {
1787
+ throw CampaignException.BAD_PARAMETER("inout", inout, `Parameter '${paramName}' of schema '${entitySchemaId}' is not correctly defined as an input or output parameter`);
1592
1788
  }
1593
1789
  param = DomUtil.getNextSiblingElement(param, "param");
1594
1790
  }
1595
1791
  }
1596
1792
 
1597
- return that._makeSoapCall(soapCall).then(function() {
1598
- if (!isStatic) {
1599
- // Non static methods, such as xtk:query#SelectAll return a element named "entity" which is the object itself on which
1600
- // the method is called. This is the new version of the object (in XML form)
1601
- const entity = soapCall.getEntity();
1602
- if (entity) {
1603
- callContext.object = that._toRepresentation(entity, callContext.representation);
1604
- }
1605
- }
1606
-
1607
- if (params) {
1608
- var param = DomUtil.getFirstChildElement(params, "param");
1609
- while (param) {
1610
- const inout = DomUtil.getAttributeAsString(param, "inout");
1611
- if (inout=="out") {
1612
- const type = DomUtil.getAttributeAsString(param, "type");
1613
- const paramName = DomUtil.getAttributeAsString(param, "name");
1614
- var returnValue;
1615
- if (type == "string")
1616
- returnValue = soapCall.getNextString();
1617
- else if (type == "primarykey")
1618
- returnValue = soapCall.getNextPrimaryKey();
1619
- else if (type == "boolean")
1620
- returnValue = soapCall.getNextBoolean();
1621
- else if (type == "byte")
1622
- returnValue = soapCall.getNextByte();
1623
- else if (type == "short")
1624
- returnValue = soapCall.getNextShort();
1625
- else if (type == "long")
1626
- returnValue = soapCall.getNextLong();
1627
- else if (type == "int64")
1628
- // int64 are represented as strings to make sure no precision is lost
1629
- returnValue = soapCall.getNextInt64();
1630
- else if (type == "datetime")
1631
- returnValue = soapCall.getNextDateTime();
1632
- else if (type == "date")
1633
- returnValue = soapCall.getNextDate();
1634
- else if (type == "DOMDocument") {
1635
- returnValue = soapCall.getNextDocument();
1636
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1637
- if (schemaId === "xtk:queryDef" && methodName === "ExecuteQuery" && paramName === "output") {
1638
- // https://github.com/adobe/acc-js-sdk/issues/3
1639
- // Check if query operation is "getIfExists". The "object" variable at this point
1640
- // is always an XML, regardless of the xml/json representation
1641
- const objectRoot = object.documentElement;
1642
- const emptyResult = Object.keys(returnValue).length == 0;
1643
- var operation = DomUtil.getAttributeAsString(objectRoot, "operation");
1644
- if (operation == "getIfExists" && emptyResult)
1645
- returnValue = null;
1646
- if (operation == "select" && emptyResult) {
1647
- const querySchemaId = DomUtil.getAttributeAsString(objectRoot, "schema");
1648
- const index = querySchemaId.indexOf(':');
1649
- const querySchemaName = querySchemaId.substr(index + 1);
1650
- returnValue[querySchemaName] = [];
1651
- }
1652
- }
1653
- }
1654
- else if (type == "DOMElement") {
1655
- returnValue = soapCall.getNextElement();
1656
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1657
- }
1658
- else {
1659
- // type can reference a schema element. The naming convension is that the type name
1660
- // is {schemaName}{elementNameCamelCase}. For instance, the type "sessionUserInfo"
1661
- // matches the "userInfo" element of the "xtkSession" schema
1662
- let element;
1663
- if (type.substr(0, schemaName.length) == schemaName) {
1664
- const shortTypeName = type.substr(schemaName.length, 1).toLowerCase() + type.substr(schemaName.length + 1);
1665
- element = DomUtil.getFirstChildElement(schema, "element");
1666
- while (element) {
1667
- if (element.getAttribute("name") == shortTypeName) {
1668
- // Type found in schema: Process as a DOM element
1669
- returnValue = soapCall.getNextElement();
1670
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1671
- break;
1672
- }
1673
- element = DomUtil.getNextSiblingElement(element, "element");
1674
- }
1793
+ // Make the SOAP call
1794
+ await this._makeInterceptableSoapCall(entitySchemaId, schema, soapCall, inputParams, outputParams, callContext.representation);
1795
+
1796
+ // Simplify the result when there's 0 or 1 return value
1797
+ if (!isStatic) {
1798
+ const newObject = outputParams.shift().value;
1799
+ if (newObject) callContext.object = newObject;
1800
+ }
1801
+ if (outputParams.length == 0) return null;
1802
+ if (outputParams.length == 1) return outputParams[0].value;
1803
+ const result = [];
1804
+ for (var i=0; i<outputParams.length; i++) {
1805
+ result.push(outputParams[i].value);
1806
+ }
1807
+ return result;
1675
1808
 
1676
- }
1677
- if (!element)
1678
- throw CampaignException.UNEXPECTED_SOAP_RESPONSE(soapCall, `Unsupported return type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}'`);
1679
- }
1680
- result.push(returnValue);
1681
- }
1682
- param = DomUtil.getNextSiblingElement(param, "param");
1683
- }
1684
- }
1685
- soapCall.checkNoMoreArgs();
1686
- if (result.length == 0) return null;
1687
- if (result.length == 1) return result[0];
1688
- return result;
1689
- });
1690
1809
  }
1691
1810
 
1692
1811
  async _makeHttpCall(request) {
@@ -1868,6 +1987,56 @@ class Client {
1868
1987
  const result = this._toRepresentation(doc);
1869
1988
  return result;
1870
1989
  }
1990
+
1991
+ /**
1992
+ * Creates a Job object which can be used to submit jobs, retrieve status, logs and progress, etc.
1993
+ * @param {Campaign.XtkSoapCallSpec} soapCallSpec the definition of the SOAP call
1994
+ * @returns {Campaign.XtkJobInterface} a job
1995
+ */
1996
+ jobInterface(soapCallSpec) {
1997
+ return new XtkJobInterface(this, soapCallSpec);
1998
+ }
1999
+
2000
+ // Calls the beforeSoapCall method on all observers which have this method. Ignore any exception
2001
+ // that may occur in the callbacks. This function does not return anything but may modify the object
2002
+ // or parameters before the SOAP call is performed
2003
+ // @param {*} method is an object decribing the method
2004
+ // @param {Array<*>} inputParameters is an array containing the method parameters
2005
+ // @param {string} representation is the representation (SimpleJson, xml, etc.) used for this method and in which the object and parameters are set
2006
+ async _beforeSoapCall(method, inputParameters, representation) {
2007
+ if (!representation) representation = this._representation;
2008
+ for (const observer of this._observers) {
2009
+ if (observer.beforeSoapCall) {
2010
+ try {
2011
+ await observer.beforeSoapCall(method, inputParameters, representation);
2012
+ }
2013
+ catch (any) {
2014
+ // Ignore errors occuring in observers
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+
2020
+ // Calls the afterSoapCall method on all observers which have this method. Ignore any exception
2021
+ // that may occur in the callbacks. This function does not return anything but may modify the return
2022
+ // value of a SOAP call bedore it is returned to the called
2023
+ // @param {*} method is an object decribing the method
2024
+ // @param {Array<*>} inputParameters is an array containing the method parameters
2025
+ // @param {string} representation is the representation (SimpleJson, xml, etc.) used for this method and in which the object and parameters are set
2026
+ // @param {Array<*>} outputParameters an array (possibly) empty of the values returned by the SOAP call
2027
+ async _afterSoapCall(method, inputParameters, representation, outputParameters) {
2028
+ if (!representation) representation = this._representation;
2029
+ for (const observer of this._observers) {
2030
+ if (observer.afterSoapCall) {
2031
+ try {
2032
+ await observer.afterSoapCall(method, inputParameters, representation, outputParameters);
2033
+ }
2034
+ catch (any) {
2035
+ // Ignore errors occuring in observers
2036
+ }
2037
+ }
2038
+ }
2039
+ }
1871
2040
  }
1872
2041
 
1873
2042