Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 803 lines (695 sloc) 31.025 kB
08cee94 @kalski added soap12 capability
kalski authored
1 %%%-------------------------------------------------------------------
2 %%% Created : 29 Nov 2006 by Torbjorn Tornkvist <tobbe@tornkvist.org>
3 %%% Author : Willem de Jong (w.a.de.jong@gmail.com).
4 %%% Desc. : Common SOAP code.
5 %%%-------------------------------------------------------------------
6
7 %%% modified (WdJ, May 2007): deal with imports in the WSDL.
8 %%% modified (WdJ, August 2007): the WSDL can contain more than 1 schema
9 %%% copied from yaws_soap_lib (Kaloyan Dimitrov, February 2012): to be used for soap12 calls
10
11 -module(yaws_soap12_lib).
12
13 -export([initModel/1, initModel/2,
14 initModelFile/1,
15 config_file_xsd/0,
16 call/3, call/4, call/5, call/6,
17 call_attach/4, call_attach/5, call_attach/8,
18 write_hrl/2, write_hrl/3,
19 findHeader/2,
20 parseMessage/2,
21 makeFault/2,
22 is_wsdl/1, wsdl_model/1, wsdl_op_service/1,
23 wsdl_op_port/1, wsdl_op_operation/1,
24 wsdl_op_binding/1, wsdl_op_address/1,
25 wsdl_op_action/1, wsdl_operations/1,
26 get_operation/2
27 ]).
28
29
30 %%% For testing...
31 -export([qtest/0]).
32
33
34 -include("../include/yaws_soap.hrl").
35 -include("../include/soap-envelope.hrl").
36 -include("../include/wsdl11soap12.hrl").
37
38 -define(HTTP_REQ_TIMEOUT, 20000).
39
40 %%-define(dbg(X,Y),
41 %% error_logger:info_msg("*dbg ~p(~p): " X,
42 %% [?MODULE, ?LINE | Y])).
43 -define(dbg(X,Y), true).
44
45
46 -record(yaws_soap_config, {atts, xsd_path, user_module, wsdl_file, add_files}).
47 -record(xsd_file, {atts, name, prefix, import_specs}).
48 -record(import_specs, {atts, namespace, prefix, location}).
49
50 -define(DefaultPrefix, "p").
51
52
53 %%%
54 %%% Writes the header file (record definitions) for a WSDL file
55 %%%
56 write_hrl(WsdlURL, Output) when is_list(WsdlURL) ->
57 write_hrl(initModel(WsdlURL), Output);
58 write_hrl(#wsdl{model = Model}, Output) when is_list(Output) ->
59 erlsom:write_hrl(Model, Output).
60
61 write_hrl(WsdlURL, Output, PrefixOrOptions)
62 when is_list(WsdlURL),is_list(PrefixOrOptions) ->
63 write_hrl(initModel(WsdlURL, PrefixOrOptions), Output).
64
65
66
67 %%% For testing only...
68 qtest() ->
69 call("http://www.webservicex.net/WeatherForecast.asmx?WSDL",
70 "GetWeatherByPlaceName",
71 ["Boston"]).
72
73 %%% --------------------------------------------------------------------
74 %%% Access functions
75 %%% --------------------------------------------------------------------
76 is_wsdl(Wsdl) when is_record(Wsdl,wsdl) -> true;
77 is_wsdl(_) -> false.
78
79 wsdl_operations(#wsdl{operations = Ops}) -> Ops.
80
81 wsdl_model(#wsdl{model = Model}) -> Model.
82
83 wsdl_op_service(#operation{service = Service}) -> Service.
84
85 wsdl_op_port(#operation{port = Port}) -> Port.
86
87 wsdl_op_operation(#operation{operation = Op}) -> Op.
88
89 wsdl_op_binding(#operation{binding = Binding}) -> Binding.
90
91 wsdl_op_address(#operation{address = Address}) -> Address.
92
93 wsdl_op_action(#operation{action = Action}) -> Action.
94
95
96 %%% --------------------------------------------------------------------
97 %%% For Quick deployment
98 %%% --------------------------------------------------------------------
99 call(WsdlURL, Operation, ListOfData) when is_list(WsdlURL) ->
100 Wsdl = initModel(WsdlURL, ?DefaultPrefix),
101 call(Wsdl, Operation, ListOfData);
102 call(Wsdl, Operation, ListOfData) when is_record(Wsdl, wsdl) ->
103 case get_operation(Wsdl#wsdl.operations, Operation) of
104 {ok, Op} ->
105 Msg = mk_msg(?DefaultPrefix, Operation, ListOfData),
106 call(Wsdl, Operation, Op#operation.port,
107 Op#operation.service, [], Msg);
108 Else ->
109 Else
110 end.
111
112 %%% --------------------------------------------------------------------
113 %%% Takes http headers
114 %%% --------------------------------------------------------------------
115 call(WsdlURL, Operation, ListOfData, http_headers, HttpHeaders) when is_list(WsdlURL) ->
116 Wsdl = initModel(WsdlURL, ?DefaultPrefix),
117 call(Wsdl, Operation, ListOfData, http_headers, HttpHeaders);
118 call(Wsdl, Operation, ListOfData, http_headers, HttpHeaders) when is_record(Wsdl, wsdl) ->
119 case get_operation(Wsdl#wsdl.operations, Operation) of
120 {ok, Op} ->
121 Msg = mk_msg(?DefaultPrefix, Operation, ListOfData),
122 call(Wsdl, Operation, Op#operation.port,
123 Op#operation.service, [], Msg, http_headers, HttpHeaders);
124 Else ->
125 Else
126 end;
127
128 %%% --------------------------------------------------------------------
129 %%% With additional specified prefix
130 %%% --------------------------------------------------------------------
131 call(WsdlURL, Operation, ListOfData, prefix, Prefix) when is_list(WsdlURL) ->
132 Wsdl = initModel(WsdlURL, Prefix),
133 call(Wsdl, Operation, ListOfData, prefix, Prefix );
134 call(Wsdl, Operation, ListOfData, prefix, Prefix) when is_record(Wsdl, wsdl) ->
135 case get_operation(Wsdl#wsdl.operations, Operation) of
136 {ok, Op} ->
137 Msg = mk_msg(Prefix, Operation, ListOfData),
138 call(Wsdl, Operation, Op#operation.port,
139 Op#operation.service, [], Msg);
140 Else ->
141 Else
142 end.
143
144
145 %%% --------------------------------------------------------------------
146 %%% Takes the actual records for the Header and Body message.
147 %%% --------------------------------------------------------------------
148 call(WsdlURL, Operation, Header, Msg) when is_list(WsdlURL) ->
149 Wsdl = initModel(WsdlURL, ?DefaultPrefix),
150 call(Wsdl, Operation, Header, Msg);
151 call(Wsdl, Operation, Header, Msg) when is_record(Wsdl, wsdl) ->
152 case get_operation(Wsdl#wsdl.operations, Operation) of
153 {ok, Op} ->
154 call(Wsdl, Operation, Op#operation.port, Op#operation.service,
155 Header, Msg);
156 Else ->
157 Else
158 end.
159
160
161 mk_msg(_Prefix, _Operation, ListOfData) ->
162 ListOfData. % rest of record data
163
164 get_operation([#operation{operation = X} = Op|_], X) ->
165 {ok, Op};
166 get_operation([_|T], Op) ->
167 get_operation(T, Op);
168 get_operation([], _Op) ->
169 {error, "operation not found"}.
170
171
172 %%% --------------------------------------------------------------------
173 %%% Make a SOAP request (no attachments)
174 %%% --------------------------------------------------------------------
175 call(Wsdl, Operation, Port, Service, Headers, Message) ->
176 call_attach(Wsdl, Operation, Port, Service, Headers, Message, [], []).
177
178 %%% --------------------------------------------------------------------
179 %%% Make a SOAP request (with http headers)
180 %%% --------------------------------------------------------------------
181 call(Wsdl, Operation, Port, Service, Headers, Message, http_headers, HttpHeaders) ->
182 call_attach(Wsdl, Operation, Port, Service, Headers, Message, [], HttpHeaders).
183
184
185 %%% --------------------------------------------------------------------
186 %%% For Quick deployment (with attachments)
187 %%% --------------------------------------------------------------------
188 call_attach(WsdlURL, Operation, ListOfData, Attachments)
189 when is_list(WsdlURL) ->
190 Wsdl = initModel(WsdlURL, ?DefaultPrefix),
191 call_attach(Wsdl, Operation, ListOfData, Attachments);
192 call_attach(Wsdl, Operation, ListOfData, Attachments)
193 when is_record(Wsdl, wsdl) ->
194 case get_operation(Wsdl#wsdl.operations, Operation) of
195 {ok, Op} ->
196 Msg = mk_msg(?DefaultPrefix, Operation, ListOfData),
197 call_attach(Wsdl, Operation, Op#operation.port,
198 Op#operation.service, [], Msg, Attachments, []);
199 Else ->
200 Else
201 end.
202
203 %%% --------------------------------------------------------------------
204 %%% Takes the actual records for the Header and Body message
205 %%% (with attachments)
206 %%% --------------------------------------------------------------------
207 call_attach(WsdlURL, Operation, Header, Msg, Attachments)
208 when is_list(WsdlURL) ->
209 Wsdl = initModel(WsdlURL, ?DefaultPrefix),
210 call_attach(Wsdl, Operation, Header, Msg, Attachments);
211 call_attach(Wsdl, Operation, Header, Msg, Attachments)
212 when is_record(Wsdl, wsdl) ->
213 case get_operation(Wsdl#wsdl.operations, Operation) of
214 {ok, Op} ->
215 call_attach(Wsdl, Operation, Op#operation.port,
216 Op#operation.service,
217 Header, Msg, Attachments, []);
218 Else ->
219 Else
220 end.
221
222
223 %%% --------------------------------------------------------------------
224 %%% Make a SOAP request (with attachments)
225 %%% --------------------------------------------------------------------
226 call_attach(#wsdl{operations = Operations, model = Model},
227 Operation, Port, Service, Headers, Message, Attachments, HttpHeaders) ->
228 %% find the operation
229 case findOperation(Operation, Port, Service, Operations) of
230 #operation{address = URL, action=Action, operation = Operation} ->
231 %% Add the Soap envelope
232 Envelope = mk_envelope(Message, Headers),
233 %% Encode the message
234 case erlsom:write(Envelope, Model) of
235 {ok, XmlMessage} ->
236
237 {ContentType, Request} =
238 make_request_body(XmlMessage, Attachments, Operation),
239 HttpClientOptions = [],
240 ?dbg("+++ Request = ~p~n", [Request]),
241 HttpRes = http_request(URL, Action, Request,
242 HttpClientOptions, HttpHeaders,
243 ContentType),
244 ?dbg("+++ HttpRes = ~p~n", [HttpRes]),
245 case HttpRes of
246 {ok, _Code, _ReturnHeaders, Body} ->
247 parseMessage(Body, Model);
248 Error ->
249 %% in case of HTTP error: return
250 %% {error, description}
251 Error
252 end;
253 {error, EncodingError} ->
254 {error, {encoding_error, EncodingError}}
255 end;
256 false ->
257 {error, {unknown_operation, Operation}}
258 end.
259
260 %%%
261 %%% returns {ok, Header, Body} | {error, Error}
262 %%%
263 parseMessage(Message, #wsdl{model = Model}) ->
264 parseMessage(Message, Model);
265 %%
266 parseMessage(Message, Model) ->
267 Parsed = erlsom:scan(Message, Model),
268 case Parsed of
269 {ok, #'soap:Envelope'{'Body' = #'soap:Body'{choice = Body},
270 'Header' = undefined}, _} ->
271 {ok, undefined, Body};
272 {ok, #'soap:Envelope'{'Body' = #'soap:Body'{choice = Body},
273 'Header' = #'soap:Header'{choice = Header}}, _} ->
274 {ok, Header, Body};
275 {error, ErrorMessage} ->
276 {error, {decoding, ErrorMessage}}
277 end.
278
279
280 findOperation(_Operation, _Port, _Service, []) ->
281 false;
282 findOperation(Operation, Port, Service,
283 [Op = #operation{operation = Operation,
284 port = Port, service = Service} | _]) ->
285 Op;
286 findOperation(Operation, Port, Service, [#operation{} | Tail]) ->
287 findOperation(Operation, Port, Service, Tail).
288
289
290 mk_envelope(M, H) when is_tuple(M) -> mk_envelope([M], H);
291 mk_envelope(M, H) when is_tuple(H) -> mk_envelope(M, [H]);
292 %%
293 mk_envelope(Messages, []) when is_list(Messages) ->
294 #'soap:Envelope'{'Body' = #'soap:Body'{choice = Messages}};
295 mk_envelope(Messages, Headers) when is_list(Messages),is_list(Headers) ->
296 #'soap:Envelope'{'Body' = #'soap:Body'{choice = Messages},
297 'Header' = #'soap:Header'{choice = Headers}}.
298
299 %%% --------------------------------------------------------------------
300 %%% Parse a WSDL file and return a 'Model'
301 %%% --------------------------------------------------------------------
302 initModel(WsdlFile) ->
303 initModel(WsdlFile, ?DefaultPrefix).
304
305 %% PrefixOrOptions can be a property list that contains the options
306 %% for Erlsom, or a String. If it is a string, this is used as the
307 %% Erlsom 'prefix' option (and the other options are left unspecified).
308 initModel(WsdlFile, PrefixOrOptions) ->
309 Options = case is_string(PrefixOrOptions) of
310 no ->
311 %% It is an option list
312 %% Add the default prefix at the end - it will only be used
313 %% if no other prefix is specified
314 PrefixOrOptions ++ [{prefix, ?DefaultPrefix}];
315 _ ->
316 %% just the prefix
317 [{prefix, PrefixOrOptions}]
318 end,
319 PrivDir = priv_dir(),
320 initModel2(WsdlFile, Options, PrivDir, undefined, undefined).
321
322 initModelFile(ConfigFile) ->
323 {ok, ConfigSchema} = erlsom:compile_xsd(config_file_xsd()),
324 %% read (parse) the config file
325 {ok, Config, _} = erlsom:scan_file(ConfigFile, ConfigSchema),
326 #yaws_soap_config{xsd_path = XsdPath,
327 wsdl_file = Wsdl,
328 add_files = AddFiles} = Config,
329 #xsd_file{name = WsdlFile, prefix = Prefix, import_specs = Import} = Wsdl,
330 initModel2(WsdlFile, [{prefix, Prefix}], XsdPath, Import, AddFiles).
331
332 priv_dir() ->
333 case code:priv_dir(yaws) of
334 {error, bad_name} ->
335 filename:join([filename:dirname(code:which(yaws)),"..", "priv"]);
336 A ->
337 A
338 end.
339
340 initModel2(WsdlFile, ErlsomOptions, Path, Import, AddFiles) ->
341 WsdlName = filename:join([Path, "wsdl.xsd"]),
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
342 IncludeWsdl = {"http://schemas.xmlsoap.org/wsdl/", "wsdl", WsdlName},
08cee94 @kalski added soap12 capability
kalski authored
343 {ok, WsdlModel} = erlsom:compile_xsd_file(
344 filename:join([Path, "wsdl11soap12.xsd"]),
345 [{prefix, "soap"},
346 {include_files, [IncludeWsdl]}]),
347 %% uncomment to generate the wsdl11soap12.hrl file
348 %% erlsom:write_hrl(WsdlModel, "/home/kalski/test/wsdl11soap12.hrl"),
349 %% add the xsd model (since xsd is also used in the wsdl)
350 WsdlModel2 = erlsom:add_xsd_model(WsdlModel),
351 Options = ErlsomOptions ++ makeOptions(Import),
352 %% parse Wsdl
353 {Model, Operations} = parseWsdls([WsdlFile], WsdlModel2,
354 Options, {undefined, []}),
355 %% TODO: add files as required
356 %% now compile envelope.xsd, and add Model
357 {ok, EnvelopeModel} = erlsom:compile_xsd_file(
358 filename:join([Path, "soap-envelope.xsd"]),
359 [{prefix, "soap"},
360 {include_files, [{"http://www.w3.org/XML/1998/namespace", undefined, filename:join([Path, "xml.xsd"])}]}]),
361 SoapModel = erlsom:add_model(EnvelopeModel, Model),
362 %% uncomment to generate the soap-envelope.hrl file
363 %% erlsom:write_hrl(EnvelopeModel, "/home/kalski/test/soap-envelope.hrl"),
364 SoapModel2 = addModels(AddFiles, SoapModel),
365 #wsdl{operations = Operations, model = SoapModel2}.
366
367
368 %%% --------------------------------------------------------------------
369 %%% Parse a list of WSDLs and import (recursively)
370 %%% Returns {Model, Operations}
371 %%% --------------------------------------------------------------------
372 parseWsdls([], _WsdlModel, _Options, Acc) ->
373 Acc;
374 parseWsdls([WsdlFile | Tail], WsdlModel, Options, {AccModel, AccOperations}) ->
375 WsdlFileNoSpaces = rmsp(WsdlFile),
376 {ok, WsdlFileContent} = get_url_file(WsdlFileNoSpaces),
377 {ok, ParsedWsdl, _} = erlsom:scan(WsdlFileContent, WsdlModel),
378 %% get the xsd elements from this model, and hand it over to erlsom_compile.
379 Xsds = getXsdsFromWsdl(ParsedWsdl),
380 %% Now we need to build a list: [{Namespace, Xsd, Prefix}, ...] for
381 %% all the Xsds in the WSDL.
382 %% This list is used when a schema includes one of the other schemas.
383 %% The AXIS java2wsdl tool generates wsdls that depend on this feature.
384 ImportList = makeImportList(Xsds, []),
385 Model2 = addSchemas(Xsds, AccModel, Options, ImportList),
386 Ports = getPorts(ParsedWsdl),
387 Operations = getOperations(ParsedWsdl, Ports),
388 Imports = getImports(filename:dirname(WsdlFileNoSpaces), ParsedWsdl),
389 Model3 = addSchemaFiles(Imports, Model2, Options, []),
390 Acc2 = {Model3, Operations ++ AccOperations},
391 %% process imports (recursively, so that imports in the imported files are
392 %% processed as well).
393 %% For the moment, the namespace is ignored on operations etc.
394 %% this makes it a bit easier to deal with imported wsdl's.
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
395 %% TODO uncomment if imports can be WSDL
08cee94 @kalski added soap12 capability
kalski authored
396 %%Acc3 = parseWsdls(Imports, WsdlModel, Options, Acc2),
397 parseWsdls(Tail, WsdlModel, Options, Acc2).
398
399 %%% --------------------------------------------------------------------
400 %%% build a list: [{Namespace, Xsd}, ...] for all the Xsds in the WSDL.
401 %%% This list is used when a schema inlcudes one of the other schemas.
402 %%% The AXIS java2wsdl tool generates wsdls that depend on this feature.
403 makeImportList([], Acc) ->
404 Acc;
405 makeImportList([ Xsd | Tail], Acc) ->
406 makeImportList(Tail, [{erlsom_lib:getTargetNamespaceFromXsd(Xsd),
407 undefined, Xsd} | Acc]).
408
409
410 %%% --------------------------------------------------------------------
411 %%% compile each of the schemas, and add it to the model.
412 %%% Returns Model
413 %%% (TODO: using the same prefix for all XSDS makes no sense)
414 %%% --------------------------------------------------------------------
415 addSchemas([], AccModel, _Options, _ImportList) ->
416 AccModel;
417 addSchemas([Xsd| Tail], AccModel, Options, ImportList) ->
418 Model2 = case Xsd of
419 undefined ->
420 AccModel;
421 _ ->
422 {ok, Model} =
423 erlsom_compile:compile_parsed_xsd(
424 Xsd,
425 [{include_files, ImportList} |Options]),
426 case AccModel of
427 undefined -> Model;
428 _ -> erlsom:add_model(AccModel, Model)
429 end
430 end,
431 addSchemas(Tail, Model2, Options, ImportList).
432
433 %%% --------------------------------------------------------------------
434 %%% compile each of the schema files, and add it to the model.
435 %%% Returns Model
436 %%% (TODO: using the same prefix for all XSD files makes no sense)
437 %%% --------------------------------------------------------------------
438 addSchemaFiles([], AccModel, _Options, _ImportList) ->
439 AccModel;
440 addSchemaFiles([Xsd| Tail], AccModel, Options, ImportList) ->
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
441 {ok, Model} =
442 erlsom:compile_xsd_file(get_file_with_path(Xsd),
443 [{include_files, ImportList} |Options]),
444 Model2 = case AccModel of
445 undefined -> Model;
446 _ -> erlsom:add_model(AccModel, Model)
08cee94 @kalski added soap12 capability
kalski authored
447 end,
448 addSchemaFiles(Tail, Model2, Options, ImportList).
449
450 %%% --------------------------------------------------------------------
451 %%% Get a file from an URL spec.
452 %%% --------------------------------------------------------------------
453 get_url_file("http://"++_ = URL) ->
454 case httpc:request(URL) of
455 {ok,{{_HTTP,200,_OK}, _Headers, Body}} ->
456 {ok, Body};
457 {ok,{{_HTTP,RC,Emsg}, _Headers, _Body}} ->
458 error_logger:error_msg("~p: http-request got: ~p~n",
459 [?MODULE, {RC, Emsg}]),
460 {error, "failed to retrieve: "++URL};
461 {error, Reason} ->
462 error_logger:error_msg("~p: http-request failed: ~p~n",
463 [?MODULE, Reason]),
464 {error, "failed to retrieve: "++URL}
465 end;
466 get_url_file("file://"++Fname) ->
467 {ok, Bin} = file:read_file(Fname),
468 {ok, binary_to_list(Bin)};
469 %% added this, since this is what is used in many WSDLs (i.e.: just a filename).
470 get_url_file(Fname) ->
471 {ok, Bin} = file:read_file(Fname),
472 {ok, binary_to_list(Bin)}.
473
474
475 %%% --------------------------------------------------------------------
476 %%% Make a HTTP Request
477 %%% --------------------------------------------------------------------
478 http_request(URL, Action, Request, Options, Headers, ContentType) ->
479 case code:ensure_loaded(ibrowse) of
480 {module, ibrowse} ->
481 %% If ibrowse exist in the path then let's use it...
482 ibrowse_request(URL, Action, Request, Options,
483 Headers, ContentType);
484 _ ->
485 %% ...otherwise, let's use the OTP http client.
486 inets_request(URL, Action, Request, Options,
487 Headers, ContentType)
488 end.
489
490 inets_request(URL, Action, Request, Options, Headers, ContentType) ->
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
491 case Action of
08cee94 @kalski added soap12 capability
kalski authored
492 undefined ->
493 NHeaders = Headers;
494 _ ->
495 NHeaders = [{"SOAPAction", Action} | Headers]
496 end,
497 NewHeaders = case proplists:get_value("Host", NHeaders) of
498 undefined ->
499 [{"Host", "localhost:8800"}|NHeaders];
500 _ ->
501 NHeaders
502 end,
503 NewOptions = [{cookies, enabled}|Options],
504 httpc:set_options(NewOptions),
505 case httpc:request(post,
506 {URL,NewHeaders,
507 ContentType,
508 Request},
509 [{timeout,?HTTP_REQ_TIMEOUT}],
510 [{sync, true}, {full_result, true},
511 {body_format, string}]) of
512 {ok,{{_HTTP,200,_OK},ResponseHeaders,ResponseBody}} ->
513 {ok, 200, ResponseHeaders, ResponseBody};
514 {ok,{{_HTTP,500,_Descr},ResponseHeaders,ResponseBody}} ->
515 {ok, 500, ResponseHeaders, ResponseBody};
516 {ok,{{_HTTP,ErrorCode,_Descr},ResponseHeaders,ResponseBody}} ->
517 {ok, ErrorCode, ResponseHeaders, ResponseBody};
518 Other ->
519 Other
520 end.
521
522 ibrowse_request(URL, Action, Request, Options, Headers, ContentType) ->
523 case start_ibrowse() of
524 ok ->
9e6c9e1 @vinoski revert SOAP response MIME type to "text/xml"
vinoski authored
525 NewHeaders = [{"Content-Type", ContentType} |
526 case Action of
527 undefined ->
528 Headers;
529 _ ->
530 [{"SOAPAction", Action} | Headers]
531 end],
532 case ibrowse:send_req(URL, NewHeaders, post, Request, Options) of
08cee94 @kalski added soap12 capability
kalski authored
533 {ok, Status, ResponseHeaders, ResponseBody} ->
9e6c9e1 @vinoski revert SOAP response MIME type to "text/xml"
vinoski authored
534 {ok, list_to_integer(Status), ResponseHeaders, ResponseBody};
08cee94 @kalski added soap12 capability
kalski authored
535 {error, Reason} ->
536 {error, Reason}
537 end;
538 error ->
539 {error, "could not start ibrowse"}
540 end.
541
542 start_ibrowse() ->
543 case ibrowse:start() of
544 {ok, _} -> ok;
545 {error, {already_started, _}} -> ok;
546 _ -> error
547 end.
548
549
550 rmsp(Str) -> string:strip(Str, left).
551
552
553 make_request_body(Content, [], Operation) ->
554 {"application/soap+xml;charset=UTF-8;action=\"" ++ Operation ++ "\"",
555 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"++ Content};
556 make_request_body(Content, AttachedFiles, _Operation) ->
557 {"application/dime",
558 yaws_dime:encode("<?xml version=\"1.0\" encoding=\"utf-8\"?>" ++ Content,
559 AttachedFiles)}.
560
561 makeFault(FaultCode, FaultString) ->
562 try
563 "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
564 "<SOAP-ENV:Body>"
565 "<SOAP-ENV:Fault>"
566 "<faultcode>SOAP-ENV:" ++ FaultCode ++ "</faultcode>" ++
567 "<faultstring>" ++ FaultString ++ "</faultstring>" ++
568 "</SOAP-ENV:Fault>"
569 "</SOAP-ENV:Body>"
570 "</SOAP-ENV:Envelope>"
571 catch
572 _:_ ->
573 "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"
574 "<SOAP-ENV:Body>"
575 "<SOAP-ENV:Fault>"
576 "<faultcode>SOAP-ENV:Server</faultcode>"
577 "<faultstring>Server error</faultstring>"
578 "</SOAP-ENV:Fault>"
579 "</SOAP-ENV:Body>"
580 "</SOAP-ENV:Envelope>"
581 end.
582
583 %% record http_header is not defined??
584 findHeader(Label, Headers) ->
585 findHeader0(yaws:to_lower(Label), Headers).
586
587 findHeader0(_Label, []) ->
588 undefined;
589 findHeader0(Label, [{_,_,Hdr,_,Val}|T]) ->
590 case {Label, yaws:to_lower(Hdr)} of
591 {X,X} -> Val;
592 _ -> findHeader0(Label, T)
593 end;
594 findHeader0(_Label, undefined) ->
595 undefined.
596
597
598 makeOptions(undefined) ->
599 [];
600 makeOptions(Import) ->
601 lists:map(fun makeOption/1, Import).
602
603 %% -record(import_specs, {atts, namespace, prefix, location}).
604 makeOption(#import_specs{namespace = Ns, prefix = Pf, location = Lc}) ->
605 {Ns, Pf, Lc}.
606
607
608 addModels(undefined, Model) ->
609 Model;
610 addModels(Import, Model) ->
611 lists:foldl(fun addModel/2, Model, Import).
612
613 %% -record(xsd_file, {atts, name, prefix, import_specs}).
614 addModel(undefined, Acc) ->
615 Acc;
616 addModel(#xsd_file{name = XsdFile, prefix = Prefix, import_specs = Import},
617 Acc) ->
618 Options = makeOptions(Import),
619 {ok, Model2} = erlsom:add_xsd_file(XsdFile, [{prefix, Prefix}|Options],Acc),
620 Model2.
621
622 %% returns [#port{}]
623 %% -record(port, {service, port, binding, address}).
624 getPorts(ParsedWsdl) ->
625 Services = getTopLevelElements(ParsedWsdl, 'wsdl:tService'),
626 getPortsFromServices(Services, []).
627
628 getPortsFromServices([], Acc) ->
629 Acc;
630 getPortsFromServices([Service|Tail], Acc) ->
631 getPortsFromServices(Tail, getPortsFromService(Service) ++ Acc).
632
633 getPortsFromService(#'wsdl:tService'{name = Name, port = Ports}) ->
634 getPortsInfo(Ports, Name, []).
635
636 getPortsInfo([], _Name, Acc) ->
637 Acc;
638
639 getPortsInfo([#'wsdl:tPort'{name = Name,
640 binding = Binding,
641 choice =
642 [#'soap:tAddress'{location = URL}]} | Tail],
643 ServiceName, Acc) ->
644 getPortsInfo(Tail, ServiceName, [#port{service = ServiceName,
645 port = Name,
646 binding = Binding,
647 address = URL}|Acc]);
648 %% non-soap bindings are ignored.
649 getPortsInfo([#'wsdl:tPort'{} | Tail], ServiceName, Acc) ->
650 getPortsInfo(Tail, ServiceName, Acc).
651
652
653 getTopLevelElements(#'wsdl:tDefinitions'{choice1 = TLElements}, Type) ->
654 getTopLevelElements(TLElements, Type, []).
655
656 getTopLevelElements([], _Type, Acc) ->
657 Acc;
658 getTopLevelElements([#'wsdl:anyTopLevelOptionalElement'{choice = Tuple}| Tail],
659 Type, Acc) ->
660 case element(1, Tuple) of
661 Type -> getTopLevelElements(Tail, Type, [Tuple|Acc]);
662 _ -> getTopLevelElements(Tail, Type, Acc)
663 end.
664
665 get_file_with_path(Url) ->
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
666 case Url of
08cee94 @kalski added soap12 capability
kalski authored
667 "http://" ++ _ ->
668 undefined;
669 "file://" ++ FName ->
670 FName;
671 _ ->
672 Url
673 end.
674
675
676 getImports(WsdlDirname, Definitions) ->
677 Imports = getTopLevelElements(Definitions, 'wsdl:tImport'),
678 lists:map(fun(Import) ->
f3622d2 @vinoski dialyzer fixes and comments
vinoski authored
679 case WsdlDirname of
08cee94 @kalski added soap12 capability
kalski authored
680 "http://" ++ _AbsDirname ->
681 WsdlDirname ++ "/" ++ Import#'wsdl:tImport'.location;
682 "file://" ++ _AbsDirname ->
683 WsdlDirname ++ "/" ++ Import#'wsdl:tImport'.location;
684 Fname ->
685 filename:join(Fname, Import#'wsdl:tImport'.location)
686 end
687 end, Imports).
688
689 %% returns [#operation{}]
690 getOperations(ParsedWsdl, Ports) ->
691 Bindings = getTopLevelElements(ParsedWsdl, 'wsdl:tBinding'),
692 getOperationsFromBindings(Bindings, Ports, []).
693
694 getOperationsFromBindings([], _Ports, Acc) ->
695 Acc;
696 getOperationsFromBindings([Binding|Tail], Ports, Acc) ->
697 getOperationsFromBindings(Tail, Ports,
698 getOperationsFromBinding(Binding, Ports) ++ Acc).
699
700 getOperationsFromBinding(#'wsdl:tBinding'{name = BindingName,
701 type = BindingType,
702 choice = _Choice,
703 operation = Operations}, Ports) ->
704 %% TODO: get soap info from Choice
705 getOperationsFromOperations(Operations, BindingName, BindingType,
706 Operations, Ports, []).
707
708 getOperationsFromOperation(BindingName, BindingType, Ports, Name, Action, Operations, Tail, Acc) ->
709 %% lookup Binding in Ports, and create a combined result
710 Ports2 = searchPorts(BindingName, Ports),
711 %% for each port, make an operation record
712 CombinedPorts = combinePorts(Ports2, Name, BindingName, Action),
713 getOperationsFromOperations(
714 Tail, BindingName, BindingType,
715 Operations, Ports, CombinedPorts ++ Acc).
716
717 getOperationsFromOperations([], _BindingName, _BindingType, _Operations, _Ports, Acc) ->
718 Acc;
719
720 getOperationsFromOperations([#'wsdl:tBindingOperation'{name = Name,
721 choice = Choice} | Tail],
722 BindingName, BindingType, Operations, Ports, Acc) ->
723 %% get SOAP action from Choice,
724 case Choice of
725 [#'soap:tOperation'{soapAction = Action}] ->
726 getOperationsFromOperation(BindingName, BindingType, Ports, Name, Action, Operations, Tail, Acc);
727 _ ->
728 getOperationsFromOperation(BindingName, BindingType, Ports, Name, undefined, Operations, Tail, Acc)
729 end.
730
731 combinePorts(Ports, Name, BindingName, Action) ->
732 combinePorts(Ports, Name, BindingName, Action, []).
733
734 combinePorts([], _Name, _BindingName, _Action, Acc) ->
735 Acc;
736 combinePorts([#port{service = Service,
737 port = PortName,
738 address = Address} | Tail],
739 Name, BindingName, Action, Acc) ->
740 combinePorts(Tail, Name, BindingName, Action,
741 [#operation{service = Service,
742 port = PortName, operation = Name,
743 binding = BindingName,
744 address = Address, action = Action} | Acc]).
745
746 searchPorts(BindingName, Ports) ->
747 searchPorts(BindingName, Ports, []).
748
749 searchPorts(_BindingName, [], Acc) ->
750 Acc;
751 searchPorts(BindingName, [Port | Tail], Acc) ->
752 PortBinding = erlsom_lib:localName(Port#port.binding),
753 case PortBinding of
754 BindingName ->
755 searchPorts(BindingName, Tail, [Port | Acc]);
756 _ ->
757 searchPorts(BindingName, Tail, Acc)
758 end.
759
760 %% copied from yaws/json.erl
761 is_string([]) -> yes;
762 is_string(List) -> is_string(List, non_unicode).
763
764 is_string([C|Rest], non_unicode) when C >= 0, C =< 255 -> is_string(Rest, non_unicode);
765 is_string([C|Rest], _) when C =< 65000 -> is_string(Rest, unicode);
766 is_string([], non_unicode) -> yes;
767 is_string([], unicode) -> unicode;
768 is_string(_, _) -> no.
769
770 getXsdsFromWsdl(Definitions) ->
771 case getTopLevelElements(Definitions, 'wsdl:tTypes') of
772 [#'wsdl:tTypes'{choice = Xsds}] -> Xsds;
773 [] -> []
774 end.
775
776 config_file_xsd() ->
777 "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">"
778 " <xs:element name=\"yaws_soap_config\">"
779 " <xs:complexType>"
780 " <xs:sequence>"
781 " <xs:element name=\"xsd_path\" type=\"xs:string\" minOccurs=\"0\"/>"
782 " <xs:element name=\"user_module\" type=\"xs:string\"/>"
783 " <xs:element name=\"wsdl_file\" type=\"xsd_file\"/>"
784 " <xs:element name=\"add_file\" type=\"xsd_file\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>"
785 " </xs:sequence>"
786 " </xs:complexType>"
787 " </xs:element>"
788 " <xs:complexType name=\"xsd_file\">"
789 " <xs:sequence>"
790 " <xs:element name=\"import_specs\" type=\"import_specs\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>"
791 " </xs:sequence>"
792 " <xs:attribute name=\"name\" type=\"string\" use=\"required\"/>"
793 " <xs:attribute name=\"prefix\" type=\"string\"/>"
794 " </xs:complexType>"
795 " <xs:complexType name=\"import_specs\">"
796 " <xs:attribute name=\"namespace\" type=\"string\" use=\"required\"/>"
797 " <xs:attribute name=\"prefix\" type=\"string\"/>"
798 " <xs:attribute name=\"location\" type=\"string\"/>"
799 " </xs:complexType>"
800 "</xs:schema>".
801
802
Something went wrong with that request. Please try again.