Implementing communication client step by step

Since module version:

1.2.0


If one needs to implement a new communication client like FHIR or REST this tutorial will show you how it can be achieved.

Note: Currently, we only support HTTP based communication clients.


In order to create a new communication client you will have to follow these 9 steps:


STEP 1. Add dependency to FHIR module:

  • Add this line to your properties in pom.xml:
<fhirVersion>1.X.Y</fhirVersion>

Note: Specify version you wish to have dependency on by setting 'X' and 'Y' values. Minimum version is 1.14.0.


  • Add this block to your dependencies in your API and OMOD pom.xml files:
<dependency>
    <groupId>org.openmrs.module</groupId>
    <artifactId>fhir-api</artifactId>
    <scope>provided</scope>
    <version>${fhirVersion}</version>
</dependency>




STEP 2. Implement org.openmrs.module.fhir.api.helper.ClientHelper interface:

org.openmrs.module.fhir.api.helper.ClientHelper interface
/**
 * <h1>ClientHelper</h1>
 * <p>Adapts specific Client to be used by pull & push business logic.</p>
 *
 * @see <a href="https://issues.openmrs.org/browse/SYNCT-274">SYNCT-274</a>
 * @since 1.14.0
 */
public interface ClientHelper {

   /**
    * <p>Implements creation of retrieve request appropriate for specific Client.</p>
    *
    * @param url represents URL of resource to be retrieved
    * @return returns new RequestEntity with 'retrieve' request
    * @throws URISyntaxException
    */
   RequestEntity retrieveRequest(String url) throws URISyntaxException;

   /**
    * <p>Implements creation of create request appropriate for specific Client.</p>
    *
    * @param url represents URL of resource category, where an object will be created
    * @param object represents an object that will be sent
    * @return returns new RequestEntity with 'create' request
    * @throws URISyntaxException
    */
   RequestEntity createRequest(String url, Object object) throws URISyntaxException;

   /**
    * <p>Implements creation of delete request appropriate for specific Client.</p>
    *
    * @param url represents URL of resource category, from where an object will be deleted
    * @param uuid represents UUID of the object, that will be deleted
    * @return returns new RequestEntity with 'delete' request
    * @throws URISyntaxException
    */
   RequestEntity deleteRequest(String url, String uuid) throws URISyntaxException;

   /**
    * <p>Implements creation of update request appropriate for specific Client.</p>
    *
    * @param url represents URL of resource, that will be updated
    * @param object represents an updated object
    * @return returns new RequestEntity with 'update' request
    * @throws URISyntaxException
    */
   RequestEntity updateRequest(String url, Object object) throws URISyntaxException;

   /**
    * <p>Returns Class object corresponding to category name.</p>
    *
    * @param category represents name of category
    * @return returns Class object
    */
   Class resolveClassByCategory(String category);

   /**
    * <p>Returns a list of HTTP interceptors used in communication between nodes.</p>
    *
    * @param username represents username of a user
    * @param password represents password of a user
    * @return return a list of custom interceptors
    */
   List<ClientHttpRequestInterceptor> getCustomInterceptors(String username, String password);

   /**
    * <p>Returns a list of custom converters, that will be used to convert messages between formats.</p>
    *
    * @return returns a list of custom converters
    */
   List<HttpMessageConverter<?>> getCustomMessageConverter();

   /**
    * <p>Compares two objects of the same category.</p>
    *
    * @param category represents category of two compared objects
    * @param from represents incoming object
    * @param dest represents destination object
    * @return returns true if objects are equal, false otherwise
    */
   boolean compareResourceObjects(String category, Object from, Object dest);

   /**
    * <p>Converts a String of formatted data to an object of a specified class.</p>
    *
    * @param formattedData represents data as a String
    * @param clazz represents desired class of returned object
    * @return returns an object of specified class
    */
   Object convertToObject(String formattedData, Class<?> clazz);

