Understanding Service Description XML

Introduction

I decided to create this page as a place where I can place notes and comments about building service description XML files.  If you know nothing about this topic then there are a few other resources that you might want to check out first:

Documentation on Service Descriptions. This content will describe the different parts of a service description.

Mapping two values to one output parameter

This is a use case that was just brought to my attention.  Let's say that you have a service that returns several values and within your application you want to present those values as a single field.  We can combine response values from your service call directly in the service description xml file.  In this example I created a service that returns information about a person and we want to surface the address information as an address block that can be rendered in a single multi-line field.  There are other ways that this can be accomplished, for example using JavaScript within your form but that would require that code to be used in every application that uses the service.  The technique I am proposing here is better because the solution is implemented once.

Let's dive in and look at how it works.

The XML instance that is returned by the service looks like this:

<people>
    <person>
        <fname>Chris</fname>
        <lname>Dawes</lname>
        <email>cdawes@ca.ibm.com</email>
        <addressBlock>
            <address1>123 Someplace Rd</address1>
            <address2>Suite 220</address2>
            <city>Victoria</city>
            <state>BC</state>
            <postalZip>V7T 4R5</postalZip>
            </addressBlock>
    </person>
</people>

The service outputs are:


<parameters>
            <parameter>
                <id>fname</id>
                <name xml:lang="en">First Name</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>INTEGER</type>
              </parameter>
              <parameter>
                <id>lname</id>
                <name xml:lang="en">Last Name</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
                    </parameter>
                    <parameter>
                <id>email</id>
                <name xml:lang="en">Email</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
                    </parameter>
              <parameter>
                <id>AddressBlock</id>
                <name xml:lang="en">Address Block</name>
                <description xml:lang="en">The entire address returned as one string.</description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>      
              <parameter>
                <id>address1</id>
                <name xml:lang="en">Address 1</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>
              <parameter>
                <id>address2</id>
                <name xml:lang="en">Address 2</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>
              <parameter>
                <id>City</id>
                <name xml:lang="en">City</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>
              <parameter>
                <id>State</id>
                <name xml:lang="en">State</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>
              <parameter>
                <id>zipPostal</id>
                <name xml:lang="en">Zip/Postal Code</name>
                <description xml:lang="en"></description>
                <mandatory>false</mandatory>
                <type>STRING</type>
              </parameter>
      </parameters>

All the service mappings for the parameters follow the same format as any of the other articles or documentation that I have already pointed out, for the sake of brevity I am only going to focus on the addressBlock that shows the concatenated values.  The service mapping looks like this:

<mapping source="transport:response-entity" sourceRef="concat(people/person/addressBlock/address1,'&#xA;',people/person/addressBlock/address2,'&#xA;',people/person/addressBlock/city,', ',people/person/addressBlock/state,'&#xA;',people/person/addressBlock/zipPostal)" sourceType="XML" target="parameter:AddressBlock" targetType="STRING"/>

When you inspect this you will see that the sourceRef is the attribute that is doing the heavy lifting.  The sourceRef and targetRef attributes support XPath queries.  This means that we can inject smart processing into our service descriptions to massage the result data before passing it back to our application.  In this case I use the concat function to build the "addressBlock" with all the related fields from my service response.  The string '&#xA;' is used to inject a new line into our content.

You could also use this technique to inject static strings or other content along with your service result data.

The serviceDescription is attached to this article, the service that it calls does not exist, but you can use it as a reference for using this technique in your own service descriptions.

Splitting One Response Value Into Two Parameters

This example uses a similar technique to the first but we are doing the opposite action.  Let's say for example that your service returns a value that you want to break out into more then one parameter:

<people>
    <person>
        <fullName>Dawes, Chris</fullName>
        <email>cdawes@ca.ibm.com</email>
        <addressBlock>
            <address1>123 Someplace Rd</address1>
            <address2>Suite 220</address2>
            <city>Victoria</city>
            <state>BC</state>
            <postalZip>V7T 4R5</postalZip>
            </addressBlock>
    </person>
</people>

I want to break apart the fullName value in to first name and last name parameters within my service description.  To use this technique we have to be sure of the format that the data is returned.  If the format changes then this technique may not make sense to use.  Here is what our mappings look like:

<mapping source="transport:response-entity" sourceRef="normalize-space(substring-after(people/person/fullName,','))" sourceType="XML" target="parameter:fname" targetType="STRING"/>
                <mapping source="transport:response-entity" sourceRef="substring-before(people/person/fullName, ',')" sourceType="XML" target="parameter:lname" targetType="STRING"/>

What you should notice about both of these mappings is that we are using more XPath functions.  In the case of the last name mapping we are using the substring-before function that will return the content of the string up to (but not including) the comma.  For the First name parameter I am explicitly using two functions for the sake of the example but it could be reduced to one.  I use the substring-after function to return the content after the  comma, this by itself will include a leading space.  I remove the space by using the normalize-space function, but I could have just used ', ' in my substring-after function to return the characters after the comma and space.

