Coverage Report - ca.uhn.hl7v2.protocol.impl.HL7Server
 
Classes in this File Line Coverage Branch Coverage Complexity
HL7Server
41%
42/101
25%
3/12
2.429
HL7Server$1
100%
12/12
83%
5/6
2.429
HL7Server$2
58%
7/12
50%
2/4
2.429
 
 1  
 /*
 2  
 The contents of this file are subject to the Mozilla Public License Version 1.1 
 3  
 (the "License"); you may not use this file except in compliance with the License. 
 4  
 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
 5  
 Software distributed under the License is distributed on an "AS IS" basis, 
 6  
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
 7  
 specific language governing rights and limitations under the License. 
 8  
 
 9  
 The Original Code is "HL7Server.java".  Description: 
 10  
 "A TCP/IP based server." 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2004.  All Rights Reserved. 
 14  
 
 15  
 Contributor(s): ______________________________________. 
 16  
 
 17  
 Alternatively, the contents of this file may be used under the terms of the 
 18  
 GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
 19  
 applicable instead of those above.  If you wish to allow use of your version of this 
 20  
 file only under the terms of the GPL and not to allow others to use your version 
 21  
 of this file under the MPL, indicate your decision by deleting  the provisions above 
 22  
 and replace  them with the notice and other provisions required by the GPL License.  
 23  
 If you do not delete the provisions above, a recipient may use your version of 
 24  
 this file under either the MPL or the GPL. 
 25  
 */
 26  
 
 27  
 package ca.uhn.hl7v2.protocol.impl;
 28  
 
 29  
 import java.io.IOException;
 30  
 import java.net.MalformedURLException;
 31  
 import java.net.ServerSocket;
 32  
 import java.net.URL;
 33  
 import java.util.ArrayList;
 34  
 import java.util.Iterator;
 35  
 import java.util.List;
 36  
 import java.util.StringTokenizer;
 37  
 
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 import ca.uhn.hl7v2.HL7Exception;
 42  
 import ca.uhn.hl7v2.protocol.ApplicationRouter;
 43  
 import ca.uhn.hl7v2.protocol.Processor;
 44  
 import ca.uhn.hl7v2.protocol.ProcessorContext;
 45  
 import ca.uhn.hl7v2.protocol.SafeStorage;
 46  
 import ca.uhn.hl7v2.protocol.TransportException;
 47  
 import ca.uhn.hl7v2.protocol.TransportLayer;
 48  
 
 49  
 /**
 50  
  * A TCP/IP based server. 
 51  
  * 
 52  
  * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
 53  
  * @version $Revision: 1.2 $ updated on $Date: 2009-06-30 13:30:45 $ by $Author: jamesagnew $
 54  
  */
 55  4
 public class HL7Server {
 56  
 
 57  1
     private static Logger log = LoggerFactory.getLogger(HL7Server.class);
 58  
     
 59  
     private final ServerSocket myServerSocket;
 60  
     private ServerSocket myServerSocket2;
 61  
     private final ApplicationRouter myRouter;
 62  
     private final SafeStorage myStorage;
 63  
     
 64  2
     private boolean myIsRunning = false;
 65  
     private List<Processor> myProcessors;
 66  
     
 67  
     /**
 68  
      * @param theServerSocket a ServerSocket on which to listen for connections that will
 69  
      *      be used for both locally-driven and remotely-driven message exchanges
 70  
      * @param theRouter used to send incoming messages to appropriate <code>Application</code>s
 71  
      * @param theStorage used to commit incoming messages to safe storage before returning 
 72  
      *      an accept ACK 
 73  
      */
 74  2
     public HL7Server(ServerSocket theServerSocket, ApplicationRouter theRouter, SafeStorage theStorage) {
 75  2
         myServerSocket = theServerSocket;
 76  2
         myRouter = theRouter;
 77  2
         myStorage = theStorage;
 78  2
         initProcessorList();
 79  2
     }
 80  
     
 81  
     /**
 82  
      * @param theLocallyDriven a ServerSocket on which to listen for connections that will
 83  
      *      be used for locally-initiated message exchanges
 84  
      * @param theRemotelyDriven a ServerSocket on which to listen for connections that will
 85  
      *      be used for remotely-initiated message exchanges
 86  
      * @param theRouter used to send incoming messages to appropriate <code>Application</code>s
 87  
      * @param theStorage used to commit incoming messages to safe storage before returning 
 88  
      *      an accept ACK 
 89  
      */
 90  
     public HL7Server(ServerSocket theLocallyDriven, ServerSocket theRemotelyDriven, 
 91  0
         ApplicationRouter theRouter, SafeStorage theStorage) {
 92  
     
 93  0
         myServerSocket = theLocallyDriven;
 94  0
         myServerSocket2 = theRemotelyDriven;
 95  0
         myRouter = theRouter;
 96  0
         myStorage = theStorage;  
 97  0
         initProcessorList();       
 98  0
     }
 99  
 
 100  
     //creates list and starts thread to clean dead processors from it     
 101  
     private void initProcessorList() {
 102  2
         myProcessors = new ArrayList<Processor>();
 103  
         
 104  2
         final List<Processor> processors = myProcessors; 
 105  2
         Thread cleaner = new Thread() {
 106  
             public void run() {
 107  
                 try {
 108  2
                     Thread.sleep(1000);
 109  2
                 } catch (InterruptedException e) {}
 110  
                 
 111  2
                 synchronized (processors) {
 112  2
                     Iterator<Processor> it = processors.iterator();
 113  4
                     while (it.hasNext()) {
 114  2
                         Processor proc = it.next();
 115  2
                         if (!proc.getContext().getLocallyDrivenTransportLayer().isConnected() 
 116  
                                 || !proc.getContext().getRemotelyDrivenTransportLayer().isConnected()) {
 117  1
                             it.remove();
 118  
                         }
 119  2
                     }
 120  2
                 }
 121  2
             }
 122  
         };
 123  2
         cleaner.start();
 124  2
     }
 125  
         
 126  
     /**
 127  
      * Accepts a single inbound connection if the same ServerSocket is used for 
 128  
      * all message exchanges, or a connection from each if two ServerSockets are 
 129  
      * being used. 
 130  
      *  
 131  
      * @param theAddress the IP address from which to accept connections (null means 
 132  
      *      accept from any address).  Connection attempts from other addresses will 
 133  
      *      be ignored.  
 134  
      * @return a <code>Processor</code> connected to the given address  
 135  
      * @throws TransportException
 136  
      */
 137  
     public Processor accept(String theAddress) throws TransportException {
 138  4
         TransportLayer transport = getTransport(myServerSocket, theAddress);
 139  4
         ProcessorContext context = null;
 140  
         
 141  4
         if (myServerSocket2 == null) { //we're doing inbound & outbound on the same port
 142  4
             transport.connect();
 143  2
             context = new ProcessorContextImpl(myRouter, transport, myStorage);
 144  
         } else {
 145  0
             TransportLayer transport2 = getTransport(myServerSocket2, theAddress);
 146  0
             DualTransportConnector connector = new DualTransportConnector(transport, transport2);
 147  0
             connector.connect();
 148  
             
 149  0
             context = new ProcessorContextImpl(myRouter, transport, transport2, myStorage);
 150  
         }
 151  2
         return new ProcessorImpl(context, true);        
 152  
     }
 153  
     
 154  
     private static TransportLayer getTransport(ServerSocket theServerSocket, String theAddress) throws TransportException {
 155  4
         ServerSocketStreamSource ss = new ServerSocketStreamSource(theServerSocket, theAddress);
 156  4
         return new MLLPTransport(ss);
 157  
     }
 158  
     
 159  
     /**
 160  
      * Starts accepting connections in a new Thread.  Note that this can be 
 161  
      * called multiple times with separate addresses.  The stop() method ends
 162  
      * all Threads started here.  
 163  
      * 
 164  
      * @param theAddress IP address from which connections are accepted (null 
 165  
      *  means any address is OK) 
 166  
      */
 167  
     public void start(final String theAddress) {  
 168  2
         final HL7Server server = this;      
 169  2
         Runnable acceptor = new Runnable() {
 170  
             public void run() {
 171  4
                 while (server.isRunning()) {
 172  
                     try {
 173  4
                         Processor p = server.accept(theAddress);
 174  2
                         if (!myIsRunning) {
 175  0
                                 p.stop();
 176  
                         } else {
 177  2
                             server.newProcessor(p); 
 178  2
                             Thread.sleep(1);
 179  
                         }
 180  0
                     } catch (TransportException e) {
 181  0
                         log.error(e.getMessage(), e);
 182  0
                     } catch (InterruptedException e) {
 183  2
                     } 
 184  
                 }
 185  0
             }
 186  
         };
 187  
         
 188  2
         myIsRunning = true;
 189  
         
 190  2
         Thread thd = new Thread(acceptor);
 191  2
         thd.start();
 192  2
     }
 193  
     
 194  
     private void newProcessor(Processor theProcessor) {
 195  2
         synchronized (myProcessors) {
 196  2
             myProcessors.add(theProcessor);
 197  2
         }
 198  2
     }
 199  
     
 200  
     /**
 201  
      * Stops running after the next connection is made. 
 202  
      */
 203  
     public void stop() {
 204  1
         myIsRunning = false;
 205  1
         synchronized (myProcessors) {
 206  1
             for (Processor next : myProcessors) {
 207  1
                     next.stop();
 208  1
             }
 209  1
         }
 210  1
     }
 211  
     
 212  
     /**
 213  
      * Returns <code>true</code> between when start() returns and when stop() is called.
 214  
      * 
 215  
      * Note that this is not the same as checking whether there are any active connections to
 216  
      * this server. To determine this, call {@link #getProcessors()} and check whether the array
 217  
      * returned is non-empty.
 218  
      * 
 219  
      * @return true between when start() returns and when stop() is called.  
 220  
      */
 221  
     public boolean isRunning() {
 222  4
         return myIsRunning;
 223  
     }
 224  
     
 225  
     /**
 226  
      * @return <code>Processor</code>s arising from connections to this server 
 227  
      */
 228  
     public Processor[] getProcessors() {
 229  1
         synchronized (myProcessors) {
 230  1
             return (Processor[]) myProcessors.toArray(new Processor[0]);
 231  0
         }
 232  
     }
 233  
     
 234  
     /**
 235  
      * 
 236  
      * @param theUrlSpec a string specifying an URL, which can optionally begin with "classpath:" 
 237  
      * @return the resource specified after "classpath:", if that's how it starts, otherwise 
 238  
      *      new URL(theUrlSpec) 
 239  
      * @throws MalformedURLException
 240  
      */
 241  
     private static URL getURL(String theUrlSpec) throws MalformedURLException {
 242  0
         URL url = null;
 243  0
         if (theUrlSpec.startsWith("classpath:")) {
 244  0
             StringTokenizer tok = new StringTokenizer(theUrlSpec, ":", false);
 245  0
             tok.nextToken();
 246  0
             String resource = tok.nextToken();
 247  0
             url = Thread.currentThread().getContextClassLoader().getResource(resource);
 248  0
         } else {
 249  0
             url = new URL(theUrlSpec);
 250  
         }
 251  0
         return url;
 252  
     }
 253  
     
 254  
     public static void main(String[] args) {
 255  0
         if (args.length < 1 || args.length > 3) {
 256  0
             System.out.println("Usage: HL7Server (shared_port | (locally_driven_port remotely_driven_port)) app_binding_URL");
 257  0
             System.exit(1);
 258  
         }
 259  
         
 260  0
         SafeStorage storage = new NullSafeStorage();
 261  0
         ApplicationRouter router = new ApplicationRouterImpl();
 262  
         
 263  
         try {
 264  0
             HL7Server server = null;
 265  0
             String appURL = null;
 266  0
             if (args.length == 2) {
 267  0
                 int port = Integer.parseInt(args[0]);
 268  0
                 server = new HL7Server(new ServerSocket(port), router, storage);
 269  0
                 appURL = args[1];                
 270  0
             } else {
 271  0
                 int localPort = Integer.parseInt(args[0]);
 272  0
                 int remotePort = Integer.parseInt(args[1]);
 273  0
                 server = new HL7Server(new ServerSocket(localPort), new ServerSocket(remotePort), router, storage);
 274  0
                 appURL = args[2];
 275  
             }
 276  
             
 277  0
             ApplicationLoader.loadApplications(router, getURL(appURL));
 278  
             
 279  0
             server.start(null); //any address OK            
 280  
             
 281  0
         } catch (NumberFormatException e) {
 282  0
             System.out.println("Port arguments must be integers");
 283  0
             System.exit(2);
 284  0
         } catch (IOException e) {
 285  0
             e.printStackTrace();
 286  0
             System.exit(3);
 287  0
         } catch (HL7Exception e) {
 288  0
             e.printStackTrace();
 289  0
             System.exit(4);
 290  0
         } catch (ClassNotFoundException e) {
 291  0
             e.printStackTrace();
 292  0
             System.exit(5);
 293  0
         } catch (InstantiationException e) {
 294  0
             e.printStackTrace();
 295  0
             System.exit(6);
 296  0
         } catch (IllegalAccessException e) {
 297  0
             e.printStackTrace();
 298  0
             System.exit(7);
 299  0
         } 
 300  
 
 301  0
     }
 302  
 }