@adobe/acc-js-sdk 1.1.16 → 1.1.18

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
@@ -61,6 +61,13 @@ const qsStringify = require('qs-stringify');
61
61
  * @memberOf Campaign
62
62
  */
63
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
+ */
64
71
 
65
72
  /**
66
73
  * Java Script Proxy handler for an XTK object. An XTK object is one constructed with the following syntax:
@@ -640,6 +647,15 @@ class Client {
640
647
  if (instanceKey.startsWith("https://")) instanceKey = instanceKey.substr(8);
641
648
  const rootKey = `acc.js.sdk.${sdk.getSDKVersion().version}.${instanceKey}.cache`;
642
649
 
650
+ // Clear storage cache if the sdk versions or the instances are different
651
+ if (this._storage && typeof this._storage.removeItem === 'function') {
652
+ for (let key in this._storage) {
653
+ if (key.startsWith("acc.js.sdk.") && !key.startsWith(rootKey)) {
654
+ this._storage.removeItem(key);
655
+ }
656
+ }
657
+ }
658
+
643
659
  this._entityCache = new XtkEntityCache(this._storage, `${rootKey}.XtkEntityCache`, connectionParameters._options.entityCacheTTL);
644
660
  this._entityCacheRefresher = new CacheRefresher(this._entityCache, this, "xtk:schema", `${rootKey}.XtkEntityCache`);
645
661
  this._methodCache = new MethodCache(this._storage, `${rootKey}.MethodCache`, connectionParameters._options.methodCacheTTL);
@@ -919,17 +935,19 @@ class Client {
919
935
  * @private
920
936
  * @param {string} urn is the API name space, usually the schema. For instance xtk:session
921
937
  * @param {string} method is the method to call, for instance Logon
938
+ * @param {boolean} isStatic is a boolean indicating if the method is static or not
922
939
  * @param {boolean} internal is a boolean indicating whether the SOAP call is performed by the SDK (internal = true) or on behalf of a user
923
940
  * @return {SOAP.SoapMethodCall} a SoapMethodCall which have been initialized with security tokens... and to which the method
924
941
  * parameters should be set
925
942
  */
