Writing a custom subject mapper

Typically when a user makes a subscription for ESP prices you want to return prices appropriate for that user’s tier and current volume band. In this tutorial we shall be looking at how subject mapping can be used to achieve this without the user becoming aware of the tier and volume band selected.

Download the presentations for this course.

Suffix Mapping

Suffix Mappings are a simple way to add user-specific attributes (such as the user’s tier) to a request. You shall now create a suffix mapping for your platform’s users (admin, user1, user2, user3 and demouser) so that their requests shall be altered by Liberator before being propagated to the PricingAdapter.

Follow the steps below:

  1. Use the code below in the PermissioningAdapter to map user1 and demouser to tier1 and user2 and user3 to tier2.

    Map<String, String> subjectMappings = new HashMap<String, String>();
    subjectMappings.put("/FX/.*", "/tier1");
    adminUser.addSubjectMapping("TIER_INFO", subjectMappings);
  2. Restart the Platform and make a new request for /FX/GBPUSD. Notice that now the Pricing Adapter’s console output includes requests received for /FX/GBPUSD/tier1. The auth module in Liberator is using the subject mapping on the user and is applying it before Liberator propagates the request to Transformer. Alas, the client is not receiving a price updates as the PricingAdapter is publishing messages to /FX/GBPUSD rather than /FX/GBPUSD/tier1 - this is something we shall correct in the next section.

This is only a partial solution for ESP prices, ideal for appending the tier. However, to determine and apply an additional mapping from the amount to the volume band we shall need to use a Custom Subject Mapper as described in the next section.

Using a Custom Subject Mapper

In this part of the tutorial you shall create a custom subject mapper in order to map the (trading) amount to a volume band and the user to a tier when making a request. Thus the client will make requests in the form:

/FX/<currency pair>/<amount>

This must be mapped before being sent to the PricingAdapter in the form:

/FX/<currency pair>/<volume band>/<tier>

Follow the steps below:

  1. Start by removing the user-specific subject mappings which you added to the PermissioningAdapter in the previous section. This will now be done by the custom subject mapper.

  2. Add the following line to the Pricing Adapter in Blade\Liberator\etc\rttpd.conf:

    object-map                  "/FX/%1/%2"             "/FX/%1/%2/%u"

    An object mapping changes the internal name of an object when a user requests it. So this line of configuration tells Liberator to append the username to any FX request made so that the request becomes:

    /FX/<currency pair>/<amount>/<username>.

  3. Export the PricingAdapter and deploy it to upgrade the deployed Liberator configuration.

  4. Next you must create the custom subject mapper. For this, create a new Java project called CustomSubjectMapper. Import the permissioning-datasource jar file into the build path of the project. (You will find this library in the CIS "lib" directory.)

  5. Create a new class called CustomSubjectMapper.java. This class must implement the SubjectMapper interface and its methods. The role of this class is to read the mappings off the GlobalContext and apply them to incoming requests' subjects.

  6. The setGlobalMappings() method is invoked when the PermissioningAdapter executes a transaction updating the GlobalContext with a new set of mappings. Add the implementation of this method as shown below to update the tier and volume band mappings which are each represented by a simple Map structure.

    //Class Fields
    public final static String TIER_INFO = "CustomSubjectMapper.TIER.INFO";
    public final static String VOLUME_BAND = "CustomSubjectMapper.VOLUME.BAND";
    private Map<String, String> volumeBands = new HashMap<String, String>();
    private Map<String, String> tiers = new HashMap<String, String>();
    
    @Override
    public void setGlobalContext(GlobalContext globalContext) {
      updateVolumeBands(globalContext);
      updateTierInfo(globalContext);
    }
    
    private void updateTierInfo(GlobalContext globalContext) {
      tiers.putAll(globalContext.get(TIER_INFO));
    }
    
    private void updateVolumeBands(GlobalContext globalContext) {
      volumeBands.putAll(globalContext.get(VOLUME_BAND));
    }
  7. The updated variables above (tiers and volumeBands) are used when the CustomSubjectMapper detects a request and needs to map this request. When this occurs the mapSubject() method is invoked to map the requested subject from the /FX/<currency pair>/<amount>/<user> to the /FX/<currency pair>/<volume band>/<tier> form:

    //Class Fields
    ...
    private static final String OUTGOING_SUBJECT_PATTERN = "/FX/%s/%s/%s";
    
    @Override
    public String mapSubject(String subject) {
      //map subject: /FX/GBPUSD/500/user1 to /FX/GBPUSD/1/tier1
      String[] tokens = subject.split("/");
      String currencyPair = tokens[2];
      String amount = tokens[3];
      String user = tokens[4];
    
      String volumeBand = getVolumeBand(amount);
      String tierUser = tiers.get(user);
    
      return String.format(OUTGOING_SUBJECT_PATTERN,
        currencyPair, volumeBand, tierUser);
    }
    
    private String getVolumeBand(String amount) {
      List<String> maxAmounts = new ArrayList<String>(volumeBands.keySet());
      Collections.sort(maxAmounts,
        new Comparator<String>() {
          @Override
          public int compare(String amt1, String amt2) {
              return Double.valueOf(amt1).compareTo(Double.valueOf(amt2));
          }
        });
      Integer amountEntered = new Integer(amount);
      for (String max : maxAmounts) {
        Integer maxAmount = new Integer(max);
        if (maxAmount > 0 && amountEntered <= maxAmount) {
          return volumeBands.get(maxAmount.toString());
        }
      }
      return volumeBands.get("0");
    }
  8. Having completed the implementation of the CustomSubjectMapper, you must now export this as a library to be used by the PermissioningAdapter.

  9. Assign this as the subject mapper for each of the users as follows:

    adminUser.setSubjectMapper(CustomSubjectMapper.class.getName());
  10. Also, create a new transaction that will update the GlobalContext with all the volume band and tier mappings:

    permissioningDataSource.startUpdateTransaction();
    Map<String, String> volumeBands = new HashMap<String, String>();
    volumeBands.put("1000", "1");
    volumeBands.put("10000", "2");
    volumeBands.put("100000", "3");
    volumeBands.put("0", "4");
    
    Map<String, String> tierInfo = new HashMap<String, String>();
    tierInfo.put("admin", "tier1");
    tierInfo.put("user1", "tier1");
    tierInfo.put("demouser", "tier1");
    tierInfo.put("user2", "tier2");
    tierInfo.put("user3", "tier2");
    
    permissioningDataSource.updateGlobalContext(
      CustomSubjectMapper.VOLUME_BAND, volumeBands);
    permissioningDataSource.updateGlobalContext(
      CustomSubjectMapper.TIER_INFO, tierInfo);
    permissioningDataSource.commitTransaction();
  11. Finally, copy the CustomSubjectMapper jar file to the \global_config\overrides\PermissioningService\Liberator\etc directory. Here, alter the pam_custom_subject_mapper.conf file to point the PermissioningService to this subject mapper:

    define PAM_CUSTOM_SUBJECT_MAPPER_CLASSPATH "${ccd}/CustomSubjectMapper.jar"

