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}