Adding new object for FHIR synchronization

To add another object to FHIR synchronization, you must implement the FHIR Strategy Pattern

To do this, Firstly you need to create GenericObjectnameStrategy interface if you wish to synchronize Objectname object

It should be a simple CRUD that looks more/less like this:


package org.openmrs.module.fhir.api.strategies.objectname;
 
import org.hl7.fhir.dstu3.model.Objectname;
 
public interface GenericObjectnameStrategy {
 
   Objectname getObjectname(String uuid);
 
   void deleteObjectname(String uuid);
 
   Objectname updateObjectname(String uuid, Objectname objectname);
 
   Objectname createObjectname(Objectname objectname);
}


As shown, it should have at least method stubs for getting, deleting, updatng and creating of Objectname object

After that, the interface, by default needs to be implemented by ObjectnameStrategy class that would contain concrete implementatons of methods.

A Strategy Util class, like ObjectNameStrategyUtil  should be created next to retrieve the implemented strategy in the following way:

package org.openmrs.module.fhir.api.strategies.objectname;
 
import org.openmrs.api.context.Context;
import org.openmrs.module.fhir.api.util.FHIRUtils;
 
public class ObjectNameStrategyUtil {
 
   public static GenericObjectNameStrategy getObjectNameStrategy() {
      String strategy = FHIRUtils.getObjectNameStrategy();
 
      return strategy == null ? new ObjectNameStrategy() :
            Context.getRegisteredComponent(strategy, GenericObjectNameStrategy.class);
   }
}

This is done in order to enable using a different strategy in an easy way as the "String strategy = FHIRUtils.getObjectNameStrategy();" retrieves a bean name that should be used

By default, it's the default strategy shown above, but an additional one can be implemented and used as well

Then, to separate the FHIR mapping logic from the rest of the module ObjectNameServiceImpl class needs to be created to call ObjectNameStrategyUtil class:


package org.openmrs.module.fhir.api.impl;
 
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hl7.fhir.dstu3.model.ObjectName;
import org.openmrs.api.impl.BaseOpenmrsService;
import org.openmrs.module.fhir.api.ObjectNameService;
import org.openmrs.module.fhir.api.db.FHIRDAO;
import org.openmrs.module.fhir.api.strategies.objectname.ObjectNameStrategyUtil;
 
public class ObjectNameServiceImpl extends BaseOpenmrsService implements ObjectNameService {
 
   protected final Log log = LogFactory.getLog(this.getClass());
 
   private FHIRDAO dao;
 
   /**
    * @return the dao
    */
   public FHIRDAO getDao() {
      return dao;
   }
 
   /**
    * @param dao the dao to set
    */
   public void setDao(FHIRDAO dao) {
      this.dao = dao;
   }
 
   @Override
   public ObjectName getObjectName(String uuid) {
      return ObjectNameStrageryUtil.getObjectNameStrategy().getObjectName(uuid);
   }
 
   @Override
   public void deleteObjectName(String uuid) {
      ObjectNameStrageryUtil.getObjectNameStrategy().deleteObjectName(uuid);
   }
 
   @Override
   public ObjectName updateObjectName(String uuid, ObjectName objectName) {
      return ObjectNameStrategyUtil.getObjectName().updateObjectName(uuid, objectName);
   }
 
   @Override
   public ObjectName createObjectName(ObjectName objectName) {
      return ObjectNameStrategyUtil.getObjectNameStrategy().createObjectName(ObjectName);
   }
}


It should implement an ObjectNameService interface:


package org.openmrs.module.fhir.api;
 
import org.hl7.fhir.dstu3.model.ObjectName;
import org.openmrs.api.OpenmrsService;
import org.springframework.transaction.annotation.Transactional;
 
@Transactional
public interface ObjectNameService extends OpenmrsService {
 
   /**
    * Get object by id
    *
    * @param uuid The uuid of object
    * @return ObjectName fhir resource
    */
   ObjectName getObjectName(String uuid);
 
   /**
    * Delete object by id
    *
    * @param uuid The uuid of object
    */
   void deleteObjectName(String uuid);
 
   /**
    * Update object
    *
    * @param uuid          The uuid of object
    * @param objectName representation of object fhir resource
    */
   ObjectName updateObjectName(String uuid, ObjectName objectName);
 
   /**
    * Create object
    *
    * @param objectName the object to create
    */
   ObjectName createObjectName(ObjectName objectName);
}


Also, a FHIRObjectNameResource needs to be created that is going to use the created Service:

package org.openmrs.module.fhir.resources;
 
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ObjectName;
import org.openmrs.api.context.Context;
import org.openmrs.module.fhir.api.ObjectNameService;
 
public class FHIRObjectNameResource extends Resource {
 
   public ObjectName getByUniqueId(IdType id) {
      ObjectNameService objectNameService = Context.getService(ObjectNameService.class);
      return objectNameService.getObjectName(id.getIdPart());
   }
 
   public void deleteObjectName(IdType id) {
      ObjectNameService ObjectNameService = Context.getService(ObjectNameService.class);
      objectNameService.deleteObjectName(id.getIdPart());
   }
 
   public ObjectName updateObjectName(String id, ObjectName objectName) {
      return Context.getService(ObjectNameService.class).updateObjectName(id, objectName);
   }
 
   public ObjectName createObjectName(ObjectName objectName) {
      return Context.getService(ObjectNameService.class).createObjectName(objectName);
   }
}


 Last class that needs to be created is RestfulObjectNameProvider:

package org.openmrs.module.fhir.providers;
 
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.RelatedPerson;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.openmrs.module.fhir.resources.FHIRRelatedPersonResource;
import org.openmrs.module.fhir.util.MethodOutcomeBuilder;
 