   /**
    * <p>Converts an object to a String of formatted data.</p>
    *
    * @param object represents an object that will be converted to a String of formatted data
    * @return returns String of formatted data
    */
   String convertToFormattedData(Object object);

   /**
    * <p>Converts an object to OpenMRS object of specified category.</p>
    *
    * @param object represents an object that will be converted to OpenMRS object
    * @param category represents category of OpenMRS object as a String
    * @return returns OpenMRS object of specified category
    * @throws NotSupportedException
    */
   Object convertToOpenMrsObject(Object object, String category) throws NotSupportedException;
}


for example:

TestClientHelper class
public class TestClientHelper implements ClientHelper {
	 ...
}

Note: We will be using TestClientHelper example until the end of this tutorial.




STEP 3. Modify Sync2 module. Add a new constant to org.openmrs.module.sync2.SyncConstants:

As a name take the first part of your ClientHelper interface implementation class name, in our case it will be:

org.openmrs.module.sync2.SyncConstants class
...
public static final String TEST_CLIENT = "test"; 
...




STEP 4. In Sync2 module add a new case to org.openmrs.module.sync2.client.ClientHelperFactory switch:

org.openmrs.module.sync2.client.ClientHelperFactory class
public class ClientHelperFactory {

   private static final Logger LOGGER = LoggerFactory.getLogger(ClientHelperFactory.class);

   public static ClientHelper createClient(final String clientType) {
      switch (clientType) {
         case REST_CLIENT:
            return new RESTClientHelper();
	     ...
		 case TEST_CLIENT:
	        return new TestClientHelper();
	     ...
         default:
            LOGGER.warn(String.format("Unrecognized clientType: %s. The REST Client will be used.", clientType));
            return new RESTClientHelper();
      }
   }
}


Remember: You have to import TEST_CLIENT constant first.




STEP 5. In Sync2 module, in org.openmrs.module.sync2.api.utils.SyncUtils class, implement your own method for extracting UUID from your resource link:

private static String extractUUIDFromTestResource(String link) {
   	String uuid;
	...
	// extracting UUID from link
	...
	return uuid;
}

Note: Name of this method follows this pattern: extractUUIDFrom<name of your client>Resource.




STEP 6. In Sync2 module add a new case to org.openmrs.module.sync2.api.utils.extractUUIDFromResourceLinks switch:

org.openmrs.module.sync2.api.utils.extractUUIDFromResourceLinks method
public static String extractUUIDFromResourceLinks(Map<String, String> resourceLinks) {
   for (String client : resourceLinks.keySet()) {
      switch (client) {
         case REST_CLIENT:
            return extractUUIDFromRestResource(resourceLinks.get(client));
		 ...
         case TEST_CLIENT:
            return extractUUIDFromTestResource(resourceLinks.get(client));
		 ...
         default:
      }
   }

   LOGGER.error("Couldn't find any supported client to extract uuid from.");
   return null;
}

Note: As an argument of your extract method put "resourceLinks.get(client)".

Remember: You have to import TEST_CLIENT constant first.




STEP 7. When running your OpenMRS instance, go to Atomfeed > Load Configuration page

Add your API endpoints to linkTemplates object in JSON file, for example:

Atomfeed configuration
"feedConfigurations": [
  ...
  {
    "openMrsClass": "org.openmrs.Location",
    "enabled": "true",
    "title": "Location",
    "category": "location",
    "linkTemplates": {
      ...
	  "test":"/test/Location/{uuid}"
    }
  },
  ...
]


Note: Key (in our case "test") must match TEST_CLIENT constant that was set in STEP 2.




STEP 8. Set your communication client as preferred client for pulling resources.

From Home page go to System AdministrationAdvanced AdministrationMaintenence > Advanced Settings.

Find sync2.resource.preferred.client property and set its value to the value of TEST_CLIENT constant, for example:




STEP 9. That's the end. Once you have finished this tutorial, you should have a working communication client.