SOAP WSDL: Unterschied zwischen den Versionen
Cg (Diskussion | Beiträge) |
Cg (Diskussion | Beiträge) |
||
Zeile 283: | Zeile 283: | ||
</PRE></CODE> |
</PRE></CODE> |
||
== SOAP Package Structure == |
|||
The whole package is structured into a number of sub-packages, of which some are usable outside the SOAP context. |
The whole package is structured into a number of sub-packages, of which some are usable outside the SOAP context. |
Version vom 3. Juli 2015, 08:15 Uhr
Inhaltsverzeichnis
SOAP WSDL Package[Bearbeiten]
Basic Usage[Bearbeiten]
These chapter describes the basic underlying implementation. The code generated by the WSDL class generator calls those functions.
To make a SOAP request as a client, given a WSDL (either as file, string or URL),
you have to:
- create a service from the WSDL
- instantiate a client
- create a call object with call arguments
- perform the call
- extract the values from the returned result object
Creating a Service Instance[Bearbeiten]
The service instance keeps the information about the available entry points, the protocol and encoding and the data types. You need a WSDL to create a service. This can come from multiple sources:
From URL (preferred)[Bearbeiten]
this is the preferred method, especially as it will automatically deal with imported schema definitions (i.e. if the WSDL refers to other documents via an import)
service := SprayWSDLService onUrl: 'anUrlString'
From a String[Bearbeiten]
this only works, if the WSDL is self contained (eg. contains all required definitions and does not import other documents). If it does, missing documents will be fetched automatically via HTTP (see description on transports below).
service := SprayWSDLService onXmlString: 'wsdlString'
From a Set of Files/Strings[Bearbeiten]
when a WSDL URL is parsed, an transport object instance is used to fetch imported documents. By default, an instance of SptHTTPClient is used to fetch required documents.
A mock transport class named SptHTTPLocalTransport can be used. This keeps a list of string documents in a dictionary mapped by URL and delivers that contents, when asked for (i.e. it simulates an SptHTTPClient-Transport).
Thus, if you have a WSDL document, which imports other documents, AND you want to prevent WSDL fetches via http of your program at runtime, you should setup an instance of SptHTTPLocalTransport, give it all the documents (incl. any imported docs), and provide that as a document retriever.
For example, if you have a WSDL in (urlA), which imports urlB and urlC, and urlC imports urlD, use the following setup:
localTransport := SptHTTPLocalTransport new.
localTransport localDocuments
at: urlA "eg something like: 'http://foo.services.de:30050/partner/PartnerBusinessService?SCHEMA'"
put:
'<?xml version="1.0"?>
... the whole urlA document as string...
'.
localTransport localDocuments
at: urlB "eg something like: 'http://foo.services.de..."
put:
'<?xml version="1.0"?>
... the whole urlB document as string...
'.
localTransport localDocuments
at: urlC "eg something like: 'http://foo.services.de..."
put:
'<?xml version="1.0"?>
... the whole urlC document as string...
'.
and then create the service with:
definitions := WSDLDefinitions
onUrl: urlA
transport: localTransport.
service := SprayWSDLService onDefinitions: definitions.
In such a setup, all required import urls will be fetched from there (eg. no HTTP requests required).
You can provide the url contents from class variables, class getter methods, etc. Of course, you can also read the documents from a local file, and setup the localTransport instance from their contents.
From Binary Storage[Bearbeiten]
All of the above methods require some initial processing time (in the order of a few 100ms), because they read the textual WSDL with the XML parser, and construct a ratherr complicated object structure containing those definitions. (most of the space is taken up by XML-schema definitions, which are kept as XML-node trees).
You can experiment with a scheme used in the original Dolphin implementation, where definition objects are converted to a binary store string at development time, and restored from it in the deployed system at execution time.
Be aware, that such binary storage data is both system dependent and harder to analyze in case of errors. Our experience is that it is worth to have the WSDL around in plain text, as fixes are much easier to make there, in case of problems.
Instantiating a Client[Bearbeiten]
Once you have the service object, you have to create a client to a concrete partner service. This client prepares the outgoing and incoming envelopes, to save some processing time in the actual message send.
To create a client, use either:
client := service createClient
which creates a client to the service's default host. This is the one as specified in the URL.
Often, this is not the one you want to connect to (for example, if you have a local test service running, and/or the host address of the service is different for in-house connections). Also, some WSDLs do not include a valid service port URL.
Then use:
client := service createClientTo:'http://foo.bar.com:4933/bla/...'.
Create a Call Object with Call Arguments[Bearbeiten]
Finally, an actual service call is to be performed.
This is a two-step operation: first, a call instance is created, which takes the name of the operation and its arguments, and generates an out-envelope (which includes the XML representation of the arguments, as specified in the WSDL's encoding information). The second step is the actual call, which is described below.
Call argument objects fall into two categories:
- simple objects (xsd:simpleTypes, which can be directy mapped to Smalltalk objects)
- complex objects (in the XML schema, these are xsd:complexType elements)
Simple types are mapped to corresponding Smalltalk objects:
- xsd:string - String
- xsd:int - Integer
- xsd:integer - Integer
- xsd:boolean - Boolean
- xsd:date - Date
- xsd:time - Time
- etc.
There are a few xsd:simpleTypes, for which no standard Smalltalk object exists on all machines (xsd:datetime). Depending on the dialect, these are either mapped to existing classes or to classes from the SOAP framework. For example, xsd:datetime is mapped to the Timestamp class in Smalltalk/X but to SOAP__XeXSDDateTime in VSE.
Complex types must be represented as instances of one of:
- an XeQstruct - this is a special kind of Dictionary, which uses qualified names (eg. XML names with namespace and prefix) as keys. This is the default representation used by the SOAP framework.
- a Dictionary - if values are given as dictionary instance, it must contain key-value mappings for the sub-elements. The keys are the localName of the schema's element names (i.e. if the element is named foo:someType, then the key should be 'foo' only).
- a special data holder object - this must understand getter- and setter messages, corresponding to the local names of the elements.
A concrete example is found in the BLZ-testcase:
call := client send: 'getBank' withArguments:getBankArg.
Performing the Service Call[Bearbeiten]
Finally, execute the call by giving it a #value message (think of the call object as a block, which does the call for you):
result := call value.
this harmless looking message performs the transfer. It sets up the communication (usually HTTP, where the SOAP message is wrapped into an HTTP-request), sends the message in an asynchronous send process, awaits the response, decodes the returned XML and instantiates the result object(s).
Notice that SOAP allows for multiple return values to be specified in the WSDL. If this is the case, the result returned by #value is the first return value, and a sequenceable collection of all return values is retrieved via #outParameters.
WSDL Class Generator[Bearbeiten]
The wsdl class generator takes WSDL definitions and generates a client class containing entries for the operations, and type classes which hold the arguments and return values.
Generating Classes[Bearbeiten]
First, instantiate the WSDLClassBuilder.
You need either a service instance (see above) or a wsdl definitions object, as argument to the instantiation message.
For example:
service := SprayWSDLService onXmlString:'... a lot of WSDL here ...'
builder := WSDLClassBuilder service: service
or:
definitions := WSDLDefinitions onURL:'http://.....?wsdl'.
builder := WSDLClassBuilder definitions: definitions
Then let it generate the classes with:
builder generate
Unless overwritten by the setup messages below, the generated client class will have a name constructed from the service name in the WSDL. It will have a "SOAP__" prefix, followed by the service name and "Client" (i.e. "SOAP__<service name>Client"). For example, if the DeBeKa partnerservice-WSDL is used, the client class name would be "SOAP__PartnerServiceClient".
Data objects are generated for all non-simpleType objects, which are recursively reached/used by the WSDL operations, starting with the individual operation arguments. Datatype class names are constructed in a similar fashion: a "SOAP__" prefix, the service name, an underline, and the type name from the xsd-schema. For example, if the WSDL contains an xsd type named "bestaetigePartner", the generated type class would be named "SOAP__PartnerService_bestaetigePartner". The service name is included, to avoid a name conflict with type classes from other services.
The builders naming defaults can be overwritten via setup methods described below. This must of course be done before calling the #generate method.
clientClassName: aString[Bearbeiten]
defines (overwrites) the name of the generated client class. Thus, if you say:
builder clientClassName: 'DBK_PService'.
the generated client class will be named "DBK_PService" instead of "SOAP_PartnerServiceClient"
typeClassNamePrefix: aString[Bearbeiten]
defines (overwrites) the prefix to be used for type classes. This includes the namespace prefix. Thus, if you say:
builder typeClassNamePrefix: 'DBK_'.
all generated type classes will be named "DBK_<schema name>".
classNamePrefix: aString[Bearbeiten]
defines (overwrites) the prefix to be used for all classes (i.e. type- and client classes). This includes the namespace prefix. Thus, if you say:
builder classNamePrefix: 'DBK_'.
all generated type classes will be named "DBK_<schema name>", and the client class will be "DBK_<serviceName>".
Using Generated Client Classes[Bearbeiten]
The generated client class must be instantiated, and contains a category named "operations" with all SOAP operations. Each operation expects a request-argument object as parameter, as specified in the WSDL. For example, in the partnerService, you will find methods named "bestaetigePartner:", "listPartner:" etc.
The generated methods are written to include comments and naming hints (argument names) which will help in instantiating the required argument objects correctly.
Instantiating Argument Objects[Bearbeiten]
Thes will have getters and setters for their elements. If the schema includes restrictions (such as a length restriction on a string, or an enumeration of allowed values), the setters will also perform verification of the argument. In addition, the generated code includes a comment, which describes the restrictions.
The verification can be disabled via a central flag, as returned by: "SOAP__SoapSetting verifySoapCallArguments". You can change this method to return false for deployed images.
Example: Generating PartnerService[Bearbeiten]
The following code example is also found in "WSDLClassBuilderTest >> testClassBuilder_02".
Generating the partnerService classes:
definitions := WSDLDefinitions
onUrl: 'http://plan-eval-esb.services.debeka.de:30050/partner/proxyservice/PartnerBusinessService?WSDL'.
builder := WSDLClassBuilder definitions:definitions.
builder generate.
this will generate the following classes (in ST/X: into class category "SOAP-Generated-partnerService"):
- SOAP__PartnerServiceClient
- SOAP__PartnerService_addressType
- SOAP__PartnerService_bestaetigepartner
- SOAP__PartnerService_fehler
- ...
- SOAP__PartnerService_personType
- SOAP__PartnerService_seitenSuche
Example: Using PartnerService[Bearbeiten]
suchArg := SOAP_PartnerService_seitenSuche new.
suchArg ergebnisType:'ALLES'.
service := SOAP__PartnerService new.
result := service listePartner: suchArg.
"result will be an instance of kontrollTyp"
Transcript show: result code.
Transcript show: result fehler.
Transcript show: result information.
SOAP Package Structure[Bearbeiten]
The whole package is structured into a number of sub-packages, of which some are usable outside the SOAP context. Each package has a subpackage (named "XX_test" in ST/X and usually "XXTES" in VSE).
In ST/X, all SOAP classes live in a separate namespace ("SOAP"), and thus have a "SOAP::" prefix in their name. Inside the namespace, the prefix is not needed to refer to a class which is in the same namespace. Therefore, in ST/X code, you will find class references without prefix.
For VSE, which has no namespaces, class names are translated by prefixing the name with "<namespace>__". Thus all classes will have a "SOAP__" prefix in VSE. This is automatically done by the STX-VSE class exporter, which was developped specifically for this purpose.
Notice, that this automatic rewrite can of course not detect situations where class names are constructed dynamically, and referred to via "Smalltalk at:". Such code had to be identified and manually changed to deal with both dialects. Usually, you will find code like "Smalltalk isSmalltalkX ifTrue:[...]" or "Smalltalk isVisualSmalltalkEnterprise ifTrue:[...]" in those parts. The goal is to keep a common code base, so that future improvements and fixes will be immediately available in both dialects.
Package: xe ("stx_goodies_soap_xe" in ST/X; "SOAPXE.pkg" in VSE)[Bearbeiten]
contains classes to represent qualified names, xsd schema definitions, xsd typespaces and mappings etc. there are no input/output or GUI related operations in this package. Most of the classes found there can be mapped one-by-one to corresponding elements from an xsd schema definition. Object hierarchies of them are created by the WSDL readers, the SOAP envelope creators and the SOAP encoders.
Package: spray ("stx_goodies_soap_spray")[Bearbeiten]
the basic SOAP framework. Includes both client and server code (however, the server part was not part of the VSE porting effort, and may not run without further debugging). The spray package does not need a WSDL. In theory, it is possible to setup the above described service/client/call objects manually, by appropriate instance creation and initialization messages. In practice, the setup is too complicated, and the WSDL subpackage does this for you.
Package: wsdl ("stx_goodies_soap_wsdl")[Bearbeiten]
this contains classes to represent the information inside a WSDL, and to create a service object from it. Most of the code is in WSDLDefinitions, which deals with all xsd-schema handling, especially with the import of other documents (using transport objects)
Package: yaxo ("stx_goodies_xml_yaxo")[Bearbeiten]
the YAXO ("yet another xml o-stands-for-whatever") XML parser. Reads XML and generates a hierarchy of XML nodes. This parser lives in its own namespace called "YAXO"; thus, in VSE, all of its classes will have a "YAXO__" prefix.
The SOAP framework is prepared to use different XML parsers, and uses adaptors which transforms the different XML nodes into its own XeNode representation. Currently three adaptors are present, for YAXO, VisualWorks XML parser and ExoboxXML parser. XML parsing speed is crucial when WSDLs are read and to decode SOAP envelopes. If performance ever becomes a major issue, you may replace this by a high speed XML parser (written in C), and create a matching adaptor (take SOAP::SoYaxXMLParserAdapter as a base).
Package: soap ("stx_goodies_soap")[Bearbeiten]
contains all the rest, such as error classes, abstract classes, encoders/decoders, transport interfaces (adapters to HTTP), etc.