This document aims to provide basic information on using the XRd4J-library for building and an application that is capable of communicating with X-Road. The document is divided into two parts:
- Client – describes the basic requirements for an application that is capable of producing requests to and processing responses from X-Road;
- Server – describes the basic requirements for an application that is capable for processing requests from and producing responses for X-Road.
The X-Road-capability of applications within the context of this document refers to the capability of producing and consuming requests and responses compatible with the X-Road: Message Protocol v4.0 utilizing the XRd4J-library.
The examples and code in this document present a simple and minimal implementation that is meant to overview the basic structure and idea of general adapter functionality. The examples do not constitute a reference/recommended implementation for a service adapter.
A client application must implement two classes:
request serializer
is responsible for converting the object representing the request payload to SOAP- extends
AbstractServiceRequestSerializer
- serializes all other parts of the SOAP message except request data content
- used through
ServiceRequestSerializer
interface - must implement
serializeRequest
method that serializes request data content to SOAP
- extends
response deserializer
parses the incoming SOAP response message and constructs the objects representing the response payload- extends
AbstractResponseDeserializer<?, ?>
- deserializes all the other parts of the incoming SOAP message except request and response data content
- type of the request and response data must be given as type parameters
- used through
ServiceResponseSerializer
interface - must implement
deserializeRequestData
anddeserializeResponseData
methods
- extends
N.B. If HTTPS is used between the client and the Security Server, the public key certificate of the Security Server
MUST be imported into cacerts
keystore. Detailed instructions here.
Main class (generated request, received response):
// Security server URL
// N.B. If you want to use HTTPS, the public key certificate of the Security Server
// MUST be imported into "cacerts" keystore
String url = "http://security.server.com/";
// Consumer that is calling a service
ConsumerMember consumer = new ConsumerMember("FI_TEST", "GOV", "1234567-8", "TestSystem");
// Producer providing the service
ProducerMember producer = new ProducerMember("FI_TEST", "GOV", "9876543-1", "DemoService", "helloService", "v1");
producer.setNamespacePrefix("ts");
producer.setNamespaceUrl("http://test.x-road.fi/producer");
// Create a new ServiceRequest object, unique message id is generated by MessageHelper.
// Type of the ServiceRequest is the type of the request data (String in this case)
ServiceRequest<String> request = new ServiceRequest<String>(consumer, producer, MessageHelper.generateId());
// Please uncomment the following line to enable procession of "request" and "response" wrappers.
// request.setProcessingWrappers(true);
// Set username
request.setUserId("jdoe");
// Set request data
request.setRequestData("Test message");
// Application specific class that serializes request data
ServiceRequestSerializer serializer = new HelloServiceRequestSerializer();
// Application specific class that deserializes response data
ServiceResponseDeserializer deserializer = new HelloServiceResponseDeserializer();
// Create a new SOAP client
SOAPClient client = new SOAPClientImpl();
// Send the ServiceRequest, result is returned as ServiceResponse object
ServiceResponse<String, String> serviceResponse = client.send(request, url, serializer, deserializer);
// Print out the SOAP message received as response
System.out.println(SOAPHelper.toString(serviceResponse.getSoapMessage()));
// Print out only response data. In this case response data is a String.
System.out.println(serviceResponse.getResponseData());
// Check if response contains an error and print it out
if (serviceResponse.hasError()) {
System.out.println(serviceResponse.getErrorMessage().getErrorMessageType());
System.out.println(serviceResponse.getErrorMessage().getFaultCode());
System.out.println(serviceResponse.getErrorMessage().getFaultString());
System.out.println(serviceResponse.getErrorMessage().getFaultActor());
}
HelloServiceRequestSerializer
(serialized request):
/**
* This class is responsible for serializing request data to SOAP.
*/
public class HelloServiceRequestSerializer extends AbstractServiceRequestSerializer {
@Override
/**
* Serializes the request data.
* @param request ServiceRequest holding the application specific request object
* @param soapRequest SOAPMessage's request object where the request data is added
* @param envelope SOAPMessage's SOAPEnvelope object
*/
protected void serializeRequest(ServiceRequest request, SOAPElement soapRequest, SOAPEnvelope envelope) throws SOAPException {
// Create element "name" and put request data inside the element
SOAPElement data = soapRequest.addChildElement(envelope.createName("name"));
data.addTextNode((String) request.getRequestData());
}
}
HelloServiceRequestSerializer
generates name
element and sets request data ("Test message") as its value.
<ts:helloService xmlns:ts="http://test.x-road.fi/producer">
<ts:name>Test message</ts:name>
</ts:helloService>
HelloServiceResponseDeserializer
(response to be deserialized):
/**
* This class is responsible for deserializing request and response data content of the SOAP response message
* returned by the Security Server. The type declaration "<String, String>" defines the type of request and
* response data, in this case they're both String.
*/
public class HelloServiceResponseDeserializer extends AbstractResponseDeserializer<String, String> {
@Override
/**
* Deserializes the request data content
* @param requestNode request node containing request data
* @return request data content, in this case the name element content
*/
protected String deserializeRequestData(Node requestNode) throws SOAPException {
// Loop through all the children of the request node
for (int i = 0; i < requestNode.getChildNodes().getLength(); i++) {
// We're looking for "name" element
if (requestNode.getChildNodes().item(i).getLocalName().equals("name")) {
// Return the text content of the element
return requestNode.getChildNodes().item(i).getTextContent();
}
}
// No "name" element was found, return null
return null;
}
@Override
/**
* Deserializes response data content.
* @param responseNode response node containing response data
* @param message SOAP response
* @return response data content, in this case the message element content
*/
protected String deserializeResponseData(Node responseNode, SOAPMessage message) throws SOAPException {
// Loop through all the children of the response node
for (int i = 0; i < responseNode.getChildNodes().getLength(); i++) {
// We're looking for "message" element
if (responseNode.getChildNodes().item(i).getLocalName().equals("message")) {
// Return the text content of the element
return responseNode.getChildNodes().item(i).getTextContent();
}
}
// No "message" element was found, return null
return null;
}
}
HelloServiceResponseDeserializer
's deserializeRequestData
method reads name
elements's value ("Test message")
under request content:
<ts:helloService xmlns:ts="http://test.x-road.fi/producer">
<ts:name>Test message</ts:name>
</ts:helloService>
and deserializeResponseData
method reads message
element's value ("Hello Test message! Greetings from adapter
server!") under response content:
<ts:helloServiceResponse xmlns:ts="http://test.x-road.fi/producer">
<ts:message>Hello Test message! Greetings from adapter server!</ts:message>
</ts:helloServiceResponse>
If compatibility mode for request and response wrappers is turned on, the helloService
and helloServiceResponse
elements would have request
and/or response
wrapper elements and the data elements would be placed within them.
The server returns images as base64 coded strings that are placed in SOAP attachments. Before being able to use the image the client must convert the base64 coded string to some other format. For example:
// Get the attached base64 coded image string
AttachmentPart attachment = (AttachmentPart) soapResponse.getAttachments().next();
// Get content-type of the attachment - if jpeg image, the content type is "image/jpeg"
String contentType = attachment.getContentType();
// Get the attachment as a String
String imgStr = SOAPHelper.toString(attachment);
// Convert base64 coded image string to BufferedImage
BufferedImage newImg = MessageHelper.decodeStr2Image(imgStr);
// Write the image to disk or do something else with it
// Content type must be set from "image/jpeg" to "jpg"
ImageIO.write(newImg, contentType, new File("/image/path"));
Server application must implement three classes:
servlet
is responsible for processing all the incoming SOAP requests and returning a valid SOAP response- extends
AbstractAdapterServlet
- serializes and deserializes SOAP headers, handles error processing
- incoming requests are passed as
ServiceRequest
objects - outgoing responses must be returned as
ServiceResponse
objects - must implement
handleRequest
andgetWSDLPath
methods
- extends
request deserializer
parses the incoming SOAP request message and constructs the objects representing the request payload- extends
AbstractCustomRequestDeserializer<?>
- type of the request data must be given as type parameter
- used through
CustomRequestDeserializer
interface - must implement
deserializeRequest
method that deserializes the request data content
- extends
response serializer
- extends
AbstractServiceResponseSerializer
- is responsible for converting the object representing the response payload to SOAP
- used through
ServiceResponseSerializer
interface - must implement
serializeResponse
method that serializes the response data content to SOAP
- extends
A working example of an application implementing a test service with a server adapter utilizing XRd4J can be found under
directory example-adapter
(documentation). It implements the adapter server with four
classes:
servlet
:- ExampleAdapter
- Implements the servlet requirement of the server adapter. It provides handling for example requests as well as
the
getWsdlPath
method.
- Implements the servlet requirement of the server adapter. It provides handling for example requests as well as
the
- ExampleAdapter
request deserializer
:- CustomRequestDeserializerImpl
- Implements the required request deserializer that searches for the expected
name
element containing request data necessary for the creation of thehelloService
response.getRandom
response can be constructed without parsing further data from the request.
- Implements the required request deserializer that searches for the expected
- CustomRequestDeserializerImpl
response serializer
:- ServiceResponseSerializerImpl
- Implements the required response serialization for the
getRandom
response. It creates adata
element for the response value in the response message.
- Implements the required response serialization for the
- HelloServiceResponseSerializer
- Another implementation for response serialization that is used for creating the
message
element for thehelloService
response data.
- Another implementation for response serialization that is used for creating the
- ServiceResponseSerializerImpl
With these class implementations the example adapter creates a server adapter that has two extremely simple built-in services responding to two types of service requests.
With default settings, the request and response messages would look something like these examples:
If the server needs to return images, this can be done converting images to base64 coded strings and returning them as SOAP attachments. For example:
// "img" can be BufferedImage or InputStream
// Image is converted to a base64 coded string, image type must be given
String imgstr = MessageHelper.encodeImg2String(img, "jpg");
// Image string is added as an attachment, content type must be set
AttachmentPart ap = response.getSoapMessage().createAttachmentPart(imgstr, "jpg");
// Set Content-Type header - Security Server does not accept "jpg" and "image/jpeg" can not
// be used with createAttachmentPart because "imgstr" is not an image object. Therefore the
// content type "image/jpeg" must be set afterwards
ap.addMimeHeader("Content-Type", "image/jpeg");
// The attachment is added to the SOAP message
response.getSoapMessage().addAttachmentPart(ap);