Thursday, July 16, 2015

Importing SNOMED CT concepts into Openmrs 2.2 using the web services API



The documentation on using the openmrs web services is sufficiently enough to know how to query the database using GET methods. However, when trying to create new records using POST, it's a lot of guesswork. For anybody else that's interested in creating concepts using the web services API, an example is shown below written in Groovy code:

Openmrs Reference API


 package anu.jcmsr.mml.human  
 import grails.converters.JSON  
 import grails.plugins.rest.client.RestBuilder  
 import java.nio.charset.Charset  
 import org.apache.log4j.Logger  
 import org.codehaus.groovy.grails.web.json.JSONArray  
 import org.codehaus.groovy.grails.web.json.JSONObject  
 import org.springframework.http.HttpStatus  
 import org.springframework.http.converter.StringHttpMessageConverter  
 /**  
  * Service class for interacting with openmrs database  
  * @author Philip Wu  
  *  
  */  
 //@Transactional  
 class OpenMrsService {  
      Logger logger = Logger.getLogger(OpenMrsService.class)  
      String conceptUrl = "http://localhost:8989/openmrs/ws/rest/v1/concept"  
      String conceptReferenceTermUrl = "http://localhost:8989/openmrs/ws/rest/v1/conceptreferenceterm"  
      String username = "Admin"  
      String password = "Admin123"  
      /**  
       * Following rest api reference   
       * https://wiki.openmrs.org/display/docs/REST+Web+Service+Resources+in+OpenMRS+1.9#RESTWebServiceResourcesinOpenMRS1.9-Concept  
       *   
       */  
      def importSnomedCtConcepts() {  
           System.out.println("Start importSnomedCtConcepts")  
           // File columns (of interest)            
           final int INDEX_ID = 0  
           final int INDEX_ACTIVE = 2  
           final int INDEX_CONCEPT_ID = 4  
           final int INDEX_TYPE_ID = 6  
           final int INDEX_TERM = 7  
           File file = new File ("E:/snomedCT/NEHTA_2068_2015_SNOMEDCT-AU_ReleaseFileBundle_v20150531/SnomedCT_Release_AU1000036_20150531/RF2Release/Full/Terminology/sct2_Description_Full-en-AU_AU1000036_20150531.txt")  
           // Parse each line  
           file.eachLine { String line, Integer i ->                 
                String[] lineParts = line.split("\t", -1)  
                Boolean active = (lineParts[INDEX_ACTIVE] == '1') ? Boolean.TRUE : Boolean.FALSE  
                // Only import if the concept is active  
                if (active) {  
                     System.out.println("line="+line)  
                     String id = lineParts[INDEX_ID]  
                     String conceptId = lineParts[INDEX_CONCEPT_ID]  
                     String typeId = lineParts[INDEX_TYPE_ID]  
                     String term = lineParts[INDEX_TERM]  
                     /** Reference term */  
                     // Check that the reference term doesn't already exist  
                     String conceptRefTermUuid = conceptReferenceTermExists(conceptId)  
                     //System.out.println("conceptRefTermUuid="+conceptRefTermUuid)  
                     // If no existing record could be found, create a new one  
                     if (conceptRefTermUuid == null) {  
                          conceptRefTermUuid = saveConceptReferenceTerm(conceptId, term)  
                     } else {  
                          System.out.println("Existing conceptRefTerm uuid="+conceptRefTermUuid)  
                     }  
                     try {  
                          /** Concept */  
                          // Check that the concept doesn't already exist  
                          String conceptUuid = conceptExists(term)  
                          // If it doesn't exist, create it  
                          if (conceptUuid == null) {  
                               conceptUuid = saveConcept(term)  
                               System.out.println("Saved concept uuid="+conceptUuid)  
                          }  
                          /** Concept mapping */  
                          // Map the concept to snomed reference term   
                          // Check that the mapping doesn't already exist  
                          String conceptMappingUuid = conceptMappingExists(conceptUuid)  
                          if (conceptMappingUuid == null) {     // create a new mapping  
                               conceptMappingUuid = saveConceptMapping(conceptUuid, conceptRefTermUuid)  
                          }  
                     } catch (Exception ex) {  
                          // Workaround: Handle concepts with special character encoding such as, Déjerine-Roussy syndrome   
                          if (ex.message?.contains("DuplicateConceptNameException")) {  
                               // ignore  
                          } else {  
                               // rethrow  
                               throw ex  
                          }  
                     }                      
                }  
           }  
           System.out.println("End importSnomedCtConcepts")  
      }  
      /**  
       * Construct json object for concept reference term       
       * @param code  
       * @param conceptSourceUuid  
       * @return  
       */  
      private JSONObject jsonConceptReferenceTerm(String code, String term, String conceptSourceUuid) {  
           JSONObject jsonConceptReferenceTerm = new JSONObject()  
           jsonConceptReferenceTerm.put("code", code)  
           jsonConceptReferenceTerm.put("name", term)  
           jsonConceptReferenceTerm.put("conceptSource", conceptSourceUuid)            
           return jsonConceptReferenceTerm  
      }  
      /**  
       * Create the concept represented as a JSON object from the given term  
       * @param term  
       * @return  
       */  
      private JSONObject jsonConcept(String term) {  
           // Construct the json object  
           JSONObject jsonConcept = new JSONObject()  
           // Name  
           JSONArray jsonNames = new JSONArray()  
           JSONObject jsonName = new JSONObject()  
           jsonName.put("name", term)            
           jsonName.put("locale", "en")  
           jsonName.put("conceptNameType", "FULLY_SPECIFIED")  
           jsonNames.add(jsonName)            
           jsonConcept.put("names", jsonNames)  
           // Datatype  
           jsonConcept.put("datatype", '8d4a4c94-c2cc-11de-8d13-0010c6dffd0f')  
           // Concept class  
           jsonConcept.put("conceptClass", "8d4918b0-c2cc-11de-8d13-0010c6dffd0f")  
           return jsonConcept  
      }  
      /**  
       * Using the webservices, create the new concept reference term       
       * @param conceptId  
       * @param term  
       * @return  
       */  
      private String saveConceptReferenceTerm(String conceptId, String term) {  
           // the UUID of the newly created reference term  
           String conceptRefTermUuid = null  
           // Create a new concept reference term  
           JSONObject jsonConceptReferenceTerm = jsonConceptReferenceTerm(conceptId, term, '2b3c054a-768a-102f-83f4-12313b04a615')            
           def resp = postJson(conceptReferenceTermUrl, jsonConceptReferenceTerm)  
           return resp.json.uuid  
      }  
      /**  
       * Using web services, save the concept name  
       * @param term  
       * @return  
       */  
      private String saveConceptName(String term, String parentUuid) {  
           // Create the json object  
           JSONObject jsonConceptName = new JSONObject()  
           jsonConceptName.put("name", term)  
           jsonConceptName.put("locale", "en")  
           String uuid  
           String conceptNameUrl = conceptUrl + "/"+parentUuid+"/name"  
           def resp = postJson(conceptNameUrl, jsonConceptName)   
           return resp.json.uuid  
      }  
      /**  
       * Post JSON object using the given url  
       * @param url  
       * @param jsonObj  
       * @return  
       */  
      private Object postJson(String url, JSONObject jsonObj) {  
           System.out.println("Posting: "+jsonObj.toString())  
           RestBuilder rest = new RestBuilder()  
           def resp = rest.post(url) {  
                auth 'Admin', 'Admin123'  
                contentType "application/json;charset=UTF-8"  
                json jsonObj as JSON  
           }  
           //System.out.println("savingConceptName status code="+resp.statusCode)  
           if (resp.statusCode == HttpStatus.CREATED) {     // created  
                return resp  
           } else if (resp.statusCode == HttpStatus.INTERNAL_SERVER_ERROR) {  
                throw new Exception("OpenMRS internal error: "+resp.text)  
           } else if (resp.statusCode == HttpStatus.BAD_REQUEST) {  
                throw new Exception("OpenMRS bad request: "+resp.text)  
           }  
      }  
      /**  
       * Using web services, save the concept to the database  
       * @param term  
       * @return  
       */  
      private String saveConcept(String term) {  
           String uuid = null  
           JSONObject jsonConcept = jsonConcept(term)  
           def resp = postJson(conceptUrl, jsonConcept)  
           return resp.json.uuid  
      }  
      /**  
       * Using web services, save the mapping between the concept and the reference term (mapping to snomed CT)  
       * @param conceptUuid  
       * @param conceptRefTermUuid  
       * @return  
       */  
      private String saveConceptMapping(String conceptUuid, String conceptRefTermUuid) {  
           String conceptMappingUrl = conceptUrl + "/" + conceptUuid + "/mapping"  
           JSONObject jsonMapping = new JSONObject()  
           jsonMapping.put("conceptMapType", '35543629-7d8c-11e1-909d-c80aa9edcf4e')     // SAME-AS  
           jsonMapping.put("conceptReferenceTerm", conceptRefTermUuid)  
           def resp = postJson(conceptMappingUrl, jsonMapping)  
           return resp.json.uuid  
      }  
      /**  
       * Check to see if the given concept code already exists as a reference term  
       * If it does, return the uuid  
       * @param code  
       * @return  
       */  
      private String conceptReferenceTermExists(String conceptCode) {  
           String checkConceptRefTermUrl = conceptReferenceTermUrl + "?q="+conceptCode+"&v=default"  
           RestBuilder rest = new RestBuilder()  
           //rest.restTemplate.setMessageConverters([new StringHttpMessageConverter(Charset.forName("UTF-8"))])  
           def resp = rest.get(checkConceptRefTermUrl) {  
                auth username, password                 
           }  
           //System.out.println("check conceptCode"+conceptCode+": "+resp.text)  
           for (Object conceptRefTerm: resp.json.results) {            
                if (conceptRefTerm.code?.equals(conceptCode)) {  
                     System.out.println("found existing ref term: "+conceptRefTerm.uuid)  
                     return conceptRefTerm.uuid  
                }  
           }       
           return null  
      }  
      /**  
       * Check to see if the given concept term already exists  
       * @param term  
       * @return  
       */  
      private String conceptExists(String term) {  
           String checkConceptUrl = conceptUrl + "?v=custom:(uuid,name)&q="+term  
           System.out.println("conceptUrl: "+checkConceptUrl)  
           // Terms can contains UTF-8 characters  
           //checkConceptUrl = URLEncoder.encode(checkConceptUrl, "UTF-8")  
           //System.out.println("encoded url = "+checkConceptUrl)  
           RestBuilder rest = new RestBuilder()  
           //rest.restTemplate.setMessageConverters([new StringHttpMessageConverter(Charset.forName("UTF-8"))])  
           def resp = rest.get(checkConceptUrl) {  
                auth username, password                 
           }  
           //System.out.println("conceptExists: "+resp.text)  
           for (Object concept: resp.json.results) {  
                if (concept.name?.name?.equals(term)) {  
                     //System.out.println("found existing concept: "+concept.uuid)  
                     return concept.uuid  
                }  
           }  
           return null  
      }  
      /**  
       * Check to see if the concept with the given UUID already has an existing mapping to snomed  
       * @return  
       */  
      private String conceptMappingExists(String conceptUuid) {  
           String conceptMappingUrl = conceptUrl + "/" + conceptUuid + "/mapping?v=custom:(uuid,conceptMapType,conceptReferenceTerm)"  
           RestBuilder rest = new RestBuilder()  
           //rest.restTemplate.setMessageConverters([new StringHttpMessageConverter(Charset.forName("UTF-8"))])  
           def resp = rest.get(conceptMappingUrl) {  
                auth username, password                 
           }  
           //System.out.println("conceptMappingExists: "+resp.text)  
           for (Object conceptMapping: resp.json.results) {  
                // If it is a SNOMED CT mapping, then return the UUID  
                String conceptSource = conceptMapping.conceptReferenceTerm?.conceptSource?.display  
                String conceptMapType = conceptMapping.conceptMapType?.name  
                if (conceptSource?.equals("SNOMED CT") && conceptMapType?.equals("SAME-AS")) {  
                     return conceptMapping.uuid  
                }  
           }  
           return null  
      }  
      /**  
       * Testing locally  
       * @param args  
       */  
      public static void main (String[] args) {  
           OpenMrsService service = new OpenMrsService()  
           service.importSnomedCtConcepts()  
      }  
 }