As you can start to see there is a lot of flexibility in the service description xml to introduce some intelligence when manipulating results that get returned from a service.  Sometimes it will make sense to place this logic in the service description xml file, but there may be other times where you should accomplish your "logic" in the app that consumes the service.Keep in mind that any logic in the service description xml file will affect every time it is called.

If you have a use case that requires more complex logic you can always review the documented XPath functions and use them to accomplish your task.  It is unclear to me at this time if the service description mechanism supports all of these functions, but the basic list can be viewed at w3schools. As more use cases become available I will add to this article.

Localizing a Service Description

You can create localized strings for your custom service descriptions.

1. Use a key for the service parameters that need localization, specifically any name and description element:

<name key="email.service.name"></name>
  <description key="email.service.description"></description>

and

  <parameter>
        <id>To</id>
        <type>STRING</type>
        <name key="email.parameter.to.name"></name>
        <description key="email.parameter.to.description"></description>
      </parameter>

2. Create a properties file for each locale by following the format <service description name>_<locale>.properties. The locale properties files for a service called "EmailServiceDescription" would be:

EmailServiceDescription_en.properties

EmailServiceDescription_en_US.properties

EmailServiceDescription_fr.properties

Note: if you create a properties file that does not have "_<local>" in its name then it will be used as the default.

3. Define all the keys in the each property file and assign the translated string:

email.service.name=Send Email Service
email.service.description=This service enables the user to send an email
email.parameter.to.name=To
email.parameter.to.description=Email addresses separated with comma

4. Define the default locale for the service:


<serviceDescription>
  <id>Send-Email</id>
  <defaultLocale>en-us</defaultLocale>

   . . .

</serviceDescription>


5. Place the service description XML file and the properties files in the ServiceCatalog\1 directory.

Additional details can be found in the HCL Leap documentation.

Sending an XML Instance as Request Body

Some services require a specific input instance to interact with them.  It is possible within a service description xml file to define a prototypical instance and then dynamically insert inbound parameters or constants using service mappings. 

1. Define the Inbound parameters:


<parameter>
      <id>title</id>
      <name xml:lang="en">Title</name>
      <description xml:lang="en">Title of the Blog post.</description>
      <mandatory>false</mandatory>
      <type>STRING</type>
</parameter>
<parameter>
       <id>blog-content</id>
       <name xml:lang="en">Blog Content</name>
       <description xml:lang="en">The content to post to the Blog.</description>
       <mandatory>false</mandatory>
       <type>STRING</type>
</parameter>

<parameter>
        <id>tags</id>
        <name xml:lang="en">Tags</name>
        <description xml:lang="en">The list of tags to be added to the blog post</description>
        <mandatory>false</mandatory>
        <type>LIST</type>
        <parameters>
              <parameter>
                   <id>tag</id>
                   <type>STRING</type>
                   <name xml:lang="en">Tag</name>
                   <description xml:lang="en">The tag to add to the blog post</description>
               </parameter>
         </parameters>
    </parameter>


2. Define the prototypical instance as an inbound constant:its not

<constant>
      <id>post-template</id>
     <value><![CDATA[<a:entry xmlns:a="http://www.w3.org/2005/Atom"><a:title type="text"></a:title><a:content type="html"></a:content></a:entry>]]></value>
</constant>

3. Assign the template to the transport:request-entity:


<mapping target="transport:request-entity" targetType="XML" sourceType="NOOP" source="constant:post-template" />

4. Inject inbound parameters into the prototypical instance before it gets sent:


<mapping sourceType="NOOP" target="transport:request-entity" targetType="XML">
       <mapping source="parameter:title" sourceType="STRING" targetRef="a:entry/a:title"/>
       <mapping source="parameter:blog-content" sourceType="STRING" targetRef="a:entry/a:content"/>

       <!-- This denotes that there is a list of tags that will create multiple category elements -->
       <!-- it inherits the target of request-entity -->
       <mapping source="parameter:tags" targetRef="a:entry/a:category">
              <mapping source="parameter:tag" sourceType="STRING" targetRef="@term"/>
       </mapping>
</mapping>


The targetRef uses XPath to reference a specific element in the prototypical instance and replace it with the source.  When this mapping gets resolved the XML that gets posted to the server looks like:

<a:entry xmlns:a="http://www.w3.org/2005/Atom"><a:title type="text">Content from title parameter</a:title><a:content type="html">Content from blog-content parameter</a:content></a:entry>

Injecting "tags" is a bit more complicated because that is a list and we need to make sure that all the items of the list get inserted into the instance.  The first part is to link the "tags" parameter to the repeating element in your instance, in this case <a:category term="" />.  Then we can link the inner tag parameter to the term attribute. The end result might be something like:

<a:entry xmlns:a="http://www.w3.org/2005/Atom"><a:title type="text">This is a sample blog post from an app!</a:title><a:content type="html">This is a sample blog post from an app!</a:content><a:category term="sample"/><a:category term="rest_api"/><a:category term="awesome"/></a:entry>