926
- _prepareSoapCall(urn, method, internal, extraHttpHeaders, pushDownOptions) {
943
+ _prepareSoapCall(urn, method, isStatic, internal, extraHttpHeaders, pushDownOptions) {
927
944
  const soapCall = new SoapMethodCall(this._transport, urn, method,
928
945
  this._sessionToken, this._securityToken,
929
946
  this._getUserAgentString(),
930
947
  Object.assign({}, this._connectionParameters._options, pushDownOptions),
931
948
  extraHttpHeaders);
932
949
  soapCall.internal = !!internal;
950
+ soapCall.isStatic = isStatic;
933
951
  return soapCall;
934
952
  }
935
953
 
@@ -980,6 +998,225 @@ class Client {
980
998
  _soapEndPoint() {
981
999
  return this._connectionParameters._endpoint + "/nl/jsp/soaprouter.jsp";
982
1000
  }
1001
+
1002
+ // Serializes parameters for a SOAP call
1003
+ // @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
1004
+ // @param {DOMDocument} schema is the XML schema of the method call
1005
+ // @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
1006
+ // @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
1007
+ // @param {string} representation is the representation to use to interpret the input parameters
1008
+ async _writeSoapCallParameters(entitySchemaId, schema, soapCall, inputParameters, representation) {
1009
+ const methodName = soapCall.methodName;
1010
+ var isThisParam = !soapCall.isStatic;
1011
+
1012
+ for (const ip of inputParameters) {
1013
+ const type = ip.type;
1014
+ const paramName = ip.name;
1015
+ var paramValue = ip.value;
1016
+
1017
+ if (type == "string")
1018
+ soapCall.writeString(ip.name, XtkCaster.asString(ip.value));
1019
+ else if (type == "primarykey")
1020
+ soapCall.writeString(ip.name, XtkCaster.asString(ip.value));
1021
+ else if (type == "boolean")
1022
+ soapCall.writeBoolean(ip.name, XtkCaster.asBoolean(ip.value));
1023
+ else if (type == "byte")
1024
+ soapCall.writeByte(ip.name, XtkCaster.asByte(ip.value));
1025
+ else if (type == "short")
1026
+ soapCall.writeShort(ip.name, XtkCaster.asShort(ip.value));
1027
+ else if (type == "int")
1028
+ soapCall.writeLong(ip.name, XtkCaster.asLong(ip.value));
1029
+ else if (type == "long")
1030
+ soapCall.writeLong(ip.name, XtkCaster.asLong(ip.value));
1031
+ else if (type == "int64")
1032
+ soapCall.writeInt64(ip.name, XtkCaster.asInt64(ip.value));
1033
+ else if (type == "datetime")
1034
+ soapCall.writeTimestamp(ip.name, XtkCaster.asTimestamp(ip.value));
1035
+ else if (type == "date")
1036
+ soapCall.writeDate(ip.name, XtkCaster.asDate(ip.value));
1037
+ else if (type == "DOMDocument" || type == "DOMElement") {
1038
+ var docName = undefined;
1039
+ let paramRepresentation = representation;
1040
+ if (paramValue.__xtkProxy) {
1041
+ // A xtk proxy object is passed as a parameter. The call context contains the schema so we
1042
+ // can use it to determine the XML document root (docName)
1043
+ const paramValueContext = paramValue["."];
1044
+ paramValue = paramValueContext.object;
1045
+ const xtkschema = paramValueContext.schemaId;
1046
+ const index = xtkschema.indexOf(":");
1047
+ docName = xtkschema.substring(index+1);
1048
+ paramRepresentation = paramValueContext.representation; // xtk proxy may have it's own representation
1049
+ }
1050
+ else {
1051
+ // Hack for workflow API. The C++ code checks that the name of the XML element is <variables>. When
1052
+ // using xml representation at the SDK level, it's ok since the SDK caller will set that. But this does
1053
+ // not work when using "BadgerFish" representation where we do not know the root element name.
1054
+ if (entitySchemaId == "xtk:workflow" && methodName == "StartWithParameters" && paramName == "parameters")
1055
+ docName = "variables";
1056
+ if (entitySchemaId == "nms:rtEvent" && methodName == "PushEvent")
1057
+ docName = "rtEvent";
1058
+ // Try to guess the document name. This is usually available in the xtkschema attribute
1059
+ var xtkschema = EntityAccessor.getAttributeAsString(paramValue, "xtkschema");
1060
+ if (!xtkschema) xtkschema = paramValue["@xtkschema"];
1061
+ if (xtkschema) {
1062
+ const index = xtkschema.indexOf(":");
1063
+ docName = xtkschema.substring(index+1);
1064
+ }
1065
+ if (!docName) docName = paramName; // Use te parameter name as the XML root element
1066
+ }
1067
+ var xmlValue = this._fromRepresentation(docName, paramValue, paramRepresentation || representation);
1068
+ if (type == "DOMDocument") {
1069
+ if (isThisParam) {
1070
+ isThisParam = false;
1071
+ // The xtk:persist#NewInstance requires a xtkschema attribute which we can compute here
1072
+ // Actually, we're always adding it, for all non-static methods
1073
+ const xmlRoot = xmlValue.nodeType === 9 ? xmlValue.documentElement : xmlValue;
1074
+ if (!xmlRoot.hasAttribute("xtkschema"))
1075
+ xmlRoot.setAttribute("xtkschema", entitySchemaId);
1076
+ soapCall.writeDocument("document", xmlValue);
1077
+ }
1078
+ else
1079
+ soapCall.writeDocument(paramName, xmlValue);
1080
+ }
1081
+ else
1082
+ soapCall.writeElement(paramName, xmlValue);
1083
+ }
1084
+ else
1085
+ throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${entitySchemaId}`);
1086
+ }
1087
+ }
1088
+
1089
+ // Deserializes results for a SOAP call
1090
+ // @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
1091
+ // @param {DOMDocument} schema is the XML schema of the method call
1092
+ // @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
1093
+ // @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
1094
+ // @param {string} representation is the representation to use to interpret the parameters
1095
+ async _readSoapCallResult(entitySchemaId, schema, soapCall, outputParameters, representation) {
1096
+ const methodName = soapCall.methodName;
1097
+ var isThisParam = !soapCall.isStatic;
1098
+ for (const op of outputParameters) {
1099
+ const type = op.type;
1100
+ const paramName = op.name;
1101
+ var returnValue = op.value;
1102
+
1103
+ if (isThisParam) {
1104
+ isThisParam = false;
1105
+ // Non static methods, such as xtk:query#SelectAll return a element named "entity" which is the object itself on which
1106
+ // the method is called. This is the new version of the object (in XML form)
1107
+ const entity = soapCall.getEntity();
1108
+ if (entity)
1109
+ returnValue = this._toRepresentation(entity, representation);
1110
+ }
1111
+ else if (type == "string")
1112
+ returnValue = soapCall.getNextString();
1113
+ else if (type == "primarykey")
1114
+ returnValue = soapCall.getNextPrimaryKey();
1115
+ else if (type == "boolean")
1116
+ returnValue = soapCall.getNextBoolean();
1117
+ else if (type == "byte")
1118
+ returnValue = soapCall.getNextByte();
1119
+ else if (type == "short")
1120
+ returnValue = soapCall.getNextShort();
1121
+ else if (type == "long")
1122
+ returnValue = soapCall.getNextLong();
1123
+ else if (type == "int64")
1124
+ // int64 are represented as strings to make sure no precision is lost
1125
+ returnValue = soapCall.getNextInt64();
1126
+ else if (type == "datetime")
1127
+ returnValue = soapCall.getNextDateTime();
1128
+ else if (type == "date")
1129
+ returnValue = soapCall.getNextDate();
1130
+ else if (type == "DOMDocument") {
1131
+ returnValue = soapCall.getNextDocument();
1132
+ returnValue = this._toRepresentation(returnValue, representation);
1133
+ }
1134
+ else if (type == "DOMElement") {
1135
+ returnValue = soapCall.getNextElement();
1136
+ returnValue = this._toRepresentation(returnValue, representation);
1137
+ }
1138
+ else {
1139
+ const schemaName = entitySchemaId.substring(entitySchemaId.indexOf(':') + 1);
1140
+ // type can reference a schema element. The naming convension is that the type name
1141
+ // is {schemaName}{elementNameCamelCase}. For instance, the type "sessionUserInfo"
1142
+ // matches the "userInfo" element of the "xtkSession" schema
1143
+ let element;
1144
+ if (type.substr(0, schemaName.length) == schemaName) {
1145
+ const shortTypeName = type.substr(schemaName.length, 1).toLowerCase() + type.substr(schemaName.length + 1);
1146
+ element = DomUtil.getFirstChildElement(schema, "element");
1147
+ while (element) {
1148
+ if (element.getAttribute("name") == shortTypeName) {
1149
+ // Type found in schema: Process as a DOM element
1150
+ returnValue = soapCall.getNextElement();
1151
+ returnValue = this._toRepresentation(returnValue, representation);
1152
+ break;
1153
+ }
1154
+ element = DomUtil.getNextSiblingElement(element, "element");
1155
+ }
1156
+
1157
+ }
1158
+ if (!element)
1159
+ throw CampaignException.UNEXPECTED_SOAP_RESPONSE(soapCall, `Unsupported return type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${entitySchemaId}'`);
1160
+ }
1161
+ op.value = returnValue;
1162
+ }
1163
+ soapCall.checkNoMoreArgs();
1164
+ }
1165
+
1166
+ /**
1167
+ * Serialize input parameters, make a SOAP call and deserialize result parameters. Calls observer when necessary.
1168
+ *
1169
+ * The inputParams and outputParams arrays contain prepopulated parameter lists with the name, type (and value for
1170
+ * input params). This function does not return anything but will populate the outputParams array with the actual result.
1171
+ *
1172
+ * @private
1173
+ * @param {string} entitySchemaId is the id of the schema of the entity underlying the SOAP call
1174
+ * @param {DOMDocument} schema is the XML schema of the method call
1175
+ * @param {SOAP.SoapMethodCall} soapCall is the SOAP call being performed
1176
+ * @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
1177
+ * @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
1178
+ * @param {string} representation is the representation to use to interpret the parameters
1179
+ */
1180
+ async _makeInterceptableSoapCall(entitySchemaId, schema, soapCall, inputParams, outputParams, representation) {
1181
+ // Call observers and give them a chance to modify the parameters before the call is actually made
1182
+ if (!soapCall.internal) {
1183
+ await this._beforeSoapCall({
1184
+ urn: soapCall.urn,
1185
+ name: soapCall.methodName,
1186
+ }, inputParams, representation);
1187
+ }
1188
+
1189
+ // Make SOAP call
1190
+ await this._writeSoapCallParameters(entitySchemaId, schema, soapCall, inputParams, representation);
1191
+ await this._makeSoapCall(soapCall);
1192
+ await this._readSoapCallResult(entitySchemaId, schema, soapCall, outputParams, representation);
1193
+
1194
+ // Specific handling of query results
1195
+ // https://github.com/adobe/acc-js-sdk/issues/3
1196
+ if (entitySchemaId === "xtk:queryDef" && soapCall.methodName === "ExecuteQuery") {
1197
+ const returnValue = outputParams[1].value; // first parameter is the "this", second one (index 1) is the query result
1198
+ const emptyResult = Object.keys(returnValue).length == 0;
1199
+ const object = inputParams[0].value; // first parmater is the "this"
1200
+ const operation = EntityAccessor.getAttributeAsString(object, "operation");
1201
+ if (operation == "getIfExists" && emptyResult)
1202
+ outputParams[1].value = null;
1203
+ else if (operation == "select" && emptyResult) {
1204
+ const querySchemaId = EntityAccessor.getAttributeAsString(object, "schema");
1205
+ const index = querySchemaId.indexOf(':');
1206
+ const querySchemaName = querySchemaId.substr(index + 1);
1207
+ outputParams[1].value[querySchemaName] = [];
1208
+ }
1209
+ }
1210
+
1211
+ // Call observers and give them a chance to modify the results
1212
+ if (!soapCall.internal) {
1213
+ await this._afterSoapCall({
1214
+ urn: soapCall.urn,
1215
+ name: soapCall.methodName
1216
+ }, inputParams, representation, outputParams);
1217
+ }
1218
+ }
1219
+
983
1220
  /**
984
1221
  * After a SOAP method call has been prepared with '_prepareSoapCall', and parameters have been added,
985
1222
  * this function actually executes the SOAP call
@@ -1102,7 +1339,7 @@ class Client {
1102
1339
  return Promise.resolve();
1103
1340
  }
1104
1341
  else if (credentials._type == "UserPassword" || credentials._type == "BearerToken") {
1105
- const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", false, this._connectionParameters._options.extraHttpHeaders);
1342
+ const soapCall = that._prepareSoapCall("xtk:session", credentials._type === "UserPassword" ? "Logon" : "BearerTokenLogon", true, false, this._connectionParameters._options.extraHttpHeaders);
1106
1343
  // No retry for logon SOAP methods
1107
1344
  soapCall.retry = false;
1108
1345
  if (credentials._type == "UserPassword") {
@@ -1181,7 +1418,7 @@ class Client {
1181
1418
  this.stopRefreshCaches();
1182
1419
  const credentials = this._connectionParameters._credentials;
1183
1420
  if (credentials._type != "SessionToken" && credentials._type != "AnonymousUser") {
1184
- var soapCall = that._prepareSoapCall("xtk:session", "Logoff", false, this._connectionParameters._options.extraHttpHeaders);
1421
+ var soapCall = that._prepareSoapCall("xtk:session", "Logoff", true, false, this._connectionParameters._options.extraHttpHeaders);
1185
1422
  return this._makeSoapCall(soapCall).then(function() {
1186
1423
  that._sessionToken = "";
1187
1424
  that._securityToken = "";
@@ -1370,17 +1607,17 @@ class Client {
1370
1607
  * @return {XML.XtkObject} A DOM or JSON representation of the entity, or null if the entity is not found
1371
1608
  */
1372
1609
  async getEntityIfMoreRecent(entityType, fullName, representation, internal) {
1373
- const that = this;
1374
- const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", internal, this._connectionParameters._options.extraHttpHeaders);
1375
- soapCall.writeString("pk", entityType + "|" + fullName);
1376
- soapCall.writeString("md5", "");
1377
- soapCall.writeBoolean("mustExist", false);
1378
- return this._makeSoapCall(soapCall).then(function() {
1379
- var doc = soapCall.getNextDocument();
1380
- soapCall.checkNoMoreArgs();
1381
- doc = that._toRepresentation(doc, representation);
1382
- return doc;
1383
- });
1610
+ const soapCall = this._prepareSoapCall("xtk:persist", "GetEntityIfMoreRecent", true, internal, this._connectionParameters._options.extraHttpHeaders);
1611
+ const inputParams = [
1612
+ { name: "pk", type: "string", value: entityType + "|" + fullName },
1613
+ { name: "md5", type: "string", value: "" },
1614
+ { name: "mustExist", type: "boolean", value: false },
1615
+ ];
1616
+ const outputParams = [
1617
+ { name: "doc", type: "DOMDocument" },
1618
+ ];
1619
+ await this._makeInterceptableSoapCall("xtk:session", undefined, soapCall, inputParams, outputParams, representation);
1620
+ return outputParams[0].value;
1384
1621
  }
1385
1622
 
1386
1623
  /**
@@ -1462,7 +1699,6 @@ class Client {
1462
1699
  */
1463
1700
  async _callMethod(methodName, callContext, parameters) {
1464
1701
  const that = this;
1465
- const result = [];
1466
1702
  const schemaId = callContext.schemaId;
1467
1703
 
1468
1704
  var entitySchemaId = schemaId;
@@ -1476,7 +1712,6 @@ class Client {
1476
1712
  var schema = await that.getSchema(methodSchemaId, "xml", true);
1477
1713
  if (!schema)
1478
1714
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Schema '${schemaId}' not found`);
1479
- var schemaName = schema.getAttribute("name");
1480
1715
 
1481
1716
  // Lookup the method to call
1482
1717
  var method = that._methodCache.get(methodSchemaId, methodName);
@@ -1499,7 +1734,8 @@ class Client {
1499
1734
  var urn = schemaId !== 'xtk:jobInterface' ? that._methodCache.getSoapUrn(schemaId, methodName)
1500
1735
  : `xtk:jobInterface|${entitySchemaId}`;
1501
1736
 
1502
- var soapCall = that._prepareSoapCall(urn, methodName, false, callContext.headers, callContext.pushDownOptions);
1737
+ const isStatic = DomUtil.getAttributeAsBoolean(method, "static");
1738
+ var soapCall = that._prepareSoapCall(urn, methodName, isStatic, false, callContext.headers, callContext.pushDownOptions);
1503
1739
 
1504
1740
  // If method is called with one parameter which is a function, then we assume it's a hook: the function will return
1505
1741
  // the actual list of parameters
@@ -1509,21 +1745,28 @@ class Client {
1509
1745
  if (isfunc)
1510
1746
  parameters = parameters[0](method, callContext);
1511
1747
 
1512
- const isStatic = DomUtil.getAttributeAsBoolean(method, "static");
1513
- var object = callContext.object;
1748
+ // Create input and output parameters arrays. Each array will contain elements for the corresponding parameter name, type and value
1749
+ const inputParams = [];
1750
+ const outputParams = [];
1751
+
1752
+ // For non static methods, the first input and the first output parameters represent the entity itself. The name of the corresponding
1753
+ // parameter is set the the entity schema name.
1514
1754
  if (!isStatic) {
1515
- if (!object)
1755
+ var schemaName = entitySchemaId.substring(entitySchemaId.indexOf(':') + 1);
1756
+ if (!callContext.object)
1516
1757
  throw CampaignException.SOAP_UNKNOWN_METHOD(schemaId, methodName, `Cannot call non-static method '${methodName}' of schema '${schemaId}' : no object was specified`);
1517
-
1518
- const rootName = entitySchemaId.substr(entitySchemaId.indexOf(':') + 1);
1519
- object = that._fromRepresentation(rootName, object, callContext.representation);
1520
- // The xtk:persist#NewInstance requires a xtkschema attribute which we can compute here
1521
- // Actually, we're always adding it, for all non-static methods
1522
- const xmlRoot = object.nodeType === 9 ? object.documentElement : object;
1523
- if (!xmlRoot.hasAttribute("xtkschema"))
1524
- xmlRoot.setAttribute("xtkschema", entitySchemaId);
1525
- soapCall.writeDocument("document", object);
1758
+ inputParams.push({
1759
+ name: schemaName,
1760
+ type: "DOMDocument",
1761
+ value: callContext.object
1762
+ });
1763
+ outputParams.push({
1764
+ name: schemaName,
1765
+ type: "DOMDocument"
1766
+ });
1526
1767
  }
1768
+
1769
+ // Traverse the <parameters> object and create the corresponding parameter objects
1527
1770
  const parametersIsArray = (typeof parameters == "object") && parameters.length;
1528
1771
  const params = DomUtil.getFirstChildElement(method, "parameters");
1529
1772
  if (params) {
@@ -1531,167 +1774,47 @@ class Client {
1531
1774
  var paramIndex = 0;
1532
1775
  while (param) {
1533
1776
  const inout = DomUtil.getAttributeAsString(param, "inout");
1777
+ const type = DomUtil.getAttributeAsString(param, "type");
1778
+ const paramName = DomUtil.getAttributeAsString(param, "name");
1534
1779
  if (!inout || inout=="in") {
1535
- const type = DomUtil.getAttributeAsString(param, "type");
1536
- const paramName = DomUtil.getAttributeAsString(param, "name");
1537
1780
  let paramValue = parametersIsArray ? parameters[paramIndex] : parameters;
1781
+ const inputParam = {
1782
+ name: paramName,
1783
+ type: type,
1784
+ value: paramValue
1785
+ };
1786
+ inputParams.push(inputParam);
1538
1787
  paramIndex = paramIndex + 1;
1539
- if (type == "string")
1540
- soapCall.writeString(paramName, XtkCaster.asString(paramValue));
1541
- else if (type == "primarykey")
1542
- soapCall.writeString(paramName, XtkCaster.asString(paramValue));
1543
- else if (type == "boolean")
1544
- soapCall.writeBoolean(paramName, XtkCaster.asBoolean(paramValue));
1545
- else if (type == "byte")
1546
- soapCall.writeByte(paramName, XtkCaster.asByte(paramValue));
1547
- else if (type == "short")
1548
- soapCall.writeShort(paramName, XtkCaster.asShort(paramValue));
1549
- else if (type == "int")
1550
- soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1551
- else if (type == "long")
1552
- soapCall.writeLong(paramName, XtkCaster.asLong(paramValue));
1553
- else if (type == "int64")
1554
- soapCall.writeInt64(paramName, XtkCaster.asInt64(paramValue));
1555
- else if (type == "datetime")
1556
- soapCall.writeTimestamp(paramName, XtkCaster.asTimestamp(paramValue));
1557
- else if (type == "date")
1558
- soapCall.writeDate(paramName, XtkCaster.asDate(paramValue));
1559
- else if (type == "DOMDocument" || type == "DOMElement") {
1560
- var docName = undefined;
1561
- let representation = callContext.representation;
1562
- if (paramValue.__xtkProxy) {
1563
- // A xtk proxy object is passed as a parameter. The call context contains the schema so we
1564
- // can use it to determine the XML document root (docName)
1565
- const paramValueContext = paramValue["."];
1566
- paramValue = paramValueContext.object;
1567
- const xtkschema = paramValueContext.schemaId;
1568
- const index = xtkschema.indexOf(":");
1569
- docName = xtkschema.substring(index+1);
1570
- representation = paramValueContext.representation; // xtk proxy may have it's own representation
1571
- }
1572
- else {
1573
- // Hack for workflow API. The C++ code checks that the name of the XML element is <variables>. When
1574
- // using xml representation at the SDK level, it's ok since the SDK caller will set that. But this does
1575
- // not work when using "BadgerFish" representation where we do not know the root element name.
1576
- if (entitySchemaId == "xtk:workflow" && methodName == "StartWithParameters" && paramName == "parameters")
1577
- docName = "variables";
1578
- if (entitySchemaId == "nms:rtEvent" && methodName == "PushEvent")
1579
- docName = "rtEvent";
1580
- // Try to guess the document name. This is usually available in the xtkschema attribute
1581
- var xtkschema = EntityAccessor.getAttributeAsString(paramValue, "xtkschema");
1582
- if (!xtkschema) xtkschema = paramValue["@xtkschema"];
1583
- if (xtkschema) {
1584
- const index = xtkschema.indexOf(":");
1585
- docName = xtkschema.substring(index+1);
1586
- }
1587
- if (!docName) docName = paramName; // Use te parameter name as the XML root element
1588
- }
1589
- var xmlValue = that._fromRepresentation(docName, paramValue, representation);
1590
- if (type == "DOMDocument")
1591
- soapCall.writeDocument(paramName, xmlValue);
1592
- else
1593
- soapCall.writeElement(paramName, xmlValue);
1594
- }
1595
- else
1596
- throw CampaignException.BAD_SOAP_PARAMETER(soapCall, paramName, paramValue, `Unsupported parameter type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}`);
1788
+ }
1789
+ else if (inout=="out") {
1790
+ outputParams.push({
1791
+ name: paramName,
1792
+ type: type,
1793
+ });
1794
+ }
1795
+ else {
1796
+ throw CampaignException.BAD_PARAMETER("inout", inout, `Parameter '${paramName}' of schema '${entitySchemaId}' is not correctly defined as an input or output parameter`);
1597
1797
  }
1598
1798
  param = DomUtil.getNextSiblingElement(param, "param");
1599
1799
  }
1600
1800
  }
1601
1801
 
1602
- return that._makeSoapCall(soapCall).then(function() {
1603
- if (!isStatic) {
1604
- // Non static methods, such as xtk:query#SelectAll return a element named "entity" which is the object itself on which
1605
- // the method is called. This is the new version of the object (in XML form)
1606
- const entity = soapCall.getEntity();
1607
- if (entity) {
1608
- callContext.object = that._toRepresentation(entity, callContext.representation);
1609
- }
1610
- }
1611
-
1612
- if (params) {
1613
- var param = DomUtil.getFirstChildElement(params, "param");
1614
- while (param) {
1615
- const inout = DomUtil.getAttributeAsString(param, "inout");
1616
- if (inout=="out") {
1617
- const type = DomUtil.getAttributeAsString(param, "type");
1618
- const paramName = DomUtil.getAttributeAsString(param, "name");
1619
- var returnValue;
1620
- if (type == "string")
1621
- returnValue = soapCall.getNextString();
1622
- else if (type == "primarykey")
1623
- returnValue = soapCall.getNextPrimaryKey();
1624
- else if (type == "boolean")
1625
- returnValue = soapCall.getNextBoolean();
1626
- else if (type == "byte")
1627
- returnValue = soapCall.getNextByte();
1628
- else if (type == "short")
1629
- returnValue = soapCall.getNextShort();
1630
- else if (type == "long")
1631
- returnValue = soapCall.getNextLong();
1632
- else if (type == "int64")
1633
- // int64 are represented as strings to make sure no precision is lost
1634
- returnValue = soapCall.getNextInt64();
1635
- else if (type == "datetime")
1636
- returnValue = soapCall.getNextDateTime();
1637
- else if (type == "date")
1638
- returnValue = soapCall.getNextDate();
1639
- else if (type == "DOMDocument") {
1640
- returnValue = soapCall.getNextDocument();
1641
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1642
- if (entitySchemaId === "xtk:queryDef" && methodName === "ExecuteQuery" && paramName === "output") {
1643
- // https://github.com/adobe/acc-js-sdk/issues/3
1644
- // Check if query operation is "getIfExists". The "object" variable at this point
1645
- // is always an XML, regardless of the xml/json representation
1646
- const objectRoot = object.documentElement;
1647
- const emptyResult = Object.keys(returnValue).length == 0;
1648
- var operation = DomUtil.getAttributeAsString(objectRoot, "operation");
1649
- if (operation == "getIfExists" && emptyResult)
1650
- returnValue = null;
1651
- if (operation == "select" && emptyResult) {
1652
- const querySchemaId = DomUtil.getAttributeAsString(objectRoot, "schema");
1653
- const index = querySchemaId.indexOf(':');
1654
- const querySchemaName = querySchemaId.substr(index + 1);
1655
- returnValue[querySchemaName] = [];
1656
- }
1657
- }
1658
- }
1659
- else if (type == "DOMElement") {
1660
- returnValue = soapCall.getNextElement();
1661
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1662
- }
1663
- else {
1664
- // type can reference a schema element. The naming convension is that the type name
1665
- // is {schemaName}{elementNameCamelCase}. For instance, the type "sessionUserInfo"
1666
- // matches the "userInfo" element of the "xtkSession" schema
1667
- let element;
1668
- if (type.substr(0, schemaName.length) == schemaName) {
1669
- const shortTypeName = type.substr(schemaName.length, 1).toLowerCase() + type.substr(schemaName.length + 1);
1670
- element = DomUtil.getFirstChildElement(schema, "element");
1671
- while (element) {
1672
- if (element.getAttribute("name") == shortTypeName) {
1673
- // Type found in schema: Process as a DOM element
1674
- returnValue = soapCall.getNextElement();
1675
- returnValue = that._toRepresentation(returnValue, callContext.representation);
1676
- break;
1677
- }
1678
- element = DomUtil.getNextSiblingElement(element, "element");
1679
- }
1802
+ // Make the SOAP call
1803
+ await this._makeInterceptableSoapCall(entitySchemaId, schema, soapCall, inputParams, outputParams, callContext.representation);
1804
+
1805
+ // Simplify the result when there's 0 or 1 return value
1806
+ if (!isStatic) {
1807
+ const newObject = outputParams.shift().value;
1808
+ if (newObject) callContext.object = newObject;
1809
+ }
1810
+ if (outputParams.length == 0) return null;
1811
+ if (outputParams.length == 1) return outputParams[0].value;
1812
+ const result = [];
1813
+ for (var i=0; i<outputParams.length; i++) {
1814
+ result.push(outputParams[i].value);
1815
+ }
1816
+ return result;
1680
1817
 
1681
- }
1682
- if (!element)
1683
- throw CampaignException.UNEXPECTED_SOAP_RESPONSE(soapCall, `Unsupported return type '${type}' for parameter '${paramName}' of method '${methodName}' of schema '${schemaId}'`);
1684
- }
1685
- result.push(returnValue);
1686
- }
1687
- param = DomUtil.getNextSiblingElement(param, "param");
1688
- }
1689
- }
1690
- soapCall.checkNoMoreArgs();
1691
- if (result.length == 0) return null;
1692
- if (result.length == 1) return result[0];
1693
- return result;
1694
- });
1695
1818
  }
1696
1819
 
1697
1820
  async _makeHttpCall(request) {
@@ -1882,6 +2005,47 @@ class Client {
1882
2005
  jobInterface(soapCallSpec) {
1883
2006
  return new XtkJobInterface(this, soapCallSpec);
1884
2007
  }
2008
+
2009
+ // Calls the beforeSoapCall method on all observers which have this method. Ignore any exception
2010
+ // that may occur in the callbacks. This function does not return anything but may modify the object
2011
+ // or parameters before the SOAP call is performed
2012
+ // @param {*} method is an object decribing the method
2013
+ // @param {Array<*>} inputParameters is an array containing the method parameters
2014
+ // @param {string} representation is the representation (SimpleJson, xml, etc.) used for this method and in which the object and parameters are set
2015
+ async _beforeSoapCall(method, inputParameters, representation) {
2016
+ if (!representation) representation = this._representation;
2017
+ for (const observer of this._observers) {
2018
+ if (observer.beforeSoapCall) {
2019
+ try {
2020
+ await observer.beforeSoapCall(method, inputParameters, representation);
2021
+ }
2022
+ catch (any) {
2023
+ // Ignore errors occuring in observers
2024
+ }
2025
+ }
2026
+ }
2027
+ }
2028
+
2029
+ // Calls the afterSoapCall method on all observers which have this method. Ignore any exception
2030
+ // that may occur in the callbacks. This function does not return anything but may modify the return
2031
+ // value of a SOAP call bedore it is returned to the called
2032
+ // @param {*} method is an object decribing the method
2033
+ // @param {Array<*>} inputParameters is an array containing the method parameters
2034
+ // @param {string} representation is the representation (SimpleJson, xml, etc.) used for this method and in which the object and parameters are set
2035
+ // @param {Array<*>} outputParameters an array (possibly) empty of the values returned by the SOAP call
2036
+ async _afterSoapCall(method, inputParameters, representation, outputParameters) {
2037
+ if (!representation) representation = this._representation;
2038
+ for (const observer of this._observers) {
2039
+ if (observer.afterSoapCall) {
2040
+ try {
2041
+ await observer.afterSoapCall(method, inputParameters, representation, outputParameters);
2042
+ }
2043
+ catch (any) {
2044
+ // Ignore errors occuring in observers
2045
+ }
2046
+ }
2047
+ }
2048
+ }
1885
2049
  }
1886
2050
 
1887
2051