Adjusting the Pricing Adapter to support tiers and volume bands

The set of steps above will achieve the subject mapping so that the PricingAdapter receives a request in the form: /FX/<currency pair>/<volume band>/<tier>. The current implementation of the Pricing Adapter, however, does not take the latter two parts into account when requesting prices from the server.

Follow the steps below:

  1. Replace the current version of the SimpleFXMessenger library with this version in Blade\DataSource\lib directory and in the build path. This new library takes the volume band and tier into account, multiplying the actual price by 1.01 for each tier or volume band before sending the price update to the Pricing Adapter. (Therefore a request with a higher tier number or volume band will result in higher Bid and Ask prices than previously.)

  2. You can now make the following set of adjustments in the PricingAdapterPricingProvider.

    1. To subscribe:

      @Override
      public void onRequest(RequestEvent requestEvent) {
        ...
        if (subject.equals("/CONTAINER/FX/MAJOR")) {
          ...
        } else {
          String instrumentName = convertSubjectToInstrumentName(subject);
          Integer tier = getTier(subject);
          Integer vb = getVolumeBand(subject);
          messenger.subscribe(instrumentName, tier, vb);
          RecordType1Message initialMessage = publisher.getMessageFactory()
            .createRecordType1Message(requestEvent.getSubject());
          publisher.publishInitialMessage(initialMessage);
        }
      }
      
      private Integer getTier(String subject) {
        String[] splittedSubject = subject.split("/");
        return Integer.parseInt(splittedSubject[4].replace("tier", ""));
      }
      
      private Integer getVolumeBand(String subject) {
        String[] splittedSubject = subject.split("/");
        return Integer.parseInt(splittedSubject[3]);
      }
    2. To publish price updates:

      @Override
      public void onPriceUpdate(FXQuote fxQuote) {
        String instrumentName = fxQuote.getCurrencyPair();
        String tier = "tier" + fxQuote.getTier().toString();
        String volumeband = fxQuote.getVolumeBand().toString();
        String subject = convertInstrumentNameToSubject(
          instrumentName, tier, volumeband);
        RecordType1Message updateMessage =
          publisher.getMessageFactory().createRecordType1Message(subject);
        ...
        publisher.publishToSubscribedPeers(updateMessage);
      }
      
      private String convertInstrumentNameToSubject(String instrumentName,
          String tier, String volumeband) {
        String subject = "/FX/"
          + instrumentName.substring(0, 3)
          + instrumentName.substring(4, 7)
          + "/" + volumeband + "/" + tier;
        return subject;
      }
  3. Restart the framework and deployed adapters and you should now notice the difference in simultaneous prices for different volume band and tier requests:

    tutorial adv different volume bands