FIFA Connect Service Bus Java SDK, v3.1
The main responsibility of FIFA Connect ID Service Bus is to provide unified infrastructure for asynchronous messaging. Using FIFA Connect Service Bus and its SDK it is possible to exchange encrypted messages with other applications connected to the bus.
Java 11 (or higher) is required
You can reference either the jar with dependencies embedded or the jar without them. In the latter case, the following dependencies need to be referenced as well:
com.google.guava : guava : 27.1com.squareup.okhttp3 : logging-interceptor : 4.2.2com.squareup.retrofit2 : retrofit : 2.9.0com.fasterxml.jackson.core : jackson-databind : 2.10.1com.fasterxml.jackson.datatype : jackson-datatype-joda : 2.10.1com.microsoft.rest : client-runtime : 1.7.9com.microsoft.azure : msal4j : 1.7.1FIFA Connect Service Bus provides end-to-end encryption which means that message is encrypted in SDK of sending party and is decrypted in SDK or receiving party. Consequently none of the devices transferring message (including FIFA Connect Service Bus servers) can read its content.
Such a feature of Connect Service Bus is possible due to usage of infrastructure based on public and private pairs of certificates. This section briefly describes how this is achieved.
The entry point of the SDK is FifaConnectServiceBusClientImpl. With a single instance of this class all the requests can be made.In order to authenticate to the service you have to provide a set of client credentials.
In order to create a new instance of the client, instance of PrivateKeyStorage must be provided. By default SDK uses PrivateKeyMemoryStorage class that implements mentioned interface. Next step is to provide instance(s) of PrivateKey into such container. Each certificate consists of the key (from generated file) and password (optionally). Please take a look at the following example:
String certificatePassword = "[password]";
KeyStore.PasswordProtection protection = new KeyStore.PasswordProtection(certificatePassword.toCharArray());
InputStream privateCertStream = Main.class.getResourceAsStream("[path to certificate]");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(privateCertStream, certificatePassword.toCharArray());
KeyStore.PrivateKeyEntry key = (KeyStore.PrivateKeyEntry)ks.getEntry("1", protection);
PrivateKeyStorage privateKeyStorage = new PrivateKeyMemoryStorage(key.getPrivateKey());In order to generate a new certificate follow instructions from fifa-connectservicebus-certificates-generation.html. When a new pair of certificates (public and private) is generated and a new public certificate is uploaded, then it's required to update collection of private certificates returned by implementation of PrivateKeyStorage. Since some messages could be encrypted before new public certificate was uploaded, implementation of PrivateKeyStorage must return current and previous private certificate to ensure that all messages can be decrypted.
Correct order of steps is the following:
PrivateKeyStorage returns old and new private certificatesPlease take a look at the following example, where two certificates are used by PrivateKeyStorage:
String certificatePassword = "[password]";
String oldCertificatePassword = "[password]";
KeyStore.PasswordProtection protection = new KeyStore.PasswordProtection(certificatePassword.toCharArray());
KeyStore.PasswordProtection oldProtection = new KeyStore.PasswordProtection(oldCertificatePassword.toCharArray());
InputStream privateCertStream =
Main.class.getResourceAsStream("[path to certificate]");
InputStream oldPrivateCertStream =
Main.class.getResourceAsStream("[path to old certificate]");
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(privateCertStream, certificatePassword.toCharArray());
ks.load(oldPrivateCertStream, oldCertificatePassword.toCharArray());
KeyStore.PrivateKeyEntry newKey = (KeyStore.PrivateKeyEntry)ks.getEntry("1", protection);
KeyStore.PrivateKeyEntry oldKey = (KeyStore.PrivateKeyEntry)ks.getEntry("2", oldProtection);
PrivateKeyMemoryStorage privateKeyStorage = new PrivateKeyMemoryStorage(oldKey.getPrivateKey());
privateKeyStorage.addNewKey(newKey.getPrivateKey());Each SDK client method uses an instance com.fifa.connectservicebus.sdk.logging.Logger in order to log any errors that might happen during the application runtime. There is no default Logger implementation provided, as different parties could want a different way of storing logs. Implementation of such Logger class is mandatory.
A frequent mistake when implementing the Logger is ignoring the args parameter when formatting the message.
To avoid this problem, it might be convenient to use abstract class com.fifa.connectservicebus.sdk.logging.BaseLogger as a base class. This class already implements the message formatting logic.
Example implementation of the void debug(String format, Object... args):
System.out.println(String.format(format, args));There are methods accepting Exception object as the first argument. Please make sure to handle it properly, i.e. include the inner exceptions (if any) and the stack trace. The message itself might not be enough.
ConnectServiceBusEnvironment environment = ConnectServiceBusEnvironment.beta;
ClientCredentials credentials = new ClientCredentials("clientId", "secretKey");
Logger logger = new YourOwnLogger();
FifaConnectServiceBusClient client = new FifaConnectServiceBusClientImpl(environment, credentials, privateKeyStorage, logger);where environment is an instance of type ConnectServiceBusEnvironment. It provides information about the service to make request to.
In case of need for upload or download certificate, you have to use an instance of FifaConnectServiceBusCertificateClient.
ConnectServiceBusEnvironment environment = ConnectServiceBusEnvironment.beta;
ClientCredentials credentials = new ClientCredentials("clientId", "secretKey");
Logger logger = new YourOwnLogger();
FifaConnectServiceBusCertificateClient certificateClient = new FifaConnectServiceBusCertificateClientImpl(environment, credentials, logger);Encryption can be disabled using setUseEncryption() method from FifaConnectServiceBusClient instance.
ConnectServiceBusEnvironment environment = ConnectServiceBusEnvironment.beta;
ClientCredentials credentials = new ClientCredentials("clientId", "secretKey");
Logger logger = new YourOwnLogger();
FifaConnectServiceBusClient client = new FifaConnectServiceBusClientImpl(environment, credentials, privateKeyStorage, logger);
client.setUseEncryption(false);Recipient of the message is specified by the recipient parameter. It's actually a name of a queue that acts as an inbox for other client (e.g. registration system in a different MA).
As there are some pre-defined environments defined in the ConnectServiceBusEnvironment class, it's also possible to create a custom instance using environment code. In order to achieve that, environment code needs to be provided to the static factory method.
Example:
String environmentCode = "testenv";
ConnectServiceBusEnvironment environment = ConnectServiceBusEnvironment.create(environmentCode);In order to send a message in FIFA Connect Service Bus service provide content as byte[]. A recipient needs to be provided as well.
By convention value of the recipient is the FIFA ID of the receiving organisation. However, for certain applications it may have format of FIFAID_application. In case of any doubts, please contact FIFA Connect Service Bus Support team to get proper value of recipient.
Example:
try {
client.send(recipient, content);
} catch(InvalidClientDataException ex){
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch (PublicCertificateNotFoundException ex){
//there is no public certificate for given queue
} catch (CryptographyException ex){
//exception when encrypting content
} catch (QueueNotFoundException ex){
//there is no queue for specified recipient
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}To send additional metadata of the message use overloaded send method:
String action = "/person/getDetails";
Map<String, String> properties = new HashMap<>();
properties.put("id", "BVGE8T6");
client.send(recipient, content, action, properties);In order to receive a message from Connect Service Bus and leave it in the queue use peekLock method. The timeout parameter can be specified. In Connect Service Bus context timeout defines how long a request waits before returning that there is no message in the queue. When no message is found, null will be returned. Received message will not be visible for other Connect Service Bus clients for 120 seconds. The 'timeout' value does not impact this, therefore it is recommended to keep 'timeout' value smaller than 120. During that time period actions like delete or unlock can be triggered. If none of the above actions is taken, message will be returned to the queue.
In the Message some metadata can be found in property brokerProperties. The most important are messageId and lockToken that are used as required parameters in delete and unlock methods.
Example:
try {
int timeout = 60;
Message message = client.peekLock(timeout);
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch(InvalidClientDataException ex) {
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch (CryptographyException ex) {
// exception when decrypting service bus message
} catch (QueueNotFoundException ex){
//there is no queue to get messages from
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}Method used to delete a locked message.
Example:
try {
client.delete(message.getId(), message.getLockToken());
} catch(InvalidClientDataException ex) {
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}Message can be returned to the queue using unlock method.
Example:
try {
client.unlock(message.getId(), message.getLockToken());
} catch(InvalidClientDataException ex) {
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}Recommended way to upload a public certificate is the upload using console application located in certificate-upload-console folder. For more information please refer to Certificate Generation documentation. If console application can't be used, use uploadCertificate method instead. Take a look at the following example:
Path path = Paths.get("cert.pem");
byte[] certificate = Files.readAllBytes(path);
try
{
client.uploadCertificate(certificate);
} catch(InvalidClientDataException ex) {
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}To download public certificate for specific organisation use downloadCertificate method. Please take a look at the following example:
try
{
client.downloadCertificate(queueIdentifier);
} catch(InvalidClientDataException ex) {
// data sent to the service was invalid
BadRequestResponseType details = ex.getBadRequestResponse();
} catch(AuthenticationException ex){
// invalid client credentials
} catch(UnauthorizedException ex){
// unauthorized
} catch (DataNotFoundException ex){
// certificate has not been found
} catch (TooManyRequestsException ex) {
// call rate limit was exceeded
var retryAfterInSeconds = ex.getRetryAfter();
} catch (FifaConnectServiceBusException ex) {
// some other error occurred, see the details
ServiceResponse response = ex.getServiceResponse();
}Currently each recipient's queue has a quota of 80 GB (both content and message headers size is counted). When queue reaches its limit, new messages cannot be send to the recipient, so senders get an error from Service Bus API and SDK.
Each queue stores messages for 7 days. If message is not received by the recipient, it is moved to dead-letter queue and support team receives notification about unprocessed message. On client request support team can move a message to primary queue so that it can be received and processed by an application. Alternatively message can be permanently deleted from a dead-letter queue.
If client using Connect Service Bus SDK downloads message but fails to process it correctly (i.e. handler throws an exception), the counter of failed delivery is increased. If delivery fails 10 times, message is moved to a dead-letter queue. Depending on the reason different actions can be taken:
Maximum message size is 10MB. If this limit is exceeded then Connect Service Bus will return 400 (Bad Request) response code. As a result InvalidClientDataException will be thrown.
The following changes were introduced in version 3.0 of the SDK when comparing to version 2.1: