-
Notifications
You must be signed in to change notification settings - Fork 16
/
SOAPClient.js
355 lines (312 loc) · 13.2 KB
/
SOAPClient.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
//*<JasobNoObfs>*/
/*
(c) Copyright 2013 iOpus Software GmbH - http://www.iopus.com
*/
/*</JasobNoObfs>*/
var EXPORTED_SYMBOLS = ["SOAPClient"];
var SOAPClient = (function () {
var WSDL_cache = new Object();
var xmlns_schema="http://www.w3.org/2001/XMLSchema";
var xmlns_wsdl="http://schemas.xmlsoap.org/wsdl/";
function WSDLObject(url, xmlDoc) {
this.url = url;
this.doc = xmlDoc;
// assume that all the namespace definitions are provided
// in document element
this.nsTable = new Object();
this.reverseNsTable = new Object();
var atts = this.doc.documentElement.attributes;
for (var i = 0; i < atts.length; i++) {
if (/^xmlns:(.*)$/.test(atts[i].name)) {
this.nsTable[RegExp.$1] = atts[i].value;
this.reverseNsTable[atts[i].value] = RegExp.$1;
}
}
// query method names
this.methods = new Object();
var wsdl_prefix = this.reverseNsTable[xmlns_wsdl] ?
this.reverseNsTable[xmlns_wsdl]+":" : "";
var operations = this.doc.evaluate(
"//"+wsdl_prefix+"portType/"+wsdl_prefix+"operation",
this.doc, this.resolveNS.bind(this),
// XPathResult.ORDERED_NODE_ITERATOR_TYPE
5,
null
);
var op = null;
while (op = operations.iterateNext()) {
if (!op.hasAttribute('name'))
continue; // unlikely but for peace of mind
var op_name = op.getAttribute('name');
this.methods[op_name] = {};
// assume that the operation contains input/output specifiers which
// is not always true as they can be specified in bindings
var input_tagName = wsdl_prefix+"input";
var output_tagName = wsdl_prefix+"output";
for(var i = 0; i < op.children.length; i++) {
var tagName = op.children[i].tagName;
if (tagName == input_tagName) {
if (op.children[i].hasAttribute('message')) {
var msg_name = op.children[i].getAttribute('message');
this.methods[op_name].input = {
typeObject: this.getType(msg_name)
};
}
} else if (tagName == output_tagName) {
if (op.children[i].hasAttribute('message')) {
var msg_name = op.children[i].getAttribute('message');
this.methods[op_name].output = {
typeObject: this.getType(msg_name)
};
}
}
}
}
// __loginf("WSDLObject created, methods="+this.methods.toSource());
};
WSDLObject.prototype.getType = function(messageName) {
var schema_prefix = this.reverseNsTable[xmlns_schema] ?
this.reverseNsTable[xmlns_schema]+":" : "";
var wsdl_prefix = this.reverseNsTable[xmlns_wsdl] ?
this.reverseNsTable[xmlns_wsdl]+":" : "";
// TODO: Here we assume that <wsdl:message>/<wsdl:part> refer to
// Schema element containing type definition but the types can also
// be defined using one or more <wsdl:part> elements with 'type'
// attribute. We ignore such case.
// NOTE: That part I don't really understand. operations in portType
// refers to messages using prefixed names. In order to find
// corresponding message I have to remove the prefix because
// <wsdl:message> 'name' attributes do not use prefixes
var msg_name = messageName.replace(/^\w+:/, "");
var part_element = this.doc.evaluate(
"//"+wsdl_prefix+"message[@name=\""+msg_name+"\"]/"+
wsdl_prefix+"part",
this.doc, this.resolveNS.bind(this),
// XPathResult.FIRST_ORDERED_NODE_TYPE
9, null
).singleNodeValue;
if (!part_element) {
throw new Error("No type definition for "+messageName);
}
// see above for that .replace
var el_name = part_element.getAttribute("element").replace(/^\w+:/, "");
var schema_element = this.doc.evaluate(
"//"+schema_prefix+"element[@name=\""+el_name+"\"]",
this.doc, this.resolveNS.bind(this),
// XPathResult.FIRST_ORDERED_NODE_TYPE
9, null
).singleNodeValue;
if (!schema_element) {
throw new Error("No type definition for "+messageName);
}
// NOTE: we use very simplistic approach here assuming that element is
// of complex type consisting only of simple types and has no
// additional complex members
var typeObject = {};
var nodes = this.doc.evaluate(
"./"+schema_prefix+"complexType/"+schema_prefix+"sequence/"+
schema_prefix+"element",
schema_element, this.resolveNS.bind(this),
/* XPathResult.ORDERED_NODE_ITERATOR_TYPE */ 5, null
)
var n = null;
while(n = nodes.iterateNext()){
// ignore Schema restriction and any other complexities here
// just assume it is one of primitive types
typeObject[n.getAttribute('name')] = {
type: n.getAttribute('type').replace(
new RegExp("^"+schema_prefix), ""
)
};
}
return typeObject;
};
WSDLObject.prototype.resolveNS = function(ns) {
var uri = this.nsTable[ns];
if (!uri)
throw Error("Unable to resolve namespace "+ns);
return uri;
};
// assume that args is a plain JS object
WSDLObject.prototype.argsToXML = function(method, args) {
// __loginf("argsToXML, method="+method+", args="+args.toSource());
var m = this.methods[method];
if (!m || !m.input || !m.input.typeObject) {
throw new Error("Input type not specified for method "+method);
}
var type_object = m.input.typeObject;
var s = "";
// TODO: from all the variety of XML Schema types
// we use only three (which maybe too much of simplification)
// __loginf("input type object="+type_object.toSource());
var tns = this.doc.documentElement.getAttribute("targetNamespace");
var prefix = this.reverseNsTable[tns] ?
this.reverseNsTable[tns]+":" : "";
for (var x in args) {
if (type_object[x].type == "string" ) {
s += "<"+prefix+x+">"+
args[x].replace(/&/g, "&").
replace(/</g, "<").
replace(/>/g, ">")+
"</"+prefix+x+">";
} else if (type_object[x].type == "boolean") {
s += "<"+prefix+x+">"+
(args[x] ? "true" : "false")+
"</"+prefix+x+">";
} else if (type_object[x].type == "integer") {
s += "<"+prefix+x+">"+
args[x].toString()+
"</"+prefix+x+">";
} else {
throw new Error("Unsupported type "+type_object[x]);
}
}
//__loginf("argsToXML result="+s);
return s;
};
WSDLObject.prototype.makeSoapEnvelope = function(method, args) {
var targetNamespace = this.doc.documentElement.
getAttribute("targetNamespace");
var env = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
"<soap-env:Envelope"+
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""+
" xmlns:soap-env=\"http://www.w3.org/2003/05/soap-envelope\"";
for (var x in this.nsTable) {
env += " xmlns:"+x+"=\""+this.nsTable[x]+"\"";
}
env += ">";
var prefix = this.reverseNsTable[targetNamespace] ?
this.reverseNsTable[targetNamespace]+":" : "";
var suffix = this.reverseNsTable[targetNamespace] ?
":"+this.reverseNsTable[targetNamespace] : "";
env += "<soap-env:Body>"+
"<"+prefix+method+" xmlns"+suffix+"=\""+targetNamespace+"\">"+
this.argsToXML(method, args)+
"</"+prefix+method+"></soap-env:Body>"+
"</soap-env:Envelope>";
return env;
};
WSDLObject.prototype.parseRetObject = function(method, xml) {
var m = this.methods[method];
if (!m || !m.output)
return null; // no response is assumed
var type_object = m.output.typeObject;
var rv = {};
// var nsTable = new Object();
// var reverseNsTable = new Object();
// var atts = xml.documentElement.attributes;
// for (var i = 0; i < atts.length; i++) {
// if (/^xmlns:(.*)$/.test(atts[i].name)) {
// nsTable[RegExp.$1] = atts[i].value;
// reverseNsTable[atts[i].value] = RegExp.$1;
// }
// }
// var resolveNS = function(prefix) {
// return nsTable[prefix];
// };
var tns = this.doc.documentElement.getAttribute("targetNamespace");
for (var x in type_object) {
var n = xml.getElementsByTagNameNS(tns, x);
if (!n.length) {
throw new Error("No value for parameter "+x+" in response");
}
var param = n[0];
var value = param.textContent;
if (type_object[x].type == "string") {
rv[x] = value;
} else if (type_object[x].type == "boolean") {
rv[x] = /^\s*true\s*$/.test(value);
} else if (type_object[x].type == "integer") {
rv[x] = Number(value);
} else {
throw new Error("Data type "+type_object[x].type+
" is not supported");
}
}
// __loginf("return value="+rv.toSource());
return rv;
};
WSDLObject.prototype.invoke = function(method, args, callback) {
// console.log("WSDLObject.invoke, method="+method+", args="+
// JSON.stringify(args));
try {
var m = this.methods[method];
if (!m)
throw Error("No "+method+" method found");
var req = new XMLHttpRequest();
req.open('POST', this.url, true);
var self = this;
req.onreadystatechange = function() {
if (req.readyState == 4) {
if(req.status == 200) {
try {
// console.log("response="+req.responseText);
var rv = self.parseRetObject(
method, req.responseXML
);
callback(rv);
} catch(e) {
callback(null, e);
}
} else {
var err = new Error("Method "+method+" call failed"+
", status: "+req.statusText+
" ("+req.status+")");
callback(null, err);
}
}
};
var targetNamespace = this.doc.documentElement.
getAttribute("targetNamespace");
req.setRequestHeader("SOAPAction", targetNamespace+method);
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
var env = this.makeSoapEnvelope(method, args);
// __loginf("WSDLObject.invoke request="+env);
req.send(env);
} catch(e) {
callback(null, e);
}
};
function retrieveWSDL(ws_url, callback) {
var url = ws_url;
if (!/\?WSDL$/.test(url))
url += "?WSDL";
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function() {
if (req.readyState == 4) {
if(req.status == 200) {
try {
var wsdl = new WSDLObject(ws_url, req.responseXML);
WSDL_cache[ws_url] = wsdl;
callback(wsdl);
} catch(e) {
callback(null, e);
}
} else {
var err = new Error("Request failed status: "+
req.statusText+" ("+req.status+")");
callback(null, err);
}
}
};
req.send(null);
}
function SOAPClientImp(){}
SOAPClientImp.prototype.invoke = function(ws_url, method, args, callback) {
var wsdl = WSDL_cache[ws_url];
if (!wsdl) {
retrieveWSDL(ws_url, function(_wsdl, err) {
if (!_wsdl && err) {
callback(null, err);
} else {
_wsdl.invoke(method, args, callback);
}
});
} else {
wsdl.invoke(method, args, callback);
}
};
return new SOAPClientImp();
}) ();