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.HL7Exception;
040import ca.uhn.hl7v2.llp.LLPException;
041import ca.uhn.hl7v2.llp.LowerLayerProtocol;
042import ca.uhn.hl7v2.model.Message;
043import ca.uhn.hl7v2.parser.Parser;
044import ca.uhn.hl7v2.parser.PipeParser;
045import ca.uhn.hl7v2.util.MessageIDGenerator;
046import ca.uhn.hl7v2.util.Terser;
047
048/**
049 * <p>
050 * Performs the initiation role of a message exchange (i.e sender of the first
051 * message; analogous to the client in a client-server interaction), according
052 * to HL7's original mode processing rules.
053 * </p>
054 * <p>
055 * The <code>sendAndReceive(...)</code> method blocks until either a response is
056 * received with the matching message ID, or until a timeout period has passed.
057 * The timeout defaults to 10000 ms (10 sec) but can be configured by setting
058 * the system property "ca.uhn.hl7v2.app.initiator.timeout" to an integer value
059 * representing the number of ms after which to time out.
060 * </p>
061 * <p>
062 * At the time of writing, enhanced mode, two-phase reply, continuation
063 * messages, and batch processing are unsupported.
064 * </p>
065 * 
066 * @author Bryan Tripp
067 */
068public class Initiator {
069
070        private static final Logger log = LoggerFactory.getLogger(Initiator.class);
071        private static final Logger rawOutbound = LoggerFactory
072                        .getLogger("ca.uhn.hl7v2.raw.outbound");
073        private static final Logger rawInbound = LoggerFactory
074                        .getLogger("ca.uhn.hl7v2.raw.inbound");
075        private Connection conn;
076        private int timeoutMillis = 10000;
077
078        /**
079         * Creates a new instance of Initiator.
080         * 
081         * @param conn
082         *            the Connection associated with this Initiator.
083         */
084        Initiator(Connection conn) throws LLPException {
085                this.conn = conn;
086
087                // see if timeout has been set
088                String timeout = System
089                                .getProperty("ca.uhn.hl7v2.app.initiator.timeout");
090                if (timeout != null) {
091                        try {
092                                timeoutMillis = Integer.parseInt(timeout);
093                                log.debug("Setting Initiator timeout to {} ms", timeout);
094                        } catch (NumberFormatException e) {
095                                log.warn(timeout
096                                                + " is not a valid integer - Initiator is using default timeout");
097                        }
098                }
099        }
100
101        /**
102         * Sends a message to a responder system, receives the reply, and returns
103         * the reply as a Message object. This method is thread-safe - multiple
104         * threads can share an Initiator and call this method. Responses are
105         * returned to the calling thread on the basis of message ID.
106         */
107        public Message sendAndReceive(Message out) throws HL7Exception,
108                        LLPException, IOException {
109                if (out == null) {
110                        throw new HL7Exception("Can't encode null message",
111                                        HL7Exception.REQUIRED_FIELD_MISSING);
112                }
113
114                // register message with response Receiver(s) (by message ID)
115                Terser t = new Terser(out);
116                String messID = t.get("/MSH-10");
117
118                if (messID == null || messID.length() == 0) {
119                        throw new HL7Exception(
120                                        "MSH segment missing required field Control ID (MSH-10)",
121                                        HL7Exception.REQUIRED_FIELD_MISSING);
122                }
123
124                // log and send message
125                String outbound = conn.getParser().encode(out);
126                rawOutbound.debug(outbound);
127                Future<String> inbound = null;
128                try {
129                        String message = null;
130                        inbound = conn.waitForResponse(messID, timeoutMillis);
131                        conn.getSendWriter().writeMessage(outbound);
132                        if (inbound != null && (message = inbound.get()) != null) {
133                                // log that we got the message
134                                log.debug("Initiator received message: {}", message);
135                                rawInbound.debug(message);
136                                Message response = conn.getParser().parse(message);
137                                log.debug("response parsed");
138                                return response;
139                        }
140                } catch (IOException e) {
141                        if (inbound != null)
142                                inbound.cancel(true);
143                        conn.close();
144                        throw e;
145                } catch (InterruptedException e) {
146                } catch (ExecutionException e) {
147                }
148
149                throw new HL7Exception(
150                                "Timeout waiting for response to message with control ID "
151                                                + messID);
152        }
153
154        /**
155         * Sets the time (in milliseconds) that the initiator will wait for a
156         * response for a given message before timing out and throwing an exception
157         * (default is 10 seconds).
158         */
159        public void setTimeoutMillis(int timeout) {
160                this.timeoutMillis = timeout;
161        }
162
163        /**
164         * Test harness
165         */
166        public static void main(String args[]) {
167                if (args.length != 2) {
168                        System.out.println("Usage: ca.uhn.hl7v2.app.Initiator host port");
169                }
170
171                try {
172
173                        // set up connection to server
174                        String host = args[0];
175                        int port = Integer.parseInt(args[1]);
176
177                        final Parser parser = new PipeParser();
178                        LowerLayerProtocol llp = LowerLayerProtocol.makeLLP();
179                        Connection connection = new Connection(parser, llp, new Socket(
180                                        host, port));
181                        final Initiator initiator = connection.getInitiator();
182                        connection.activate();
183                        final String outText = "MSH|^~\\&|||||||ACK^^ACK|||R|2.4|\rMSA|AA";
184
185                        // get a bunch of threads to send messages
186                        for (int i = 0; i < 1000; i++) {
187                                Thread sender = new Thread(new Runnable() {
188                                        public void run() {
189                                                try {
190                                                        // get message ID
191                                                        String ID = MessageIDGenerator.getInstance()
192                                                                        .getNewID();
193                                                        Message out = parser.parse(outText);
194                                                        Terser tOut = new Terser(out);
195                                                        tOut.set("/MSH-10", ID);
196
197                                                        // send, get response
198                                                        Message in = initiator.sendAndReceive(out);
199                                                        // get ACK ID
200                                                        Terser tIn = new Terser(in);
201                                                        String ackID = tIn.get("/MSA-2");
202                                                        if (ID.equals(ackID)) {
203                                                                System.out.println("OK - ack ID matches");
204                                                        } else {
205                                                                throw new RuntimeException(
206                                                                                "Ack ID for message " + ID + " is "
207                                                                                                + ackID);
208                                                        }
209
210                                                } catch (Exception e) {
211                                                        e.printStackTrace();
212                                                }
213                                        }
214                                });
215                                sender.start();
216                        }
217
218                } catch (Exception e) {
219                        e.printStackTrace();
220                }
221        }
222
223}