Skip to main content

Exchange Framework 1.5.0 upgrade guide

A standard Fluent Function base class

We provide a standard Fluent Function base class in this release for the purpose of integration.

The FluentFunction abstract base class implements an invoke() method which includes:

  • all initialized variables including logger, svcDef, txn, inputParams, user, appDoc, applicantRole, and fluent function result
  • setup service module
  • standard input parameters injected (recordResponseInTxnProperties, recordResponseInTxnXML)
  • standard milestone events on start and complete
  • standard exception handling and update server vo and client vo when required

As you can see, we are taking care of the most commonly used code for a typical integration fluent function and all you need to do is implement any of the following methods that are relevant to your specific project.

  • init(): This is where you set up product name, service session, Server VO and Client VO. For example,

    @Override
    void init() {
    this.productName = 'AuthenticID'
    // make sure we create different server VO on different task
    this.serverVO = new AuthenticIDIdvResultServerVO()
    this.clientVO = new AuthenticIDIdvResultClientVO()
    this.serviceSession = new AuthenticIDService()
    }
  • process(): This is where you setup the input object, connection object, and triggering the main method from a service class. This method is triggered after the service session is setup. For example:

    @Override
    void process() {
    // create AuthenticID input object based on input params
    AuthenticIDInput authenticidInput = new AuthenticIDInput(inputParams)

    // get service parameters here and create connection object
    String defaultAccountCode = svcDef.paramsMap.get('defaultAccountCode')?.toString()

    // create AuthenticID connection object
    AuthenticIDService authenticIDService = serviceSession as AuthenticIDService
    authenticIDService.setConnection(svcDef.svcConn, defaultAccountCode, authenticidInput)

    // execute the service call
    result = authenticIDService.idv(authenticidInput)

    }

There are some optional methods that you can override if needed.

  • preProcess(): A method that's triggered before service session is setup.
  • milestoneStart(): The standard milestone start event which should be sufficient for most Exchange integrations. You can override this if you have specific requirements for this milestone.
  • milestoneComplete(): The standard milestone complete event after the fluent function has been executed which should be sufficient for most Exchange integrations. You can override this if you have specific requirements for this milestone.

Response processor

Before this release, you needed to create a response processor subclass and implement the processResponse() method in order to process the raw response manually and generate Server VO and Client VO. In the 1.4.0 release, we introduced simple annotations to guide the response processor to inject certain values from the raw response into Server VO automatically.

Starting with this release, we provide a feature-rich response processor, so you no longer need to create one. We go much deeper, with many more auto response processing capabilities that finally provide a fully working response processor out of the box, removing the need for you to parse the response manually and saving you lots of time.

For example, instead of creating your own response processor, you can just use the standard one (below) provided by the Exchange Framework. And even better, we also provide a new processAndPersisVOs() method that lets the response processor handle the exceptions and persist the VOs in any case so you don't need to provide any code yourself in the service class.

class MyService extends TifService {

FormFuncResult verification(MyInput myInput) {
// calling third party api
HttpResponse response = MyApi.verification(myInput, myConn, logger)

TifResponseProcessor processor = new TifResponseProcessor(response.getTextContent(), response.getStatus(), role, recordResponseInTxnXML, new MyServerVO(), new MyClientVO(), logger)
processor.processAndPersistVOs(this)

// now MyServerVO and MyClientVO inside the processor are fully generated with all values injected and also persisted in txn xml, txn properties and also function result generated based on Client VO.
}

}

Now, you just need to define the right property name in your VOs or use the @ResponsePath annotation to show the right xpath or json path in response.

info

Of course, while the flexibility to write your own processing logic if needed remains, note that in this release, the processResponse() method is now protected, so you should start using the new processAndPersistVOs() method insted.

Client VO auto-processing

With this releaase, the response processor injects the value from the response into the Client VO automatically. So, you can use annotation or auto data injection based on naming convention in the Client VO too, just like in the Server VO. For example:

public class MyClientVO extends TifClientVO {

// executionCode value 99 in raw response {"executionCode": "99", "other": "xxx"...} will be automatically injected into this property because it has the same name.
public String executionCode

@ResponsePath('DocumentFields.Fused.Rec')
public Map<String, Object> prefillData

}

Auto-match properties between Client VO and Server VO

In many cases, the Client VO is a subset of the Server VO. Once the Server VO is ready, we need to copy certain property values from the Server VO to the Client VO to ensure their values are consistent. A good example would be executionStatus in both Client VO and Server VO. In this release, we have introduced an auto-matching capability in the response processor. So, after the Server VO has been generated, the response processor will copy the same properties value from Server VO into Client VO automatically by default.

For example, say you have a Server VO and a Client VO defined as follows.


public class MyServerVO extends TifServerVO {
public String executionStatus
public String verifyStatus

public String transactionID
public String transacstionDate
}

public class MyClientVO extends TifClientVO {
public String executionStatus
public String verifyStatus

}

The response process will copy the executionStatus and verifyStatus values from MyServerVO to MyClientVO.

