Validating XML against Schema

Introduction

In this section you start developing the adapter that the imaginary company New Horizons needs, continuing the case study started in section Example: New Horizons. The adapter will process an XML document with a valid booking, see section New Horizons Requirements for an example. It will write the booking to the database tables “booking” and “visit” you created in section Database Initialization. If you did not do the previous sections, you can download that work and continue here from your download.

The ingest booking adapter

Before doing something with a booking XML, the ingest booking adapter should check that this document is valid. In this section you will write a first version of the ingest booking adapter that only does that. Please do the following:

  1. The validity of an XML documents is usually checked using an XML schema, see https://www.w3schools.com/xml/schema_intro.asp. Please make a document NewHorizons/booking.xsd and give it the following contents:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="booking">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="travelerId" type="xs:integer"/>
            <xs:element name="price" type="money"/>
            <xs:element name="fee" type="money"/>
            <xs:element name="destination" minOccurs="1" maxOccurs="unbounded" type="destination" />
          </xs:sequence>
          <xs:attribute name="id" type="xs:integer"></xs:attribute>
        </xs:complexType>
      </xs:element>
      <xs:simpleType name="money">
        <xs:restriction base="xs:decimal">
          <xs:fractionDigits value="2" />
        </xs:restriction>
      </xs:simpleType>
      <xs:complexType name="destination">
        <xs:all>
          <xs:element name="price" type="money"/>
          <xs:element name="startDate" type="xs:date"/>
          <xs:element name="endDate" type="xs:date"/>
        </xs:all>
        <xs:attribute name="hostId" type="xs:integer"/>
        <xs:attribute name="productId" type="xs:integer"/>
      </xs:complexType>
    </xs:schema> 
    

    Note

    This schema does not check all possible requirements for a booking to be valid. It does not check that the start date of a visit is before its end date. More advanced checks are possible, but then you need features that are new in XML Schema version 1.1. These features are explained at https://www.altova.com/blog/what-s-new-in-xml-schema-11/. The Frank!Framework supports XML Schema 1.1, but you need a commercial text editor to use the new features. With a free text editor, you will not have syntax checking while working on your advanced XSD file.

    To have syntax checking with a free text exitor, you have to stick to XML Schema version 1.0. We do so in this tutorial.

  2. Extend Configuration.xml as shown below:

    <Configuration
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../FrankConfig.xsd">
      <Adapter name="IngestBooking">
        <Receiver name="input">
          <ApiListener
              name="inputListener"
              uriPattern="booking"
              method="POST"/>
        </Receiver>
      </Adapter>
    </Configuration>
    

The adapter starts with a <Receiver> that contains an <ApiListener>. The choice for <ApiListener> makes the adapter listen to REST HTTP requests. The attribute method="POST" makes it listen to HTTP POST requests. The uriPattern="booking" attribute defines the relative path to which the adapter listens. The Frank!Framework extends this path to be http://localhost/api/booking.

  1. The Frank!Framework defines a pipe <XmlValidatorPipe> that checks the incoming message against an XML Schema. We use it in our adapter. Please update Configuration.xml as shown:

    <Configuration
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../FrankConfig.xsd">
      <Adapter name="IngestBooking">
        <Receiver name="input">
          <ApiListener
              name="inputListener"
              uriPattern="booking"
              method="POST"/>
        </Receiver>
        <Pipeline firstPipe="checkInput">
          <Exits>
            <Exit name="Exit" state="SUCCESS" code="201" />
            <Exit name="BadRequest" state="ERROR" code="400" />
          </Exits>
          <XmlValidatorPipe
              name="checkInput"
              root="booking"
              schema="booking.xsd">
            <Forward name="success" path="Exit" />
            <Forward name="failure" path="BadRequest" />
          </XmlValidatorPipe>
        </Pipeline>
      </Adapter>
    </Configuration>
    

The attributes root and schema are used to reference the expected root element of the incoming XML and to reference the XML schema file booking.xsd presented in step 1. A <Forward> tag links a forward name to a path. On success, we go to the pipeline exit having name Exit, finishing execution. The <Pipeline> tag contains an <Exit> tag that links path Exit to exit state SUCCESS and HTTP status code 201.

