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 "ActiveInitiator.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; 035import java.util.concurrent.TimeUnit; 036 037import ca.uhn.hl7v2.ErrorCode; 038import ca.uhn.hl7v2.HL7Exception; 039import ca.uhn.hl7v2.llp.LLPException; 040import ca.uhn.hl7v2.llp.LowerLayerProtocol; 041import ca.uhn.hl7v2.llp.MinLowerLayerProtocol; 042import ca.uhn.hl7v2.model.Message; 043import ca.uhn.hl7v2.parser.Parser; 044import ca.uhn.hl7v2.parser.PipeParser; 045import ca.uhn.hl7v2.util.Terser; 046import ca.uhn.hl7v2.util.idgenerator.IDGenerator; 047import ca.uhn.hl7v2.util.idgenerator.InMemoryIDGenerator; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 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 using 061 * {@link #setTimeout(long, java.util.concurrent.TimeUnit)} or globally by setting 062 * the system property "ca.uhn.hl7v2.app.initiator.timeout" to an long value 063 * representing the number of ms after which to time out. 064 * </p> 065 * <p> 066 * At the time of writing, enhanced mode, two-phase reply, continuation 067 * messages, and batch processing are unsupported. 068 * </p> 069 * 070 * @author Bryan Tripp 071 */ 072public class ActiveInitiator implements Initiator { 073 074 private static final Logger log = LoggerFactory.getLogger(ActiveInitiator.class); 075 private static final Logger rawOutbound = LoggerFactory 076 .getLogger("ca.uhn.hl7v2.raw.outbound"); 077 private static final Logger rawInbound = LoggerFactory 078 .getLogger("ca.uhn.hl7v2.raw.inbound"); 079 private ActiveConnection conn; 080 private long timeoutMillis = 10000; 081 082 /** 083 * Creates a new instance of ActiveInitiator. 084 * 085 * @param conn 086 * the Connection associated with this ActiveInitiator. 087 */ 088 ActiveInitiator(ActiveConnection conn) throws LLPException { 089 this.conn = conn; 090 091 // See if timeout has been set 092 String timeout = System 093 .getProperty("ca.uhn.hl7v2.app.initiator.timeout"); 094 if (timeout != null) { 095 try { 096 timeoutMillis = Long.parseLong(timeout); 097 log.debug("Setting Initiator timeout to {} ms", timeout); 098 } catch (NumberFormatException e) { 099 log.warn(timeout 100 + " is not a valid long - Initiator is using default timeout"); 101 } 102 } 103 } 104 105 /** 106 * Sends a message to a responder system, receives the reply, and returns 107 * the reply as a Message object. This method is thread-safe - multiple 108 * threads can share an Initiator and call this method. Responses are 109 * returned to the calling thread on the basis of message ID. 110 */ 111 public Message sendAndReceive(Message out) throws HL7Exception, 112 LLPException, IOException { 113 if (out == null) { 114 throw new HL7Exception("Can't encode null message", 115 ErrorCode.REQUIRED_FIELD_MISSING); 116 } 117 118 // register message with response Receiver(s) (by message ID) 119 Terser t = new Terser(out); 120 String messID = t.get("/MSH-10"); 121 122 if (messID == null || messID.length() == 0) { 123 throw new HL7Exception( 124 "MSH segment missing required field Control ID (MSH-10)", 125 ErrorCode.REQUIRED_FIELD_MISSING); 126 } 127 128 // log and send message 129 String outbound = conn.getParser().encode(out); 130 rawOutbound.debug(outbound); 131 Future<String> inbound = null; 132 try { 133 String message; 134 inbound = conn.waitForResponse(messID, timeoutMillis); 135 conn.getSendWriter().writeMessage(outbound); 136 if (inbound != null && (message = inbound.get()) != null) { 137 // log that we got the message 138 log.debug("Initiator received message: {}", message); 139 rawInbound.debug(message); 140 Message response = conn.getParser().parse(message); 141 log.debug("response parsed"); 142 return response; 143 } 144 } catch (IOException e) { 145 if (inbound != null) 146 inbound.cancel(true); 147 conn.close(); 148 throw e; 149 } catch (InterruptedException e) { 150 } catch (ExecutionException e) { 151 } 152 153 throw new HL7Exception( 154 "Timeout waiting for response to message with control ID " 155 + messID); 156 } 157 158 public void setTimeoutMillis(int timeout) { 159 setTimeout(timeout, TimeUnit.MILLISECONDS); 160 } 161 162 public void setTimeout(long timeout, TimeUnit timeUnit) { 163 this.timeoutMillis = timeUnit.toMillis(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.ActiveInitiator 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 ActiveConnection(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}