In SOAP the communication is realized via messages in the XML format. Files which are to be converted or sent back to the client as a result are transferred as MTOM-Attachment.
Tip
We recommend soapUI for developing and debugging SOAP requests.
After activating the webservice interface as described in the section called “Configuring the SOAP Interface”,
the formal description of the interface can be downloaded in WSDL format
at http://<url>?wsdl
(for example, when configured as described in the section called “Configuring the SOAP Interface”,:
http://localhost:9000/jadiceServer?wsdl
). This enables you to generate code in many webservice
frameworks in order to communicate with the webservice of jadice server. jadice server can be addressed
in two different ways with a SOAP request:
-
The workflow is pre-configured with the help of a template that has been previously stored on the server.
-
The workflow is defined during the processing time within the SOAP request.
These two options are described in the following sections.
An XML-coded job description can be stored on the server so that the client can refer to it in its SOAP request and thus the job does not have to be configured during the processing time.
The structure of the message will be explicated with the following example:
Example 7.1. Example of a SOAP Request with Template
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.server.jadice.levigo.com/"> <soapenv:Header/> <soapenv:Body> <ws:run> <ws:job templateLocation="resource:/jobtemplates/x2pdf.xml"> <ws:property name="dp.rulesetName">default</ws:property> <ws:property name="dp.targetMimeType">application/pdf</ws:property> <ws:property name="dp.timeout">8000</ws:property> <ws:stream mimeType="unknown/*" uuid="123456789"nodeId="node0"> <ws:documentData>BASE64_encoded_data</ws:documentData> </ws:stream> </ws:job> </ws:run> </soapenv:Body> </soapenv:Envelope>
Beside the pre-defined elements for header and body in SOAP messages,
the specific element run
addresses the method run
which is offered by the webservice.
This method defines a job (see Chapter 4, System Architecture) which is pre-defined by a
template (see the section called “Definition of Job Templates”).
The location of the corresponding template on the server is given by the attribute
templateLocation
. If variables are defined in the template,
they can be configured (i. e. their default values can be overwritten) with the help of
property
elements.
The attribute messageID
is optional. The client can freely assign
it and it will then be adopted in the server's reply.
Data streams which are to be processed are referenced with the help of stream
elements in the SOAP message. Information about the individual ID (uuid
)
and the MIME type are optional. If the MIME type is unknown but specified in the message,
unknown/*
should be used.
If multiple StreamInputNode
s are defined in the template file, you need to clearly allocate
which data stream should be sent to which StreamInputNode
. This allocation is realized through the
nodeId
attribute which refers to the ID assigned to the StreamInputNode
within
the template (attribute id
).
The actual data can be assigned as BASE64-coded data stream with the tag documentData
or referenced in a multipart/related container which features the CID (contentID
)
specified here if the MTOM feature is activated.
If the client is not supposed to use a job configuration pre-defined on the server, it is possible
to incorporate this configuration in the SOAP request. The format used is similar to the
one in a separate job template (see the section called “Definition of Job Templates”).
Instead of the root element job
, the definition is embedded in the SOAP
request as a configuration
element.
The following example illustrates this:
Example 7.2. Example of a SOAP Request with Embedded Job Definition
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.server.jadice.levigo.com/"> <soapenv:Header/> <soapenv:Body> <ws:run> <ws:job messageID="4711"> <ws:configuration type="demultipexer example"> <ws:nodes> <ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="input1" /> <ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="input2" /> <ws:node class="com.levigo.jadice.server.nodes.DemultiplexerNode" id="demux" /> <ws:node class="com.levigo.jadice.server.nodes.StreamOutputNode" id="out" /> </ws:nodes> <ws:connections> <ws:connect from="input1" to="demux" /> <ws:connect from="input2" to="demux" /> <ws:connect from="demux" to="out" /> </ws:connections> </ws:configuration> <ws:stream nodeId="input1"> <ws:documentData>BASE64_encoded_data</ws:documentData> </ws:stream> <ws:stream nodeId="input2"> <ws:documentData>BASE64_encoded_data</ws:documentData> </ws:stream> </ws:job> </ws:run> </soapenv:Body> </soapenv:Envelope>
In this example two StreamInputNode
s are connected through a DemultiplexerNode
and the input data is returned to the client without changes.
The definition of the Node
s and their designated workflow graph is described in the
configuration
block.
Furthermore, this example shows how to connect specific input streams to a StreamInputNode
:
The first document is connected to the first StreamInputNode
(nodeId
input1
)
and the second document to the second StreamInputNode
(nodeId
input2
).
The structure of a reply which is sent to the client in return for his request is also specified in the above-mentioned WSDL.
A reply could look like the following example:
Example 7.3. Example of a SOAP Reply
<<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <js:runResponse xmlns:js="http://ws.server.jadice.levigo.com/"> <js:return> <js:stream> <js:documentData>BASE64_encoded_data</js:documentData> </js:stream> <js:status>COMPLETED</js:status> </js:return> </js:runResponse> </soap:Body> </soap:Envelope>
Beside a (potentially empty) number of resulting streams, which are respectively referenced to a multipart/related container with the help of an unambiguous ID, this reply also contains a status message. The following values are possible:
Value | Meaning |
---|---|
|
Job has been completed. |
|
Job could not be completed. |
In both cases the return
element can enclose a number of log-entry
elements which
contain hints on the failure of the processing or messages which occurred during the processing
(see the section called “Creating a JobListener”). The following example shows the
error message occurring if a job template file which does not exist is referenced:
Example 7.4. Example of a SOAP Reply with Error Message
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <js:runResponse xmlns:js="http://ws.server.jadice.levigo.com/"> <js:return messageID="12345"> <js:log-entry timeStamp="31.12.2009 22:33:44"> <js:level>ERROR</js:level> <js:id>JS.WEBSERVICE-EXCEPTION</js:id> <js:message>java.io.FileNotFoundException: Could not locate resource: does_not_exist.xml</js:message> </js:log-entry> <js:status>FAILED</js:status> </js:return> </js:runResponse> </soap:Body> </soap:Envelope>
The definition of job templates spares the clients from knowing the internal steps
of jadice server which are necessary to convert documents. These steps are provided for the
webservice interface at a central location. Thus, the client only needs to know the
webservice method run
and the location of the desired
template (these are usually filed in the subdirectory /server-config/
).
The directory /server-config/jobtemplates
contains the XSD definition of these templates.
An example for such a template is the template x2pdf.xml
, which is included
in the distribution package. Similar to the example in the section called “Converting Unknown Entry Data into a Consistent Format (PDF)”,
this template identifies unknown input data and converts them to PDF format:
Example 7.5. Example for a Job Template
<job xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="jobtemplate.xsd" type="x2pdf"> <ws:properties> <ws:property name="PROPERTY_KEY_COMMUNICATION_TIMEOUT">${communication_timeout:22000}</ws:property> </ws:properties> <ws:nodes> <ws:node class="com.levigo.jadice.server.nodes.StreamInputNode" id="node0"> <ws:property name="timeout">${timeout:6000}</ws:property> </ws:node> <ws:node class="com.levigo.jadice.server.nodes.DynamicPipelineNode" id="node1"> <ws:property name="ruleset">${ruleset:resource:/dynamic-pipeline-rules/default.xml}</ws:property> <ws:property name="targetMimeType">${targetMimeType:application/pdf}</ws:property> <ws:property name="timeout">${timeout:6000}</ws:property> </ws:node> <ws:node class="com.levigo.jadice.server.nodes.StreamOutputNode" id="node2"> <ws:property name="timeout">${timeout:6000}</ws:property> </ws:node> </ws:nodes> <ws:connections> <ws:connect from="node0" to="node1" /> <ws:connect from="node1" to="node2" /> </ws:connections> </ws:job>
As becomes obvious, a template is divided into three blocks:
As long as the individual Node
s and their properties adhere to the conventions for java beans, the
respective properties can just be put above their names. As the above example shows, it is also possible to make
them flexible, according to the following pattern: ${<key>}
or
${<key>:<default value>}
, in which the key has to start with a letter and
may have letters, numbers, “_” (underscore), “-” (hyphen) and “.” (period).
Because of this key, these values can be set (i. e. overwritten) as property (element <property name="key">value</property>
)
in the SOAP request.
If variables that have no default value are not defined, the following error occurs on request:
com.thoughtworks.xstream.converters.ConversionException: Pattern refers to undefined
variable <key> for which there is no default
The individual Node
elements need to feature an ID that is unambiguous to the
template. With the help of these IDs, they are connected to a workflow in the
connections
block.
If data are to be transferred from the client to the server via the SOAP request
that belongs to this template, you need to define at least one StreamInputNode
. If there are multiple
StreamInputNode
s, the individual stream
elements have to be allocated to their Node
s with
the help of the attribute nodeId
in the SOAP request. This step is
superfluous if there is only one StreamInputNode
.
The data streams generated in the StreamOutputNode
s are sent back to the client as MTOM
attachments in the SOAP reply. It is possible to define multiple StreamOutputNode
s.
The order in which the StreamOutputNode
s are then called to attach their data streams onto the
SOAP reply is random.
In order to embed job templates in a SOAP request (see section the section called “Job Definition Within the SOAP message”), the root element job
needs to be removed; its content is attached to the element configuration
in the
SOAP request. In order to declare the job type in the Java code (see Example 6.2, “Configuring and Executing a Job”),
the XML attribute type
is used.
Since the webservice interface is clearly defined through the WSDL, freely available webservice libraries can process this definition and generate proxy classes from it. These proxy classes can package the necessary SOAP requests and therefore enable an efficient development of client applications. This section shows this by looking at Sun's reference implementation of JAX-WS, the library Apache Axis2 and the library Apache CXF.
The distribution package of Java Development Kit (JDK) Version 1.7 contains the command line tool wsimport with which proxy classes can be generated. If the webservice of jadice server has been activated as described in the section called “Configuring the SOAP Interface”, the necessary client classes are generated with the following command:
<jdk1.8>\bin\wsimporthttp://localhost:9000/jadiceServer?wsdl
-keep
-extension
The parameter -keep
effectuates that not only classes but also their source codes are saved.
We recommend to work with these in further development. Figure 7.2, “Classes generated by JAX-WS”
shows the generated classes which can be incorporated into the developing environment.
The classes JadiceServerJobInvoker
and JobInvocationService
are entry points for a client application since they enable access to the SOAP interface. Another
essential class is JobConfiguration
with which the request is configured. A minimal
implementation can look as follows:
Example 7.6. Implementing a SOAP Client by way of a JAX-WS Reference Implementation
// Abstraction of the SOAP interface: JadiceServerJobInvoker invoker = new JadiceServerJobInvoker(); JobInvocationService service = invoker.getJobInvocationServiceImplPort(); // Configuration of the job JobConfiguration job = new JobConfiguration(); job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml"); // Optional: Apply a property (e.g. a timeout) Property timeout = new Property(); timeout.setName("timeout"); timeout.setValue("20000"); job.getProperty().add(timeout); // Attach input data (if the template defines a StreamInputNode) Stream inputStream = new Stream(); inputStream.setDocumentData(…); job.getStream().add(inputStream); // Submit the SOAP request (method is blocking!) JobResult result = service.run(job); // Retrieve result status ResultStatus status = result.getStatus(); for (Log log : result.getLogEntry()) { // Evaluate log entries... } // Retrieve and handle result data for (Stream stream : result.getStream()) { InputStream is = stream.getDocumentData().getInputStream(); … }
If the security interface of jadice server has been activated as described in the section called “Configuring the Security Interface”,
a security header has to be added to the SOAP request on the client's side.
The following example shows this with the help of the JAVA-WS reference implementation.
A SOAPHandler
generates the necessary security header:
Example 7.7. Implementing a SecuritySoapHeaderHandler
public class SecurityHeaderSoapHandler implements SOAPHandler<SOAPMessageContext> { private static final String SOAP_HEADER_SECURITY_NAMESPACE // = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; private static final String SOAP_HEADER_USER_TOKEN_NAMESPACE // = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; private static final String SOAP_HEADER_PASSWORD_TYPE_NAMESPACE // = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"; @Override public boolean handleMessage(SOAPMessageContext context) { final Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty.booleanValue()) { final SOAPMessage message = context.getMessage(); try { SOAPEnvelope envelope = message.getSOAPPart().getEnvelope(); SOAPHeader header = envelope.getHeader(); SOAPElement security = header.addChildElement("Security", "wsse", SOAP_HEADER_SECURITY_NAMESPACE); SOAPElement usernameToken = security.addChildElement("UsernameToken", "wsse"); usernameToken.addAttribute(new QName("xmlns:wsu"), SOAP_HEADER_USER_TOKEN_NAMESPACE); SOAPElement username = usernameToken.addChildElement("Username", "wsse"); username.addTextNode("myUsername"); SOAPElement password = usernameToken.addChildElement("Password", "wsse"); password.setAttribute("Type", SOAP_HEADER_PASSWORD_TYPE_NAMESPACE); password.addTextNode("myPassword"); } catch (Exception e) { // Causes the JAX-WS runtime to cease handler processing and generate a fault throw new RuntimeException("Cannot apply username token", e); } } return outboundProperty; }
This handler is assigned to the invoker with the help of a HandlerResolver
.
In order to assign the handler, the following segment of the client code needs to be adjusted:
Example 7.8. Implementing a Security Header by way of a JAX-WS Reference Implementation
// Abstraction of the SOAP interface: JadiceServerJobInvoker invoker = new JadiceServerJobInvoker(); invoker.setHandlerResolver(new HandlerResolver() { @Override public List<Handler> getHandlerChain(PortInfo portInfo) { List<Handler> handlerChain = new ArrayList<Handler>(); SecurityHeaderSoapHandler securtiyHandler = new SecurityHeaderSoapHandler(); handlerChain.add(securtiyHandler); return handlerChain; } }); JobInvocationService service = invoker.getJobInvocationServiceImplPort();
Apache Axis2 is available under Apache License under http://axis.apache.org/axis2/java/core/. If the webservice of jadice server has been activated as described in section the section called “Configuring the SOAP Interface”, the necessary client classes are generated with the following command:
<AXIS2_HOME>\bin\wsdl2java-o generatedCode
-p com.levigo.jadice.server.ws.client.axis2.stub
-d jaxbri
-uri http://localhost:9000/jadiceServer?wsdl
The use of the parameters -o
for the output directory and -p
for the
designated package name are optional. The parameter -d
determines which data binding shall be
used for the conversion from / to XML. The Apache Axis Data Binding (ADB) is the default binding. However, in its
current
version it struggles with the deserialization of SOAP/MTOM attachments, which is why we recommend using the JAX-B
reference
implementation (jaxbri
) instead.
The classes JadiceServerJobInvokerStub
and Run
are entry points for a client application since they enable access to the SOAP interface. Another
essential class is JobConfiguration
with which the request is configured. A minimal
implementation can look as follows:
Example 7.9. Implementing a SOAP Client by way of Apache Axis2
// Abstraction of the SOAP interface: JadiceServerJobInvokerStub invoker = new JadiceServerJobInvokerStub(); // Configuration of the job JobConfiguration job = new JobConfiguration(); job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml"); // Optional: Apply a property (e.g. a timeout) Property timeout = new Property(); timeout.setName("timeout"); timeout.setValue("20000"); job.getProperty().add(timeout); // Attach input data (if the template defines a StreamInputNode) Stream inputStream = new Stream(); inputStream.setDocumentData(…); job.getStream().add(inputStream); // Configure the SOAP request Run run = new Run(); run.setJob(job); // Submit the SOAP request (method is blocking!) RunResponse response = invoker.run(run); // Retrieve the result object JobResult result = response.getReturn(); // Retrieve result status ResultStatus status = result.getStatus(); for (Log log : result.getLogEntry()) { // Evaluate log entries... } // Retrieve and handle result data for (Stream stream : result.getStream()) { InputStream is = stream.getDocumentData().getInputStream(); … }
If the security interface of jadice server has been activated as described in the section called “Configuring the Security Interface”, a security header has to be added to the SOAP request on the client side. The following example shows this implementation by way of Apache Axis2.
Example 7.10. Generating a Security Headers
private static final String SOAP_HEADER_SECURITY_NAMESPACE = // "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; private static final String SOAP_HEADER_USER_TOKEN_NAMESPACE = // "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; private static final String SOAP_HEADER_PASSWORD_TYPE_NAMESPACE = // "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"; private static OMElement createSecurityHeader() { OMFactory omFactory = OMAbstractFactory.getOMFactory(); OMElement omSecurityElement = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Security", "wsse")); omSecurityElement.addAttribute("mustUnderstand", "1", null); OMElement omUsertoken = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "UsernameToken", "wsse")); omUsertoken.declareNamespace(SOAP_HEADER_USER_TOKEN_NAMESPACE, "wsu"); OMElement omUserName = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Username", "wsse")); omUserName.setText("myUsername"); OMElement omPassword = omFactory.createOMElement(new QName(SOAP_HEADER_SECURITY_NAMESPACE, "Password", "wsse")); omPassword.addAttribute("Type", SOAP_HEADER_PASSWORD_TYPE_NAMESPACE, null); omPassword.setText("myPassword"); omUsertoken.addChild(omUserName); omUsertoken.addChild(omPassword); omSecurityElement.addChild(omUsertoken); return omSecurityElement; }
The generated header is assigned to be used for each webservice request with the help of the following
adjustments to the webservice code on the client side:
Example 7.11. Implementing a Security Headers by way of Apache Axis2
// Abstraction of the SOAP interface:
JadiceServerJobInvokerStub invoker = new JadiceServerJobInvokerStub();
ServiceClient _stub = invoker._getServiceClient();
_stub.addHeader(createSecurityHeader());
Apache CXF is available at http://cxf.apache.org/ and is under Apache License. If the webservice of jadice server has been activated as described in the section called “Configuring the SOAP Interface”, the following command generates the necessary client classes:
<CXF_HOME>\bin\wsdl2java-d generatedCode
-p com.levigo.jadice.server.ws.client.cxf
http://localhost:9000/jadiceServer?wsdl
Using the parameters -d
for the output directory and -p
for the
package name to be used are optional.
The classes JadiceServerJobInvoker
and Run
are entry points for
a client application since they enable the access to the SOAP interface. The class JobConfiguration
is also essential as the request is configured with its help. A minimal implementation solution can look as follows:
Example 7.12. Implementing a SOAP-Client by way of Apache CXF
// Abstraction of the SOAP interface: JadiceServerJobInvoker invoker = new JadiceServerJobInvoker(); JobInvocationService service = invoker.getJobInvocationServiceImplPort(); // Configuration of the job JobConfiguration job = new JobConfiguration(); job.setTemplateLocation("resource:/jobtemplates/x2pdf.xml"); // Optional: Apply a property (e.g. a timeout) Property timeout = new Property(); timeout.setName("timeout"); timeout.setValue("20000"); job.getProperty().add(timeout); // Attach input data (if the template defines a StreamInputNode) Stream inputStream = new Stream(); inputStream.setDocumentData(…); job.getStream().add(inputStream); Run run = new Run(); run.setJob(job); // Submit the SOAP request (method is blocking!) RunResponse response = service.run(run); // Retrieve the result object JobResult result = response.getReturn(); // Retrieve result status ResultStatus status = result.getStatus(); for (Log log : result.getLogEntry()) { // Evaluate log entries... } // Retrieve and handle result data for (Stream stream : result.getStream()) { InputStream is = stream.getDocumentData().getInputStream(); … }
If the security interface of jadice server has been activated as described in the section called “Configuring the Security Interface”, a security header has to be added to the SOAP request on the client side. In order to do so, you can use the same syntax as in the JAX-WS reference implementation, see the section called “JAX-WS Reference Implementation”