Automatic data value mapping

Often, we'd like to map a backend value to our interpreted value. Now, you can ask the response processor to perform data mapping for you automatically.

For example, say we have a raw response as shown below.

{
"id": "a2970744-a97a-44f3-829c-95a8e6cd76c8",
"statedIp": "1.1.1.1",
"backendResult": "A"
}

We want to set a verifyStatus property in the Server VO based on the backendResult following the rule below.

String backendResult = responsePath.val('backendResult')
switch (backendResult) {
case 'A':
serverVO.verifyStatus = 'ACCEPT'
break
case 'R':
serverVO.verifyStatus = 'REVIEW'
break
case 'D':
serverVO.verifyStatus = 'DECLINE'
break
}

Now, you can define a @ResponsePath annotation to let the response processor automatically set the value based on a data mapping.

public class MyServerVO extends TifServerVO {

@ResponsePath(value='backendResult', mapping='A:ACCEPT,R:REVIEW,D:DECLINE')
public String verifyStatus

}

In this scenario, the verifyStatus value is automatically injected to be ACCEPT because the backendResult returned was A.

info

The response processor won't set the value if there is no match in the mapping. You may want to validate the verifyStatus value later, as shown below.

if (!serverVO.verifyStatus) {
throw new SystemErrorException(SystemErrorException.ERROR_CODE_SERVICE_AGREEMENT_CHANGE, "Invalid backendResult:${checktxnVO.backendResult} from response.")
}

Response as Map object and filter on result

Now you can ask the response processor to store specific JSON child nodes as a Map object in the Server VO or Client VO. This provides a handy way to store many fields at once and dynamic key-value content. You can even control which keys are excluded from the Map by defining the removeFromMap annotation.

For example, say we have the following raw response:

{
"id": "a2970744-a97a-44f3-829c-95a8e6cd76c8",
"statedIp": "1.1.1.1",
"prefillData": {
"firstName": "John",
"lastName": "Smith",
"sex": "Male",
"birthdate": "01/01/1980",
"age": 1,
"photoBased64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEB...",
"address": "Manly"
}
}

Now, define @ResponsePath(removeFromMap='photoBased64,address') as shown below to let the response processor store all the key-value pairs of the prefillData JSON node in the Map object in the VO except for photoBased64 and address.

public class MyClientVO extends TifClientVO {

@ResponsePath(removeFromMap='photoBased64,address')
public Map<String, Object> prefillData

}

In this scenario, the expected prefillData values that get injected automatically are as follows. Note that values for photoBased64 and address are not injected into the prefillData map.

"firstName": "John"
"lastName": "Smith"
"sex": "Male"
"birthdate": "01/01/1980"

In the Maestro form or component, you can then create a block named prefillData and start creating data fields with ids matching the keys inside the Map object above to let Maestro form inject those values into related data fields automatically. This is especially useful when you have many fields in the response that you want to send back to the client.

You can also define the startsWith annotation to only include keys that begin with a certain value.

For example, say we have the raw response shown below:

{
"id": "a2970744-a97a-44f3-829c-95a8e6cd76c8",
"statedIp": "1.1.1.1",
"DocumentRiskConditions": {
"DQL_Default_Result": {
"DQL_Final_GivenName_Result": "MARIANA",
"DQL_Final_Surname_Result": "TKACHENKO",
"DQL_Structural_Image_Assessment_Result_Reason": "Test determines whether the submitted ID is a reproduction of an ID Per your risk model, the pass threshold is a score less than [Threshold_DigitalReproduction_Front], the actual tampered value is [DigitalReproduction_Front_Tampered] and therefore this conclusion is set to Passed, [Please report any concerns to [email protected]. Be sure to include the [3ZlubTDICeuaK-AX] and your company name ] [DQL9104]",
"DQL_FullName_Result": "Inconclusive",
"DQL_Final_Sex_Result": "F",
"DQL_Gender_Result_Reason": "The FullName [MARIANA TKACHENKO] was extracted from either the barcode/MRZ or the visual text on the document, the risk model requires the Fullname to be extracted, and this test is set to Pass [CODE8100] Classifier1 : [false] [DocumentClassName] [Ukraine]. Classifier2 : [true] [DocumentClassName] [Ukraine]. Classifier3 : [false] ] [DocumentClassName] [Ukraine]. REC [4] VIZ [16] 2D [4] 2D Source: [], Data From Front Usable [true]. Supplemental Info: [Submitted via API as: [Passport]. Classified as [DocumentName] [DocumentClassName]....Issued By: [Ukraine] [Ukraine]......Template Year: [Blank]]. ",
"DQL_Allowable_Doc_Type_Result": "Disabled",
"DQL_Classification_ClassName": null,
"DQL_VIZ_BirthPlace_Result": ""
}
}
}

Now, define @ResponsePath(startsWith='DQL_Final_') as shown below to let the response processor search for all keys beginning with 'DQL_Final_' and store their key-value pairs into the prefillData map object.

