Announcement

Collapse
No announcement yet.

Mirth Tools: User defined functions

Collapse
This is a sticky topic.
X
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #46
    Call Webservice With NTLM authentication (.NET Services)

    Mirth Web Service sender and Http Sender connector types do not fully support NTLM Http Authentication scheme because it works by mantaining a persistent http connection to the server during authentication/negotiation.

    This function can be used to call such services by using apache's HttpClient directly.

    This function uses packages already included in Mirth (tested on 3.4.2)

    Code:
    var body = 
    <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pub="namespacex"> <soap:Header/>
    	<soap:Body> 
    		<pub:query> 
    			input data
    		</pub:query> 
    	</soap:Body> 
    </soap:Envelope>;
    	
    var endpoint = "/WSContext/aMicrosoftService.asmx";
    var response = callWebServiceNtlm("servicehost", 80, "http", "user", "pass", "domain", body.toString(), endpoint);
    
    response.status;// holds the http status line
    response.responseBody;// holds the http response body as string
    
    function callWebServiceNtlm(host, port, protocol, username, password, domain, soapBody, endpoint) {
        importPackage(Packages.org.apache.http.impl.client);
        importPackage(Packages.org.apache.http);
        importPackage(Packages.org.apache.http.auth);
        importPackage(Packages.org.apache.http.client);
        importPackage(Packages.org.apache.http.client.methods);
        importPackage(Packages.org.apache.http.client.protocol);
        importPackage(Packages.org.apache.http.entity);
        importPackage(org.apache.commons.io);
        
    
        var	hostName = 'unknown';
        try{
         hostName = Packages.java.net.InetAddress.getLocalHost().getHostName();
        }catch(e){logger.error(e);}
        
        var httpclient = HttpClients.createDefault();
        
        var credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(AuthScope.ANY,
                new NTCredentials(username, password, hostName, domain));
        
        var target = new HttpHost(host, port, protocol);
        
        // Make sure the same context is used to execute logically related requests
        var context = HttpClientContext.create();
        context.setCredentialsProvider(credsProvider);
        
        // Execute a cheap method first. This will trigger NTLM authentication
        var httppost = new HttpPost(endpoint);
        httppost.addHeader("Content-Type", "application/soap+xml");
        httppost.setEntity(new StringEntity(soapBody));
        var response1 = httpclient.execute(target, httppost, context);
        var log = {};
        try {
            var entity1 = response1.getEntity();
            log.status = response1.getStatusLine().toString();
            var responseBody = IOUtils.toString(response1.getEntity().getContent(), "UTF-8");
            log.responseBody = responseBody;
        } finally {
            response1.close();
        }
        
        return log
    }

    Comment


    • #47
      I mentioned in Slack that I'd come and post this some time, so here it is...

      The below function converts between any image formats supported by javax.imageio (plus JAI, which is bundled, plus any other ImageIO extensions in your custom-lib).

      Code:
      function convertImage(imageData, format) {
      	var results = [];
      	var byteInputStream = new java.io.ByteArrayInputStream(FileUtil.decode(imageData));
      	var imageInputStream = javax.imageio.ImageIO.createImageInputStream(byteInputStream);
      	var imageReaders = javax.imageio.ImageIO.getImageReaders(imageInputStream);
      	var imageReader = imageReaders.next();
      	imageReader.setInput(imageInputStream);
      	for (var i=0; i<imageReader.getNumImages(true); i++){
      		var bufImage = imageReader.read(i);
      		var byteOutputStream = new java.io.ByteArrayOutputStream();
      		var imageOutputStream = javax.imageio.ImageIO.createImageOutputStream(byteOutputStream);
      		javax.imageio.ImageIO.write(bufImage, format, imageOutputStream);
      		results.push(FileUtil.encode(byteOutputStream.toByteArray()));	
      	}
      	return results;
      }
      Note that this expects a string (base64-encoding) for imageData and will return an array of strings as a result. We interact with a customer's RIS by sending QRY messages and parsing base64-encoded multipage TIFFs from the response messages. The script looks like:

      Code:
      for each (obx in msg..OBX){
      	var jpegs = convertImage(obx['OBX.5']['OBX.5.1'].toString(), 'JPEG');
      	for each (jpeg in jpegs){
      		jpegBytes = FileUtil.decode(jpeg);
      		//do magic here
      	}
      }
      Right now my "magic" is doing ugly things involving FileUtil.write and exec to use command line tools to wrap the jpegs in DICOM and send them on, but longer-term I hope to do that all inside the script by building a header and running DICOMUtil.mergeHeaderPixelData to get something that I can push to a DICOM-sending channel.

      Comment


      • #48
        Originally posted by narupley View Post
        renameField: Returns a copy of the given HL7 field, changing all segment names to the given name.
        I'm having trouble with renameField. I want to take the value in PV1-6 and put it into PV1-3. Easy enough:

        Code:
        msg.PV1['PV1.3'] = renameField(msg.PV1['PV1.6'],'PV1.3');
        However, in doing this, it's changing my OBX segment.

        Code:
        OBX|1|TX|V&GDT||CT FACE WITHOUT CONTRAST||||||F|||20170314150729
        OBX|2|TX|V&GDT|| ||||||F|||20170314150729
        OBX|3|TX|V&GDT||REASON FOR EXAMINATION: Motor vehicle accident, altered mental status.||||||F|||20170314150729
        Becomes

        Code:
        OBX|1|TX|V&GDT||CT FACE WITHOUT CONTRAST||||||F|||20170314150729
        OBX|2|TX|V&GDT||||||||F|||20170314150729
        OBX|3|TX|V&GDT||REASON FOR EXAMINATION: Motor vehicle accident, altered mental status.||||||F|||20170314150729
        In the second OBX line, OBX-5 changed from having a space in it to being completely blank.

        Is there any way to modify the code so that the spaces in OBX-5 won't be trimmed?

        Comment


        • #49
          Custom Java ClassLoader

          If you're like me, when I'm working on testing a new channel and I'm trying to implement custom java classes, I tend to do a lot of tweaking. I wrote this script to allow me to make quick changes to what classes are available. That way I can skip dropping them into the custom-lib folder and restarting mirth every time I need something small changed.

          I probably wouldn't recommend using this in a production setting, but it's been an amazing addition to my testing environment.

          Code:
          /*
           Mirth Custom Java ClassLoader in Rhino Javascript
          	Java Version 1.8
          	Rhino Version 1.7R5
          
          	folderpath: full URI folderpath to base folder where the package folder is located
          	classpath: FQN classpath from base folder
          	
          	folder structure MUST follow this format to function correctly:
          		C:/javaBaseFolder/
          			--> hello/
          				--> HelloWorld.class
          		>> folderpath = 'file:/C:/javaBaseFolder/'
          		>> classpath = 'hello.HelloWorld'
          	
          	Example Class:
          	>	package hello;
          	>
          	>	public class HelloWorld{
          	>		public static String main() {
          	>			return "Hello World!";
          	>		}
          	>	}
          
          	Use calling format like this:
          		>> var myClass = classImport('file:/C:/javaBaseFolder/','hello.HelloWorld');
          		>> myClass.getConstructor(null).newInstance(null).main();
          */ 
          function classImport(folderpath,classpath) {
            var location = new java.net.URL(folderpath);
            var urlArray = java.lang.reflect.Array.newInstance(java.net.URL, 1);
            urlArray[0] = location;
            var classLoader = new java.net.URLClassLoader(urlArray);
            return classLoader.loadClass(classpath);
          }
          
          var myClass = classImport('file:/C:/javaBaseFolder/','hello.HelloWorld');
          
          logger.info(myClass.getConstructor(null).newInstance(null).main());
          Matt

          http://theunofficialmirthadmin.blogspot.com/

          Comment


          • #50
            Originally posted by tiskinty View Post
            If you're like me, when I'm working on testing a new channel and I'm trying to implement custom java classes, I tend to do a lot of tweaking. I wrote this script to allow me to make quick changes to what classes are available. That way I can skip dropping them into the custom-lib folder and restarting mirth every time I need something small changed.

            I probably wouldn't recommend using this in a production setting, but it's been an amazing addition to my testing environment.
            That should not be necessary in MC version 3.2 and later. We added the ability to load (and reload) JARs live without requiring a restart. Look at the Settings -> Resources tab.
            Step 1: JAVA CACHE...DID YOU CLEAR ...wait, ding dong the witch is dead?

            Nicholas Rupley
            Work: 949-237-6069
            Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.


            - How do I foo?
            - You just bar.

            Comment


            • #51
              Format offset of current local time as string

              I made a library because I needed to get a formatted datetime string for x number of days prior to now to use in a File Reader.

              I put the following line in my channel deploy script to place the object in my globalChannelMap, but I suppose that it could also go in the global deploy script and globalMap, since each channel really doesn't need its own instance of it.
              Code:
              // Get object from code template that can be used to generate filenames with dates in the past.
              $gc('mdu', MyDateUtil.getJavaObject());
              Then for my Filename Filter Pattern I would use the following to get four days ago:
              Code:
              prefix_${mdu.formatCurrentDateTimeByOffset('yyyyMMdd', 'days', -4)}
              It uses java 8 time functions and can do offsets in units from nanos to years.
              Attached Files

              Comment


              • #52
                Custom DB aggregator

                Here's my small contribution. I had to work on this some time ago and I though it was worth to share.

                Starting with Mirth 3.5 new DB Reader "Aggregate Results" option, this scripts allows you to debatch/aggregate records according to one column value.

                To use it
                - go to Source tab, enable Process Batch, enable "Aggregate Results" and set appropriate ORDER BY clause to your query
                - go to Summary tab, open Set Data Types window, set Split Batch By to Javascript, paste script in corresponding field and set target column name (in lower case)

                As DB Reader generates pretty printed XML, we are able to build results reading one line after the other. This might change in a future release.

                EDIT : Actually, while reading it for the first time in weeks, I just figured out there is a much more efficient way to handle this (first version of script is left for info) :

                Code:
                var columnName = "column name in lower case" //<== SET TARGET COLUMN NAME HERE
                var line = ''; // holds single lines read from buffered reader		
                var result = ''; //string representation of one <result/> element
                var results =  <results/> ; // will hold one batch message
                var currentAggregation = ''; 
                // anything remaining from last call?
                var lastResult = $gc('lastReadResult'); // last record read (empty if we start reading) ; WARNING, it's actually a java String, not a js string
                if(lastResult != null && (''+lastResult).length>0) {
                    // a result was read previously ==> it's part of current "batch" 
                    var xmlResult = new XML(lastResult) ;
                    results.appendChild(xmlResult);
                    currentAggregation = xmlResult[columnName].toString();
                    globalChannelMap.remove('lastReadResult');	
                } 
                // start/continue reading lines
                while ((line = reader.readLine()) != null) {
                    // ignore first and last <results> elements
                    if (line != "<results>" && line != "</results>") {
                        line = line.trim();
                        result += line;
                        // end of a record?
                        if (line == '</result>') {
                            var xmlResult = new XML(result); // get result as XML
                            // same or new batch
                            if (currentAggregation != xmlResult[columnName].toString()) {
                                if (results == '') {
                                    // tricky here, an empty <results/> means that we're in the very 1st iteration : we just have to set currentAggregation for next iterations
                                    currentAggregation = xmlResult[columnName].toString();
                                } else {
                                    // store new result for next batch	                  		
                                    $gc('lastReadResult', result);
                                    // return this batch
                                    return results.toString();
                                }
                            }
                            // same batch : add result
                            results.appendChild(xmlResult);
                            // reinit for next record
                            result = '';	               
                        }
                    }
                }
                // one last batch to return?
                return results.toString(); // will return "" if there's nothing remaining
                Attached Files
                Last edited by aTom; 03-19-2018, 12:39 AM.
                Tom

                Comment


                • #53
                  configMap Resolver

                  If not already known, then there is a Mirth feature called configuration Map available from Settings->ConfigurationMap, where you can store your sensitive Credentials information like DB Credentials, API Credentials and values that are stored can be pulled by just doing configurationMap.get('key'), just like with channel Map.

                  However, you cannot update details to the configMap from channels or code templates, in a way that configurationMap.put('key') is not possible. It is a read only static Map.

                  Configuration Map Resolver
                  This function returns a credentials object thereby fetching values from the configurationMap. All that is necessary is to settle for a "pattern" while storing information in configurationMap.

                  Let's say that you have stored DB credentials to an EMR database with customerID as ABCD, so I would name the keys as.

                  ABCD-DB_URL
                  ABCD-DB_UserName
                  ABCD-DB_Password

                  Now, I will call the resolver function like

                  configMapResolver('ABCD','DB');

                  and this will return a credentials object with the information.

                  Code:
                  function configMapResolver(customerID,type) {
                  
                  sPattern=customerID+'-'+type+'_';
                  
                  oCredentials=new Object();
                  oCredentials.Type=type;
                  oCredentials.URL=configurationMap.get(sPattern+'URL');
                  oCredentials.UserName=configurationMap.get(sPattern+'UserName');
                  oCredentials.Password=configurationMap.get(sPattern+'Password');
                  
                  return oCredentials;
                  }

                  You can add more flags like version or environment to it.

                  We use this method for all purpose, DB as well as API credentials (differentiated by the type variable)
                  Last edited by siddharth; 03-19-2018, 05:52 AM. Reason: fix
                  HL7v2.7 Certified Control Specialist!

                  Comment


                  • #54
                    Get HL7v2 text-delimited representation of field from XML fragment

                    We had a specific use case where we needed to get the original HL7 "pipe-delimited" representation of selected fields from incoming HL7v2 text messages and use them elsewhere in the channel output. This function allowed us to transform the HL7 message to an E4X message object, pick the specific fields we needed, and convert the XML fragments back to how they appeared in the original HL7 pipe-delimited message.

                    Function will handle fields with repeating values as well as empty components/sub-components.

                    The function:
                    Code:
                    /**
                     * Function to convert an E4X XML fragment representing an HL7 field into its original HL7 text representation
                     * 
                     * @param XMLList nodeList         XML representing the field to translate.
                     * @param array   joinStrArray     Array containing your HL7 delimiters for repeating fields, components, and subcomponents
                     * @param int     joinStrIdx       Index representing the current delimiter in joinStrArray to use (should be 0 on first call)
                     * @param int     startComponentId Used for recursion; leave null for first call. (Represents the sub-field element the function is starting from.)
                     *
                     * @return string
                     */
                    function xmlToPipes(nodeList, joinStrArray, joinStrIdx, startComponentId) {
                    	var elementList = [];
                    	var componentIndex = null;
                    
                    	if (startComponentId !== undefined) {
                    		componentIndex = parseInt(startComponentId);
                    	}
                    
                    	for each (node in nodeList) {
                    		var nodeChildren = node.elements();
                    		var nodeNameParts = node.name().toString().split('.');
                    		var nodeComponentIndex = parseInt(nodeNameParts.pop());
                    		
                    		if (componentIndex === null) {
                    			componentIndex = nodeComponentIndex;
                    		}
                    		
                    		var componentIndexDiff = nodeComponentIndex - componentIndex;
                    		
                    		if (componentIndexDiff > 0) {
                    			for (var i = 0; i < componentIndexDiff; i++) {
                    				elementList.push('');
                    				componentIndex++;
                    			}
                    		}
                    
                    		if (nodeChildren.length() > 0) {
                    			elementList.push(xmlToPipes(nodeChildren, joinStrArray, joinStrIdx+1, 1));
                    		} else {
                    			elementList.push(node.toString());
                    		}
                    
                    		componentIndex++;
                    	}
                    
                    	return elementList.join(joinStrArray[joinStrIdx]);
                    }
                    Usage example:
                    Code:
                    // array should contain HL7 delimiters you use for (in order) repeating fields, components, and subcomponents
                    var joinStrStarterArray = ["~", "^", "&"];
                    
                    var msh4 = xmlToPipes(msg['MSH']['MSH.4'], joinStrStarterArray, 0);
                    var pid3 = xmlToPipes(msg['ORU_R01.PATIENT_RESULT']['ORU_R01.PATIENT']['PID']['PID.3'], joinStrStarterArray, 0);

                    Comment


                    • #55
                      Access globalChannelMap of any channel

                      Sometimes you need to alter the globalChannelMap of a running channel but do not want to stop it.

                      Code:
                      function $gcById(id, key, value) {
                      	var maps = com.mirth.connect.server.util.GlobalChannelVariableStoreFactory.getInstance();
                      	var map;
                      	if (maps.globalChannelVariableMap.containsKey(id)) {
                      		map = maps.get(id);
                      	}
                      	else {
                      		throw "globalChannelMap not found for channel: " + id;
                      	}
                      
                      	if (arguments.length == 2) {
                      		return map.get(key);
                      	}
                      	else {
                      		return map.put(key, value);
                      	}
                      }
                      Usage is similar to $gc, except also pass the channelId of the channel for which you want to access or update the globalChannelMap.

                      Code:
                      $gcById('388f7dfc-4a1a-45a6-b89b-91faacd67044', 'test', 'added from another channel');
                      logger.info($gcById('febe3d1e-97d6-47eb-bfe0-ca6bf895c967', 'importantMap').toString());

                      Comment


                      • #56
                        Extract Text From PDF

                        UPDATE: I've created a public GitHub repository to track these example channels, code templates, scripts, or whatever else!

                        https://github.com/nextgenhealthcare/connect-examples

                        To start with I only added the ones I wrote, because I didn't want to presume and add code from others without their explicit permission. Pull requests welcome!
                        This comes up from time to time...

                        extractTextFromPDF: Extracts and returns all text from a PDF. Uses the built-in iText library, version 2.1.7.

                        Parameters:
                        • pdfBytes: The raw byte array for the PDF.


                        Examples:
                        • Extract text from a Base64-encoded PDF string:
                          Code:
                          var pdfBytes = FileUtil.decode(pdfBase64String);
                          var pdfText = extractTextFromPDF(pdfBytes);
                        • Extract text from a PDF message attachment (before 3.6):
                          Code:
                          // Is a byte array containing Base64 ASCII bytes
                          var attachmentContent = getAttachments().get(0).getContent();
                          // Convert to a Base64 string
                          var attachmentBase64String = new java.lang.String(attachmentContent, 'US-ASCII');
                          // Convert to raw PDF bytes
                          var pdfBytes = FileUtil.decode(attachmentBase64String);
                          // Extract the text
                          var pdfText = extractTextFromPDF(pdfBytes);
                        • Extract text from a PDF message attachment (3.6 and later):
                          Code:
                          // Pass in true for base64Decode, then content is already raw PDF bytes
                          var pdfBytes = getAttachments(true).get(0).getContent();
                          // Extract the text
                          var pdfText = extractTextFromPDF(pdfBytes);


                        The code:
                        Code:
                        /**
                        	Extracts and returns all text from a PDF. Uses the built-in iText library, version 2.1.7.
                        
                        	@param {byte[]} pdfBytes - The raw byte array for the PDF.
                        	@return {String} The extracted text.
                        */
                        function extractTextFromPDF(pdfBytes) {
                        	var text = new java.lang.StringBuilder();
                        	var reader = new com.lowagie.text.pdf.PdfReader(pdfBytes);
                        	
                        	try {
                        		var extractor = new com.lowagie.text.pdf.parser.PdfTextExtractor(reader);
                        		var pages = reader.getNumberOfPages();
                        		
                        		for (var i = 1; i <= pages; i++) {
                        			text.append(extractor.getTextFromPage(i));
                        			if (i < pages) {
                        				text.append('\n\n');
                        			}
                        		}
                        	} finally {
                        		reader.close();
                        	}
                        
                        	return text.toString();
                        }
                        Attached Files
                        Last edited by narupley; 06-08-2018, 10:37 AM.
                        Step 1: JAVA CACHE...DID YOU CLEAR ...wait, ding dong the witch is dead?

                        Nicholas Rupley
                        Work: 949-237-6069
                        Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.


                        - How do I foo?
                        - You just bar.

                        Comment


                        • #57
                          FYI, I've created a public GitHub repository to track these example channels, code templates, scripts, or whatever else!

                          https://github.com/nextgenhealthcare/connect-examples

                          To start with I only added the ones I wrote, because I didn't want to presume and add code from others without their explicit permission. Pull requests welcome!
                          Step 1: JAVA CACHE...DID YOU CLEAR ...wait, ding dong the witch is dead?

                          Nicholas Rupley
                          Work: 949-237-6069
                          Always include what Mirth Connect version you're working with. Also include (if applicable) the code you're using and full stacktraces for errors (use CODE tags). Posting your entire channel is helpful as well; make sure to scrub any PHI/passwords first.


                          - How do I foo?
                          - You just bar.

                          Comment

                          Working...
                          X