Opaali-support used to get a lot of questions like “Why does OpaaliAPI not work? We always get 400 Bad Request” and occasionally these questions came with logs of the HTTP traffic.
Although we did have access to the Opaali platform vendor’s technical support, the Opaali migration team tried verifying and solving these problems by ourselves first. So I wrote tools where I could feed in the HTTP request data obtained from our customers to see what went wrong. And in our responses to said customers, we often needed to attach sample code to show how to successfully use the Opaali REST API. Out of these eventually emerged the Java library code that is used by the HTTP adapter sample application.
A template based approach
To make it easy to write calls to the Opaali REST API based on the examples in the official API documentation (or logs sent to us by customers), the OpaaliAPI Java package uses text string based templates of the actual HTTP request/response conversation between the application and the API.
Implementing an API Call function
So the templates are based on the examples in the API documentation by replacing values that change with template variables. As an example, outboundMessageRequest is documented like this:
The code in OpaaliAPI Java package follows this pattern:
- all variables are passed as method arguments
- there is a request specific template string with variables to be expanded with given values
- a new template is created (from the template string) along with a HashMap for variable names and their values
- finally an API call is made with the template and variables as arguments
Code from MessagingAPI
class for outboundMessageRequest method looks something like this:
/*
* outboundMessageRequest
* - access_token
* - config - request specific config variables or null for default config
* - address/addressList
* - senderAddress
* - senderName
* - receiptRequest
* - clientCorrelator
* - chargingInformation
* - outboundSMSMessage
*/
public static String outboundMessageRequest(AccessToken access_token,
final HashMap <String, String> config,
String[] addressList,
String senderAddress,
String senderName,
ApiCall.ReceiptRequest receiptRequest,
String clientCorrelator,
ApiCall.ChargingInfo chargingInfo,
Message message) {
String[] MTTemplate = {
// API request for sending MT messages
"POST https://${API_HOST}/production/messaging/v1/outbound/${SENDERADDRESS_ENCODED}/requests HTTP/1.1",
"Host: ${API_HOST}",
"Content-type: application/json",
"Accept: application/json",
"Authorization: Bearer ${ACCESS_TOKEN}",
"",
"{",
" \"outboundMessageRequest\":",
" {\"address\":[${RECIPIENTLIST}],",
" \"senderAddress\":\"${SENDERADDRESS}\",",
" ${MESSAGE}",
" ${SENDERNAMESTRING}${CHARGINGINFO}${DLRREQUESTSTRING}${CLIENTCORRELATOR}",
" }",
"}"
};
senderAddress = (senderAddress != null ? senderAddress : DEFAULT_SENDER);
Template tmpl = new Template(MTTemplate);
HashMap <String, String> vars = (config != null ? (HashMap<String, String>)config.clone() : Config.getConfig());
vars.put("ACCESS_TOKEN", access_token.renew());
vars.put("SENDERADDRESS", senderAddress);
vars.put("RECIPIENTLIST", makeList(addressList));
vars.put("MESSAGE", message.toString());
vars.put("SENDERNAMESTRING", (senderName != null ? ",\"senderName\":\""+senderName+"\"" : ""));
vars.put("CHARGINGINFO", (chargingInfo != null ? chargingInfo.toString() : ""));
vars.put("DLRREQUESTSTRING", (receiptRequest != null ? receiptRequest.toString() : ""));
vars.put("CLIENTCORRELATOR", (clientCorrelator != null ? ", \"clientCorrelator\":\""+ApiCall.escapeJSON(clientCorrelator)+"\"" : ""));
try {
vars.put("SENDERADDRESS_ENCODED", URLEncoder.encode(senderAddress, "UTF-8"));
} catch (UnsupportedEncodingException e) {
vars.put("SENDERADDRESS_ENCODED", URLEncoder.encode(senderAddress));
}
return ApiCall.makeRequest(access_token, tmpl, vars);
}
There are actually three variants of each API request:
- one with built in session (access_token)
- a static one with no configuration data
- a static one with configuration data supplied
The one with no configuration data was the original one which I used in testing customers’ examples. When I made the http_adapter on top of this I needed a way to store session and configuration data.
The ApiCall
class and its makeRequest
method make the HTTP Request to Opaali but hide details such as retrying when session has expired and conversions for characters that are not accepted in JSON syntax.
Class HttpRequest
also has a makeRequest
which makes the actual HTTP requests. (Due to the template approach this code is a bit more complicated than making a HTTP Request in Java normally would need to be. Lets not think about that now, though.) A HttpResponse
class stores the return value, response headers and response body.
Although there is also an implementation for the AuthAPI
, session handling with its retries is actually handled by the AccessToken
class. Getting it to work took some trial&error, so maybe it is better not to try explain how I think it works…
Log writing…
Although not visible in the example above, I guess I need to say something about the LogWriter
interface and the Log
class. These implement a pluggable logging system with a default implementation, so that when you call one of the AuthAPI or MessagingAPI methods from your code, it will by default write log information to stderr for you to see. You can provide your own LogWriter implementation for your application so that you can write into a log file instead.
This works well enough when you have only one copy of the library running in the same JVM. Afterwards, when I got the idea that I could run several processes of the http_adapter in different ports I ran into trouble with this logging code. Lets just say that it might be a better idea to run several JVM instances instead. (Or, as you have the source code, you can rewrite a multiprocessing friendly version by yourself.)
Your turn
Not all of the Opaali API is implemented in this library, only what we have needed so far. But you can easily expand it yourself following the model of existing functions.
And even if you don’t want to use this code (because it is inefficient, ugly, and the author clearly doesn’t seem to know how to write Java code…), you may want to use it as a reference implementation that actually works, while writing a better implementation by yourself. If your API requests fail, compare them to this code, before contacting Opaali support.