public class MyClientVO extends TifClientVO {

@ResponsePath(value='DocumentRiskConditions.DQL_Default_Results', startsWith='DQL_Final_')
public Map<String, Object> prefillData

}

In this scenario, the expected prefill data values are injected automatically.

"DQL_Final_GivenName_Result": "MARIANA"
"DQL_Final_Surname_Result": "TKACHENKO"
"DQL_Final_Sex_Result": "F"

Now, say you don't want the 'DQL_Final_' prefix or the '_Result' suffix. You can define @ResponsePath(removePrefixFromKey='DQL_Final_', removeSuffixFromKey = '_Result') to remove the prefix and suffix string from the keys as follows.

public class MyClientVO extends TifClientVO {

@ResponsePath(value='DocumentRiskConditions.DQL_Default_Results', startsWith='DQL_Final_', removePrefixFromKey='DQL_Final_', removeSuffixFromKey = '_Result')
public Map<String, Object> prefillData

}

In this scenario, the expected prefillData has much simplified keys:

"GivenName": "MARIANA"
"Surname": "TKACHENKO"
"Sex": "F"

In the examples above, we took a complex raw response, picked the values of interest, manipulated the keys and generated a much simplified key-value pair map, and stored the result of this data manipulation into the client VO. This is all done based on pure VO annotations.

JSON string as a single property

You can ask the response processor to store the entire JSON child node as a single property in the Server VO.

For example, given the following raw response:

       {
"id": "a2970744-a97a-44f3-829c-95a8e6cd76c8",
"statedIp": "1.1.1.1",
"details": {
"ruleResults": {
"score": 80,
"rulesMatched": 1
},
"machineLearning": {
"mlValue1": {
"value": 640
}
}
}
}

You can define @ResponsePath(type='json') as shown below to let the response processor store the details JSON node as a single property.

public class MyServerVO extends TifServerVO {

@ResponsePath(type='json')
public String details

}

In this scenario, the expected value automatically injected into details is:

{
"ruleResults": {
"score": 80,
"rulesMatched": 1
},
"machineLearning": {
"mlValue1": {
"value": 640
}
}
}

Clear VO properties on errors

Sometimes, you need to clear the value if an exception that has been thrown during response processing. For example, in the AuthenticID project, the overall result verifyStatus value is only available when the response has been processed successfully. During processing, if a data error is encountered (backend suggests a rescan), we should clear verifyStatus. The standard response processor now has the capability to clear any properties in case of errors.

For example, you could define clearWhenError=true in @ResponsePath as shown below.

public class MyServerVO extends TifServerVO {
@ResponsePath(value='ActionCodes.code',mapping='99:VERIFIED,60:FAILED,35:REVIEW', clearWhenError = true)
public String verifyStatus
}
info

By default, this feature is turned off.

Default error handling in response processor

Starting from this release, the response processor includes a dedicated errorProcess() method with standard http error handling implementation. You can override this method to perform your custom error handling. For example, application error handling, SOAP fault handling - whatever suits your project.

info

This release introduces a new constructor that accepts a response status code in order to process the error response. If you're using an earlier release, your existing response processor will still work but the old constructor is deprecated and we encourage you to update your response processor to use the new constructor and move the error handling code inside the response processor so that your code is better structured.

The main reason for the response processor not accepting a HttpResponse object directly in the constructor is that it's quite heavy to construct mock HttpResponse objects in the unit test. It's much easier to create various test cases based on local mock files, inject the content as response text, and set a different response status code in the response processor for testing purpose.

A simplified method to record mock response

Starting from this release, we provide a simplified method to record the mock response. You don't need to provide the id explicitly; instead, it is generated based on suiteId and scene automatically.

For example, prior to this release, you need to write code like that shown below to provide suiteId, id, and scene in order to record the mock response of a fake passport verification scenario.

@Test
public void idvPassportFake() throws Exception {
...
HttpResponse response = AuthenticIDApi.idv(authenticidInput, authenticidConn, logger)
// record mock response with suiteId being 'idv-passport', id being 'idv-passport-fake', scene being 'fake'
MockUtils.recordResponse('idv-passport', 'idv-passport-fake', 'fake', AuthenticIDApi.name, response.getTextContent(), authenticidConn.endpoint, null, logger, 'json')

}

Now, you can do this instead.

@Test
public void idvPassportFake() throws Exception {
...
HttpResponse response = AuthenticIDApi.idv(authenticidInput, authenticidConn, logger)
// record mock response with suiteId being 'idv-passport', scene being 'fake'
MockUtils.recordResponse('idv-passport', 'fake', AuthenticIDApi.name, response.getTextContent(), authenticidConn.endpoint, null, logger, 'json')

}

The previous method of MockUtils.recordResponse(suiteId, id, scene) still works, it's just being deprecated.

Other Updates

  • Log request/response info when httpAudit is enabled.
  • Provided a handy method in the BaseUnitTest class to create a SvcConn object from a local connection file.
  • The TifInput class constructor now allows the developer to define whether you want a system error to be thrown if data injection fails, or continue processing data injection and let developers handle a null value in processData() method themselves.