The <XmlValidatorPipe> supports another forward name failure that is followed when validation fails. It is linked to forward name “BadRequest” at this point, corresponding to exit state ERROR and code 400.

  1. The <XmlValidatorPipe> echos its input message to its output message, both if validation succeeds and if validation fails. We want an error message if we receive an invalid booking message. Please update Configuration.xml as follows:

    ...
          <XmlValidatorPipe
              name="checkInput"
              root="booking"
              schema="booking.xsd">
            <Forward name="success" path="Exit" />
            <Forward name="failure" path="makeInvalidBookingError" />
          </XmlValidatorPipe>
          <FixedResultPipe
              name="makeInvalidBookingError"
              returnString="Input booking does not satisfy booking.xsd">
            <Forward name="success" path="BadRequest"/>
          </FixedResultPipe>
        </Pipeline>
      </Adapter>
    </Configuration>
    

Forward name failure is linked to the pipe named makeInvalidBookingError. This pipe replaces the incoming message by an error message. The fixed result pipe never fails and follows its (predefined) forward name success. That forward points to path BadRequest.

Testing (Windows)

Your adapter listens to REST HTTP requests. If you are working under Windows, you can use Postman to send HTTP requests to your adapter. Please do the following:

  1. Install Postman from https://www.getpostman.com/downloads/ if you do not have it.

  2. Start Postman.

  3. Press the cog wheen shown in the picture below. It is at the top of the window.

    ../_images/postmanCogwheel.jpg
  4. Go to Settings. In the dialog that appears select tab General.

  5. Ensure that “SSL certificate verification” is not checked, see figure below:

    ../_images/postmanSettings.jpg
  6. Close this dialog.

  7. Press the “New” button shown below. It is almost at the top of the window.

    ../_images/postmanNewRequest.jpg
  8. Click “HTTP Request”.

  9. Select method POST (number 1 in the figure below) and type URL http://localhost/api/booking (number 2).

    ../_images/postmanUrl.jpg
  10. Select tab “Headers” (number1 in the figure below). Add header Content-Type (number 2) with value application/xml (number 3) and select it (number 1):

    ../_images/postmanHeaders.jpg
  11. Select tab “Body” (number 1 in the figure below).

    ../_images/postmanSend.jpg
  12. Select type “raw” (number 2).

  13. In the message field (number 3), copy/paste the following XML:

    <booking id="1">
      <travelerId>2</travelerId>
      <price>500.00</price>
      <fee>100.00</fee>
      <destination hostId="3" productId="4">
        <price>400.00</price>
        <startDate>2018-12-27</startDate>
        <endDate>2019-01-02</endDate>
      </destination>
    </booking>
    
  14. Press “Send” (number 4 in the figure).

  15. The response appears under the request, still in the “Body” tab. It equals the request body. You also see the response code, which is annotated in the figure below.

    ../_images/postmanResponse.jpg

Testing (Linux)

Under Linux, you can test your adapter as follows:

  1. Copy the valid booking XML listed above (subsection Testing (Windows)) to some file on your computer, say validBooking.xml.

  2. Execute the following Linux shell command:

    curl -i -X POST -H 'Content-Type: application/xml' -d @validBooking.xml http://localhost/api/booking
    
  3. The output will be something like the following:

    HTTP/1.1 201 Created
    Server: Apache-Coyote/1.1
    Last-Modified: Wed, 16 Oct 2019 12:39:06 GMT
    Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
    Pragma: no-cache
    Allow: OPTIONS, POST
    Content-Type: */*;charset=UTF-8
    Content-Length: 247
    Date: Wed, 16 Oct 2019 12:39:06 GMT
    
    <booking id="1">  <travelerId>2</travelerId>  <price>500.00</price>  <fee>100.00</fee>  <destination hostId="3" productId="4">    <price>400.00</price>    <startDate>2018-12-27</startDate>    <endDate>2019-01-02</endDate>  </destination></booking>
    

The HTTP status code 201 is the code attribute defined with exit state SUCCESS. To the bottom, you see that the incoming XML is echoed in the body of the response.

Final remarks (Windows and Linux)

The HTTP request includes a HTTP header Content-Type: application/xml. You need this header because the ingest booking adapter uses listener <ApiListener>. Use another listener if you want to omit the header from the request.

The exit name Exit corresponds to code 201 and state SUCCESS. This exit state SUCCESS does not appear in the HTTP response. You can see it if you use the “Test Pipeline” page in the console, see section Testing Pipelines.

Please test your adapter with XML documents that do not satisfy booking.xsd or with text that is not valid XML. You should see the message Input booking does not satisfy booking.xsd and HTTP status code 400.

Solution

If you are having troubles, you can download the solution for the work presented so far.