public class RestfulObjectNameProvider implements IResourceProvider {
 
   private FHIRObjectNameResource objectNameResource;
 
   public RestfulObjectNameProvider() {
      objectNameResource = new FHIRObjectNameResource();
   }
 
   @Override
   public Class<? extends IBaseResource> getResourceType() {
      return ObjectName.class;
   }
 
   /**
    * Get object by unique id
    *
    * @param theId object containing the id
    */
   @Read
   public ObjectName getResourceById(@IdParam IdType theId) {
      return objectNameResource.getByUniqueId(theId);
   }
 
   /**
    * Delete object by unique id
    *
    * @param theId object containing the id
    */
   @Delete
   public void deleteObjectName(@IdParam IdType theId) {
      objectNameResource.deleteObjectName(theId);
   }
 
   /**
    * Update object by unique id
    *
    * @param theId object containing the id
    */
   @Update
   public MethodOutcome updateObjectName(@ResourceParam ObjectName objectName, @IdParam IdType theId) {
      return MethodOutcomeBuilder.buildUpdate(objectName);
   }
 
   /**
    * Create object
    *
    * @param objectName fhir objectname object
    */
   @Create
   public MethodOutcome createObjectName(@ResourceParam ObjectName objectName) {
      return MethodOutcomeBuilder.buildCreate(objectNameResource.createObjectName(objectName));
   }
}


The created provided must be added in FHIRRESTServer class in the initialize method to resourceProviders like this:


resourceProviders.add(new RestfulRelatedPersonProvider());


When all these classes are created in the FHIR code, the ObjectName object is prepared to be mapped by FHIR
But, to enable the synchronization, a Client Helper needs to be created.
To do this, an interface ObjectNameHelper needs to be created:


package org.openmrs.module.fhir.api.helper;

import org.hl7.fhir.dstu3.model.ObjectName;
import org.openmrs.ObjectName;

public interface ObsHelper {

Observation.ObservationStatus getObsStatus(Obs obs);

void setStatus(Obs obs, Observation.ObservationStatus status);

CodeableConcept getInterpretation(Obs obs);

void setInterpretation(Obs obs, CodeableConcept interpretation);
}


and implemented accordingly.
Also, in the FHIRClientHelper class, CATEGORY_OBJECT_NAME needs to be added to the CATEGORY_MAP:


CATEGORY_MAP.put(CATEGORY_DRUG_ORDER, MedicationRequest.class);

and handled in the compareResourceObjects:

@Override
public boolean compareResourceObjects(String category, Object from, Object dest) {
   boolean result;
   switch (category) {
      case CATEGORY_PATIENT:
         result = FHIRPatientUtil.compareCurrentPatients(dest, from);
         break;
      case CATEGORY_ENCOUNTER:
         result = FHIREncounterUtil.compareCurrentEncounters(dest, from);
         break;
      case CATEGORY_VISIT:
         result = FHIREncounterUtil.compareCurrentEncounters(dest, from);
         break;
      case CATEGORY_OBSERVATION:
         result = FHIRObsUtil.compareCurrentObs(dest, from);
         break;
      case CATEGORY_ALLERGY:
         result = FHIRAllergyIntoleranceUtil.areAllergiesEquals(dest, from);
         break;
      case CATEGORY_PERSON:
         result = FHIRPersonUtil.arePersonsEquals(dest, from);
         break;
      case CATEGORY_COHORT:
         result = FHIRGroupUtil.areGroupsEquals(dest, from);
         break;
      case CATEGORY_DRUG_ORDER:
         result = FHIRMedicationRequestUtil.areMedicationRequestsEquals(dest, from);
         break;
      case CATEGORY_TEST_ORDER:
         result = FHIRProcedureRequestUtil.areProcedureRequestsEqual(dest, from);
         break;
      default:
         result = dest.equals(from);
   }
   return result;
}


And ConvertToOpenMRSObject

@Override
public Object convertToOpenMrsObject(Object object, String category) throws NotSupportedException {
   List<String> errors = new ArrayList<>();
   Object result;
   switch (category) {
      case CATEGORY_LOCATION:
         result = FHIRLocationUtil.generateOpenMRSLocation((Location) object, errors);
         break;
      case CATEGORY_OBSERVATION:
         result = FHIRObsUtil.generateOpenMRSObs((Observation) object, errors);
         break;
      case CATEGORY_ENCOUNTER:
         result = FHIREncounterUtil.generateOMRSEncounter((Encounter) object, errors);
         break;
      case CATEGORY_VISIT:
         result = FHIRVisitUtil.generateOMRSVisit((Encounter) object, errors);
         break;
      case CATEGORY_DRUG_ORDER:
         result = FHIRMedicationRequestUtil.generateDrugOrder((MedicationRequest) object,
               errors);
         break;
      case CATEGORY_TEST_ORDER:
         result = FHIRProcedureRequestUtil.generateTestOrder((ProcedureRequest) object,
               errors);
         break;
      case CATEGORY_PERSON:
         result = FHIRPersonUtil.generateOpenMRSPerson((Person) object, errors);
         break;
      case CATEGORY_PATIENT:
         result = FHIRPatientUtil.generateOmrsPatient((Patient) object, errors);
         break;
      case CATEGORY_COHORT:
         result = FHIRGroupUtil.generateCohort((Group) object);
         break;
      case CATEGORY_ALLERGY:
         result = ContextUtil.getAllergyHelper().generateAllergy(object);
         break;
      default:
         throw new NotSupportedException(String.format("Category %s not supported.", category));
   }
   ErrorUtil.checkErrors(errors);
   return result;
}


Also some changes might be required in classes: FHIRUtils, and RestfulBundleResourceProvider.
After that, the ObjectName object should be able to be synchronized in OpenMRS Sync 2.0