001    /**
002    The 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. 
004    You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005    Software distributed under the License is distributed on an "AS IS" basis, 
006    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007    specific language governing rights and limitations under the License. 
008    
009    The Original Code is "TwoPortService.java".  Description: 
010    "A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound messages." 
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
013    2001.  All Rights Reserved. 
014    
015    Contributor(s): ______________________________________. 
016    
017    Alternatively, the contents of this file may be used under the terms of the 
018    GNU General Public License (the  ?GPL?), in which case the provisions of the GPL are 
019    applicable instead of those above.  If you wish to allow use of your version of this 
020    file only under the terms of the GPL and not to allow others to use your version 
021    of this file under the MPL, indicate your decision by deleting  the provisions above 
022    and replace  them with the notice and other provisions required by the GPL License.  
023    If you do not delete the provisions above, a recipient may use your version of 
024    this file under either the MPL or the GPL. 
025    */
026    
027    package ca.uhn.hl7v2.app;
028    
029    import java.io.File;
030    import java.io.IOException;
031    import java.io.InterruptedIOException;
032    import java.net.ServerSocket;
033    import java.net.Socket;
034    import java.net.SocketException;
035    import java.util.Vector;
036    
037    import ca.uhn.hl7v2.llp.LLPException;
038    import ca.uhn.hl7v2.llp.LowerLayerProtocol;
039    import ca.uhn.hl7v2.parser.Parser;
040    import ca.uhn.hl7v2.parser.PipeParser;
041    import ca.uhn.log.HapiLog;
042    import ca.uhn.log.HapiLogFactory;
043    
044    /**
045     * A TCP/IP-based HL7 Service that uses separate ports for inbound and outbound messages.
046     * A connection is only activated when the same remote host connects to both the 
047     * inbound and outbound ports.  
048     * @author Bryan Tripp
049     */
050    public class TwoPortService extends HL7Service {
051    
052        private static final HapiLog log = HapiLogFactory.getHapiLog(TwoPortService.class);
053    
054        private Vector inSockets; //Vector because it's synchronized 
055        private Vector outSockets;
056        private int inboundPort;
057        private int outboundPort;
058    
059        /** Creates a new instance of TwoPortService */
060        public TwoPortService(Parser parser, LowerLayerProtocol llp, int inboundPort, int outboundPort) {
061            super(parser, llp);
062            inSockets = new Vector(20);
063            outSockets = new Vector(20);
064            this.inboundPort = inboundPort;
065            this.outboundPort = outboundPort;
066        }
067    
068        /** 
069         * Initially sets up server sockets and starts separate threads to accept connections 
070         * on them.  Then loops, calling this.accept() super.newConnection().   
071         */
072        public void run() {
073            try {
074                AcceptThread inAccept = new AcceptThread(inboundPort, inSockets);
075                AcceptThread outAccept = new AcceptThread(outboundPort, outSockets);
076                Thread inThread = new Thread(inAccept);
077                Thread outThread = new Thread(outAccept);
078                inThread.start();
079                outThread.start();
080                log.info("TwoPortService running on ports " + inboundPort + " and " + outboundPort);
081    
082                while (keepRunning()) {
083                    Connection conn = accept(3000);
084                    if (conn != null) {
085                        newConnection(conn);
086                        log.info("Accepted connection from " + conn.getRemoteAddress().getHostAddress());
087                    }
088                }
089    
090                inAccept.stop();
091                outAccept.stop();
092            }
093            catch (Exception e) {
094                log.error("Error while accepting connections: ", e);
095            }
096        }
097    
098        /** 
099         * Returns a Connection based on an inbound and outbound connection pair from 
100         * the same remote host.  This is done by looping through all the connections
101         * trying to match the host addresses of all posible inbound and outbound 
102         * pairs.  When a matching pair is found, both sockets are removed from the 
103         * pending sockets lists, so there should normally be a very small number of 
104         * sockets to search through.  This method will return null if the specified 
105         * number of milliseconds has passed, otherwise will wait until a single remote 
106         * host has connected to both the inbound and outbound ports.  
107         */
108        private Connection accept(long timeoutMillis) throws LLPException, IOException {
109            long startTime = System.currentTimeMillis();
110            Connection conn = null;
111            while (conn == null && System.currentTimeMillis() < startTime + timeoutMillis) {
112                int i = 0;
113                while (conn == null && i < inSockets.size()) {
114                    Socket in = (Socket) inSockets.get(i);
115                    int j = 0;
116                    while (conn == null && j < outSockets.size()) {
117                        Socket out = (Socket) outSockets.get(j);
118                        if (out.getInetAddress().getHostAddress().equals(in.getInetAddress().getHostAddress())) {
119                            conn = new Connection(parser, llp, in, out);
120                            inSockets.remove(i);
121                            outSockets.remove(j);
122                        }
123                        j++;
124                    }
125                    i++;
126                }
127                try {
128                    Thread.sleep(10);
129                }
130                catch (InterruptedException e) {
131                }
132            }
133            return conn;
134        }
135    
136        /** 
137         * A Runnable that accepts connections on a ServerSocket and adds them to 
138         * a Vector, so that they can be matched later.  After stop() is called, the 
139         * ServerSocket is closed.
140         */
141        private class AcceptThread implements Runnable {
142    
143            private ServerSocket ss;
144            private Vector sockets;
145            private boolean keepRunning = true;
146    
147            public AcceptThread(int port, Vector sockets) throws IOException, SocketException {
148                ss = new ServerSocket(port);
149                ss.setSoTimeout(3000);
150                this.sockets = sockets;
151            }
152    
153            public void run() {
154                try {
155                    while (keepRunning) {
156                        try {
157                            Socket s = ss.accept();
158                            sockets.add(s);
159                        }
160                        catch (InterruptedIOException e) { /* OK - just timed out */
161                        }
162                    }
163                    ss.close();
164                }
165                catch (Exception e) {
166                    log.error("Problem running connection accept thread", e);
167                }
168            }
169    
170            public void stop() {
171                keepRunning = false;
172            }
173        }
174    
175        /**
176         * Run server from command line.  Inbound and outbound port numbers should be provided as arguments,
177         * and a file containing a list of Applications to use can also be specified
178         * as an optional argument (as per <code>super.loadApplicationsFromFile(...)</code>).
179         * Uses the default LowerLayerProtocol.
180         */
181        public static void main(String args[]) {
182            if (args.length < 2 || args.length > 3) {
183                System.out.println(
184                    "Usage: ca.uhn.hl7v2.app.TwoPortService inbound_port outbound_port [application_spec_file_name]");
185                System.exit(1);
186            }
187    
188            int inPort = 0;
189            int outPort = 0;
190            try {
191                inPort = Integer.parseInt(args[0]);
192                outPort = Integer.parseInt(args[1]);
193            }
194            catch (NumberFormatException e) {
195                System.err.println("One of the given ports (" + args[0] + " or " + args[1] + ") is not an integer.");
196                System.exit(1);
197            }
198    
199            File appFile = null;
200            if (args.length == 3) {
201                appFile = new File(args[2]);
202            }
203    
204            try {
205                TwoPortService server = new TwoPortService(new PipeParser(), LowerLayerProtocol.makeLLP(), inPort, outPort);
206                if (appFile != null)
207                    server.loadApplicationsFromFile(appFile);
208                server.start();
209            }
210            catch (Exception e) {
211                e.printStackTrace();
212            }
213    
214        }
215    
216    }