Skip to content

Latest commit

 

History

History
291 lines (234 loc) · 14.2 KB

adapter-implementation.md

File metadata and controls

291 lines (234 loc) · 14.2 KB

Implementing an X-Road adapter using XRd4J

Table of Contents

1 Introduction

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 v6 Message Protocol Version 4.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.

2 Client

2.1 Client Requirements

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
  • 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 and deserializeResponseData methods

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.

2.2 Client Example

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.

2.2.1 Receiving an Image from Server

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"));

3 Server

3.1 Server requirements

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 and getWSDLPath methods
  • 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
  • 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

3.2 Server example

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 helloService and getRandom requests as well as the getWsdlPath method.
  • request deserializer:
    • CustomRequestDeserializerImpl
      • Implements the required request deserializer that searches for the expected name element containing request data necessary for the creation of the helloService response. getRandom response can be constructed without parsing further data from the request.
  • response serializer:
    • ServiceResponseSerializerImpl
      • Implements the required response serialization for the getRandom response. It creates a data element for the response value in the response message.
    • HelloServiceResponseSerializer
      • Another implementation for response serialization that is used for creating the message element for the helloService response data.

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:

3.2.1 Returning an Image From Server

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);