001/** 002The contents of this file are subject to the Mozilla Public License Version 1.1 003(the "License"); you may not use this file except in compliance with the License. 004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005Software distributed under the License is distributed on an "AS IS" basis, 006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007specific language governing rights and limitations under the License. 008 009The Original Code is "Initiator.java". Description: 010"Performs the initiation role of a message exchange accorging to HL7's original 011 mode rules." 012 013The Initial Developer of the Original Code is University Health Network. Copyright (C) 0142002. All Rights Reserved. 015 016Contributor(s): ______________________________________. 017 018Alternatively, the contents of this file may be used under the terms of the 019GNU General Public License (the ?GPL?), in which case the provisions of the GPL are 020applicable instead of those above. If you wish to allow use of your version of this 021file only under the terms of the GPL and not to allow others to use your version 022of this file under the MPL, indicate your decision by deleting the provisions above 023and replace them with the notice and other provisions required by the GPL License. 024If you do not delete the provisions above, a recipient may use your version of 025this file under either the MPL or the GPL. 026 027 */ 028 029package ca.uhn.hl7v2.app; 030 031import java.io.IOException; 032import java.net.Socket; 033import java.util.concurrent.ExecutionException; 034import java.util.concurrent.Future; 035 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import ca.uhn.hl7v2.ErrorCode; 040import ca.uhn.hl7v2.HL7Exception; 041import ca.uhn.hl7v2.llp.LLPException; 042import ca.uhn.hl7v2.llp.LowerLayerProtocol; 043import ca.uhn.hl7v2.llp.MinLowerLayerProtocol; 044import ca.uhn.hl7v2.model.Message; 045import ca.uhn.hl7v2.parser.Parser; 046import ca.uhn.hl7v2.parser.PipeParser; 047import ca.uhn.hl7v2.util.Terser; 048import ca.uhn.hl7v2.util.idgenerator.IDGenerator; 049import ca.uhn.hl7v2.util.idgenerator.InMemoryIDGenerator; 050 051/** 052 * <p> 053 * Performs the initiation role of a message exchange (i.e sender of the first 054 * message; analogous to the client in a client-server interaction), according 055 * to HL7's original mode processing rules. 056 * </p> 057 * <p> 058 * The <code>sendAndReceive(...)</code> method blocks until either a response is 059 * received with the matching message ID, or until a timeout period has passed. 060 * The timeout defaults to 10000 ms (10 sec) but can be configured by setting 061 * the system property "ca.uhn.hl7v2.app.initiator.timeout" to an integer value 062 * representing the number of ms after which to time out. 063 * </p> 064 * <p> 065 * At the time of writing, enhanced mode, two-phase reply, continuation 066 * messages, and batch processing are unsupported. 067 * </p> 068 * 069 * @author Bryan Tripp 070 */ 071public class Initiator { 072 073 private static final Logger log = LoggerFactory.getLogger(Initiator.class); 074 private static final Logger rawOutbound = LoggerFactory 075 .getLogger("ca.uhn.hl7v2.raw.outbound"); 076 private static final Logger rawInbound = LoggerFactory 077 .getLogger("ca.uhn.hl7v2.raw.inbound"); 078 private Connection conn; 079 private int timeoutMillis = 10000; 080 081 /** 082 * Creates a new instance of Initiator. 083 * 084 * @param conn 085 * the Connection associated with this Initiator. 086 */ 087 Initiator(Connection conn) throws LLPException { 088 this.conn = conn; 089 090 // see if timeout has been set 091 String timeout = System 092 .getProperty("ca.uhn.hl7v2.app.initiator.timeout"); 093 if (timeout != null) { 094 try { 095 timeoutMillis = Integer.parseInt(timeout); 096 log.debug("Setting Initiator timeout to {} ms", timeout); 097 } catch (NumberFormatException e) { 098 log.warn(timeout 099 + " is not a valid integer - Initiator is using default timeout"); 100 } 101 } 102 } 103 104 /** 105 * Sends a message to a responder system, receives the reply, and returns 106 * the reply as a Message object. This method is thread-safe - multiple 107 * threads can share an Initiator and call this method. Responses are 108 * returned to the calling thread on the basis of message ID. 109 */ 110 public Message sendAndReceive(Message out) throws HL7Exception, 111 LLPException, IOException { 112 if (out == null) { 113 throw new HL7Exception("Can't encode null message", 114 ErrorCode.REQUIRED_FIELD_MISSING); 115 } 116 117 // register message with response Receiver(s) (by message ID) 118 Terser t = new Terser(out); 119 String messID = t.get("/MSH-10"); 120 121 if (messID == null || messID.length() == 0) { 122 throw new HL7Exception( 123 "MSH segment missing required field Control ID (MSH-10)", 124 ErrorCode.REQUIRED_FIELD_MISSING); 125 } 126 127 // log and send message 128 String outbound = conn.getParser().encode(out); 129 rawOutbound.debug(outbound); 130 Future<String> inbound = null; 131 try { 132 String message; 133 inbound = conn.waitForResponse(messID, timeoutMillis); 134 conn.getSendWriter().writeMessage(outbound); 135 if (inbound != null && (message = inbound.get()) != null) { 136 // log that we got the message 137 log.debug("Initiator received message: {}", message); 138 rawInbound.debug(message); 139 Message response = conn.getParser().parse(message); 140 log.debug("response parsed"); 141 return response; 142 } 143 } catch (IOException e) { 144 if (inbound != null) 145 inbound.cancel(true); 146 conn.close(); 147 throw e; 148 } catch (InterruptedException e) { 149 } catch (ExecutionException e) { 150 } 151 152 throw new HL7Exception( 153 "Timeout waiting for response to message with control ID " 154 + messID); 155 } 156 157 /** 158 * Sets the time (in milliseconds) that the initiator will wait for a 159 * response for a given message before timing out and throwing an exception 160 * (default is 10 seconds). 161 */ 162 public void setTimeoutMillis(int timeout) { 163 this.timeoutMillis = timeout; 164 } 165 166 /** 167 * Test harness 168 */ 169 public static void main(String args[]) { 170 if (args.length != 2) { 171 System.out.println("Usage: ca.uhn.hl7v2.app.Initiator host port"); 172 } 173 174 try { 175 176 // set up connection to server 177 String host = args[0]; 178 int port = Integer.parseInt(args[1]); 179 180 final Parser parser = new PipeParser(); 181 LowerLayerProtocol llp = new MinLowerLayerProtocol(); 182 Connection connection = new Connection(parser, llp, new Socket( 183 host, port)); 184 final Initiator initiator = connection.getInitiator(); 185 connection.activate(); 186 final String outText = "MSH|^~\\&|||||||ACK^^ACK|||R|2.4|\rMSA|AA"; 187 final IDGenerator generator = new InMemoryIDGenerator(); 188 189 // get a bunch of threads to send messages 190 for (int i = 0; i < 1000; i++) { 191 Thread sender = new Thread(new Runnable() { 192 193 public void run() { 194 try { 195 // get message ID 196 String ID = generator.getID(); 197 Message out = parser.parse(outText); 198 Terser tOut = new Terser(out); 199 tOut.set("/MSH-10", ID); 200 201 // send, get response 202 Message in = initiator.sendAndReceive(out); 203 // get ACK ID 204 Terser tIn = new Terser(in); 205 String ackID = tIn.get("/MSA-2"); 206 if (ID.equals(ackID)) { 207 System.out.println("OK - ack ID matches"); 208 } else { 209 throw new RuntimeException( 210 "Ack ID for message " + ID + " is " 211 + ackID); 212 } 213 214 } catch (Exception e) { 215 e.printStackTrace(); 216 } 217 } 218 }); 219 sender.start(); 220 } 221 222 } catch (Exception e) { 223 e.printStackTrace(); 224 } 225 } 226 227}