Example 2 - Working with Lists

In the following example a custom field mapper class is used to implement encoding and decoding of a struct which aggregates a list of another struct. The list is encoded into a single string whereby the first 4 characters contain a number specifying the number of entries in the list and the remainder of the string consists of the encoded form of each struct as a fixed length string. The main purpose of this example is to illustrate how list aggregations are handled when implementing a custom mapper class.

As with the previous example, this example only shows the implementation at the Cúram end of the queue. The remote system will also need to understand the encoding method and implement the necessary translations using the language of choice on the remote system.

The following pseudo code describes the struct being used in the operation. Struct PersonDtls will be encoded as a fixed length 18 character string. Struct PersonDtlsList will be encoded by encoding each struct in its list, concatenating the results into a string, and prefixing the string with a six character string specifying the number of entries in the list.

Figure 1. Pseudo code for the structs to be mapped:
struct PersonDtls {
  String<8> idNumber;
  String<10> surname;
}

struct PersonDtlsList {
  sequence <PersonDtls> dtls;
}

Method processNames of class LegacyBPO sends a PersonDtlsList struct to a legacy system, the legacy system will perform some processing on this data and return an updated copy of PersonDtlsList.

Figure 2. Pseudo code for the BPO interface
interface LegacyBPO {
  PersonDtlsList processNames(p1 PersonDtlsList);
}

Again, as in the previous example, field dtls of struct PersonDtlsList is being used in two cases: once in the parameter to operation processNames and once in the return value from the operation. Therefore the custom mapper class must be specified for each of these cases in QueueConnectorFieldMappers.properties (the lines have been split for clarity).

Figure 3. The property file entry linking the fields to the mapper
LegacyBPO.processNames.p1.dtls=
  com.acme.mqutils.PersonDtlsListMapper
LegacyBPO.processNames.return.dtls=
  com.acme.mqutils.PersonDtlsListMapper

The following listing shows the implementation of the custom mapper class.

Figure 4. Mapper class implementation for list of structs
package com.acme.mqutils;

// implementation
public class PersonDtlsListMapper {

  /**
   * The size of a prefix at the beginning of the string
   * which specifies the number of encoded entries in the
   * remainder of the string.
   */
  private static final int kStringHeaderInfoLength = 4;

  /**
   * The number of characters used to encode one
   * 'PersonDtls' struct.
   */
  private static final int kLengthOfOneEncodedStruct = 18;

  /**
   * Encodes the 'dtls' member into a string. The first 4
   * characters contain the number of items in the list, the
   * rest of the string consists of the encoded version of
   * each struct in the list concatenated together.
   *
   * @param object the object containing the field to be
   *         encoded
   * @throws AppException if it couldn't be encoded
   * @return A encoded string.
   */
  public String encode(Object object) throws AppException {

    PersonDtlsList.List_dtls d = null;

    try {
      // get a reference to the field within the struct
      // to be encoded
      d = (PersonDtlsList.List_dtls)
        getMappedField().get(object);
    } catch (IllegalAccessException e) {
       // use the handler in the superclass to deal with
       // this exception:
      handleEncodingException(e, object);
    }

    // construct the prefix which will specify the number
    // of items in the list.
    int bufferLength = d.size();
    String sizeSpecifierString =
      String.valueOf(bufferLength);

    // apply padding to make it the right size
    sizeSpecifierString =
      MQUtils.padRight(
        sizeSpecifierString, kStringHeaderInfoLength);

    // Now go through the items in the
    // list and encode each one.
    String data = "";
    for (int i = 0; i < d.size(); i++) {
      PersonDtls currentItem = d.item(i);
      data += encodeOneEntry(currentItem);
    }

    // put the prefix and the data together.
    String result = sizeSpecifierString + data;
    return result;
  }

  /**
   * Decodes a series of PersonDtls entries in the string
   * and adds them to field PersonDtlsList.List_dtls in the
   * given PersonDtlsList object.
   *
   * @param object The class containing the field to be decoded
   * @param encodedString the string containing the field data
   * @return a number indicating the number of characters decoded
   * @throws AppException if the string could not be decoded.
   */
  public int decode(Object object, String encodedString)
  throws AppException {

    PersonDtlsList.List_dtls dtls = null;

    // Get a reference to the list field within the object.
    // Note that we will be adding to this rather than
    // reassigning it.
    try {
      dtls = (PersonDtlsList.List_dtls)
        getMappedField().get(object);
    } catch (IllegalAccessException e) {
       // use the handler in the superclass to deal with
       // this exception:
      handleEncodingException(e, object);
    }

    // find out how many entries to be decoded.
    String header =
      encodedString.substring(0, kStringHeaderInfoLength);
    int numOfEntries =
      Integer.valueOf(header.trim()).intValue();

    // skip over the header.
    int chunkBegin = kStringHeaderInfoLength;

    // take chunks from the encoded string,
    // decode each one into an instance of the
    // struct, then add the struct to the list.
    for (int i = 0 ; i < numOfEntries; i++) {

      int chunkEnd = chunkBegin + kLengthOfOneEncodedStruct;
      String currentChunk =
        encodedString.substring(chunkBegin, chunkEnd);
      // encode one struct...
      PersonDtls newItem = decodeOneEntry(currentChunk);

      // and add it to the list:
      dtls.add(newItem);

      chunkBegin = chunkEnd;
    }

    // tell the caller the number of characters we consumed
    // from the encoded message.
    return chunkBegin;
  }

  /**
   * Encodes the struct into a string. Each field is padded
   * out to its maximum size, and the fields are concatenated
   * together to yield the result.
   *
   * @param d the struct to be encoded.
   * @return an encoded string containing the struct data.
   * @throws AppException If a field was too big to encode
   */
  private String encodeOneEntry(PersonDtls d)
  throws AppException {

    String result
      = MQUtils.padRight(d.idNumber, 8)
      + MQUtils.padRight(d.surname, 10);

    return result;
  }

  /**
   * Decodes a string into an instance of the struct - does
   * the inverse of encodeOneEntry
   *
   * @param encodedEntry an encoded struct
   * @return a new instance of the struct
   * @see private String encodeOneEntry(PersonDtls)
   */
  private PersonDtls decodeOneEntry(String encodedEntry) {

    PersonDtls result = new PersonDtls();

    result.idNumber = encodedEntry.substring(0, 8).trim();
    result.surname = encodedEntry.substring(8, 18).trim();

    return result;
  }
}

For example, the following list of Ent18131 structs:

would be encoded as follows:

        "3   0000361iJames
 0024684xJohn      8211519fSharon    "

where the first four characters contain a number specifying the number of encoded structs to follow, and the remaining string consists of three 18 character blocks corresponding to the three encoded structs.