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 "HL7Server.java". Description: 010 "A TCP/IP based server." 011 012 The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 2004. 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.protocol.impl; 028 029 import java.io.IOException; 030 import java.net.MalformedURLException; 031 import java.net.ServerSocket; 032 import java.net.URL; 033 import java.util.ArrayList; 034 import java.util.Iterator; 035 import java.util.List; 036 import java.util.StringTokenizer; 037 038 import ca.uhn.hl7v2.HL7Exception; 039 import ca.uhn.hl7v2.protocol.ApplicationRouter; 040 import ca.uhn.hl7v2.protocol.Processor; 041 import ca.uhn.hl7v2.protocol.ProcessorContext; 042 import ca.uhn.hl7v2.protocol.SafeStorage; 043 import ca.uhn.hl7v2.protocol.TransportException; 044 import ca.uhn.hl7v2.protocol.TransportLayer; 045 import ca.uhn.log.HapiLog; 046 import ca.uhn.log.HapiLogFactory; 047 048 /** 049 * A TCP/IP based server. 050 * 051 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> 052 * @version $Revision: 1.2 $ updated on $Date: 2009/06/30 13:30:45 $ by $Author: jamesagnew $ 053 */ 054 public class HL7Server { 055 056 private static HapiLog log = HapiLogFactory.getHapiLog(HL7Server.class); 057 058 private final ServerSocket myServerSocket; 059 private ServerSocket myServerSocket2; 060 private final ApplicationRouter myRouter; 061 private final SafeStorage myStorage; 062 063 private ProcessorContext myContext; 064 private boolean myIsRunning = false; 065 private List myProcessors; 066 067 /** 068 * @param theServerSocket a ServerSocket on which to listen for connections that will 069 * be used for both locally-driven and remotely-driven message exchanges 070 * @param theRouter used to send incoming messages to appropriate <code>Application</code>s 071 * @param theStorage used to commit incoming messages to safe storage before returning 072 * an accept ACK 073 */ 074 public HL7Server(ServerSocket theServerSocket, ApplicationRouter theRouter, SafeStorage theStorage) { 075 myServerSocket = theServerSocket; 076 myRouter = theRouter; 077 myStorage = theStorage; 078 initProcessorList(); 079 } 080 081 /** 082 * @param theLocallyDriven a ServerSocket on which to listen for connections that will 083 * be used for locally-initiated message exchanges 084 * @param theRemotelyDriven a ServerSocket on which to listen for connections that will 085 * be used for remotely-initiated message exchanges 086 * @param theRouter used to send incoming messages to appropriate <code>Application</code>s 087 * @param theStorage used to commit incoming messages to safe storage before returning 088 * an accept ACK 089 */ 090 public HL7Server(ServerSocket theLocallyDriven, ServerSocket theRemotelyDriven, 091 ApplicationRouter theRouter, SafeStorage theStorage) { 092 093 myServerSocket = theLocallyDriven; 094 myServerSocket2 = theRemotelyDriven; 095 myRouter = theRouter; 096 myStorage = theStorage; 097 initProcessorList(); 098 } 099 100 //creates list and starts thread to clean dead processors from it 101 private void initProcessorList() { 102 myProcessors = new ArrayList(); 103 104 final List processors = myProcessors; 105 Thread cleaner = new Thread() { 106 public void run() { 107 try { 108 Thread.sleep(1000); 109 } catch (InterruptedException e) {} 110 111 synchronized (processors) { 112 Iterator it = processors.iterator(); 113 while (it.hasNext()) { 114 Processor proc = (Processor) it.next(); 115 if (!proc.getContext().getLocallyDrivenTransportLayer().isConnected() 116 || !proc.getContext().getRemotelyDrivenTransportLayer().isConnected()) { 117 it.remove(); 118 } 119 } 120 } 121 } 122 }; 123 cleaner.start(); 124 } 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 TransportLayer transport = getTransport(myServerSocket, theAddress); 139 ProcessorContext context = null; 140 141 if (myServerSocket2 == null) { //we're doing inbound & outbound on the same port 142 transport.connect(); 143 context = new ProcessorContextImpl(myRouter, transport, myStorage); 144 } else { 145 TransportLayer transport2 = getTransport(myServerSocket2, theAddress); 146 DualTransportConnector connector = new DualTransportConnector(transport, transport2); 147 connector.connect(); 148 149 context = new ProcessorContextImpl(myRouter, transport, transport2, myStorage); 150 } 151 return new ProcessorImpl(context, true); 152 } 153 154 private static TransportLayer getTransport(ServerSocket theServerSocket, String theAddress) throws TransportException { 155 ServerSocketStreamSource ss = new ServerSocketStreamSource(theServerSocket, theAddress); 156 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 final HL7Server server = this; 169 Runnable acceptor = new Runnable() { 170 public void run() { 171 while (server.isRunning()) { 172 try { 173 Processor p = server.accept(theAddress); 174 server.newProcessor(p); 175 Thread.sleep(1); 176 } catch (TransportException e) { 177 log.error(e); 178 } catch (InterruptedException e) { 179 } 180 } 181 } 182 }; 183 184 myIsRunning = true; 185 186 Thread thd = new Thread(acceptor); 187 thd.start(); 188 } 189 190 private void newProcessor(Processor theProcessor) { 191 synchronized (myProcessors) { 192 myProcessors.add(theProcessor); 193 } 194 } 195 196 /** 197 * Stops running after the next connection is made. 198 */ 199 public void stop() { 200 myIsRunning = false; 201 } 202 203 /** 204 * Returns <code>true</code> between when start() returns and when stop() is called. 205 * 206 * Note that this is not the same as checking whether there are any active connections to 207 * this server. To determine this, call {@link #getProcessors()} and check whether the array 208 * returned is non-empty. 209 * 210 * @return true between when start() returns and when stop() is called. 211 */ 212 public boolean isRunning() { 213 return myIsRunning; 214 } 215 216 /** 217 * @return <code>Processor</code>s arising from connections to this server 218 */ 219 public Processor[] getProcessors() { 220 synchronized (myProcessors) { 221 return (Processor[]) myProcessors.toArray(new Processor[0]); 222 } 223 } 224 225 /** 226 * 227 * @param theUrlSpec a string specifying an URL, which can optionally begin with "classpath:" 228 * @return the resource specified after "classpath:", if that's how it starts, otherwise 229 * new URL(theUrlSpec) 230 * @throws MalformedURLException 231 */ 232 private static URL getURL(String theUrlSpec) throws MalformedURLException { 233 URL url = null; 234 if (theUrlSpec.startsWith("classpath:")) { 235 StringTokenizer tok = new StringTokenizer(theUrlSpec, ":", false); 236 tok.nextToken(); 237 String resource = tok.nextToken(); 238 url = Thread.currentThread().getContextClassLoader().getResource(resource); 239 } else { 240 url = new URL(theUrlSpec); 241 } 242 return url; 243 } 244 245 public static void main(String[] args) { 246 if (args.length < 1 || args.length > 3) { 247 System.out.println("Usage: HL7Server (shared_port | (locally_driven_port remotely_driven_port)) app_binding_URL"); 248 System.exit(1); 249 } 250 251 SafeStorage storage = new NullSafeStorage(); 252 ApplicationRouter router = new ApplicationRouterImpl(); 253 254 try { 255 HL7Server server = null; 256 String appURL = null; 257 if (args.length == 2) { 258 int port = Integer.parseInt(args[0]); 259 server = new HL7Server(new ServerSocket(port), router, storage); 260 appURL = args[1]; 261 } else { 262 int localPort = Integer.parseInt(args[0]); 263 int remotePort = Integer.parseInt(args[1]); 264 server = new HL7Server(new ServerSocket(localPort), new ServerSocket(remotePort), router, storage); 265 appURL = args[2]; 266 } 267 268 ApplicationLoader.loadApplications(router, getURL(appURL)); 269 270 server.start(null); //any address OK 271 272 } catch (NumberFormatException e) { 273 System.out.println("Port arguments must be integers"); 274 System.exit(2); 275 } catch (IOException e) { 276 e.printStackTrace(); 277 System.exit(3); 278 } catch (HL7Exception e) { 279 e.printStackTrace(); 280 System.exit(4); 281 } catch (ClassNotFoundException e) { 282 e.printStackTrace(); 283 System.exit(5); 284 } catch (InstantiationException e) { 285 e.printStackTrace(); 286 System.exit(6); 287 } catch (IllegalAccessException e) { 288 e.printStackTrace(); 289 System.exit(7); 290 } 291 292 } 293 }