Skip to content

HTTPS clone URL

Subversion checkout URL

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