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 "HL7Server.java". Description: 010"A TCP/IP based server." 011 012The Initial Developer of the Original Code is University Health Network. Copyright (C) 0132004. All Rights Reserved. 014 015Contributor(s): ______________________________________. 016 017Alternatively, the contents of this file may be used under the terms of the 018GNU General Public License (the �GPL�), in which case the provisions of the GPL are 019applicable instead of those above. If you wish to allow use of your version of this 020file only under the terms of the GPL and not to allow others to use your version 021of this file under the MPL, indicate your decision by deleting the provisions above 022and replace them with the notice and other provisions required by the GPL License. 023If you do not delete the provisions above, a recipient may use your version of 024this file under either the MPL or the GPL. 025*/ 026 027package ca.uhn.hl7v2.protocol.impl; 028 029import java.io.IOException; 030import java.net.MalformedURLException; 031import java.net.ServerSocket; 032import java.net.URL; 033import java.util.ArrayList; 034import java.util.Iterator; 035import java.util.List; 036import java.util.StringTokenizer; 037 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import ca.uhn.hl7v2.HL7Exception; 042import ca.uhn.hl7v2.protocol.ApplicationRouter; 043import ca.uhn.hl7v2.protocol.Processor; 044import ca.uhn.hl7v2.protocol.ProcessorContext; 045import ca.uhn.hl7v2.protocol.SafeStorage; 046import ca.uhn.hl7v2.protocol.TransportException; 047import ca.uhn.hl7v2.protocol.TransportLayer; 048 049/** 050 * A TCP/IP based server. 051 * 052 * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a> 053 * @version $Revision: 1.2 $ updated on $Date: 2009-06-30 13:30:45 $ by $Author: jamesagnew $ 054 */ 055public class HL7Server { 056 057 private static final Logger log = LoggerFactory.getLogger(HL7Server.class); 058 059 private final ServerSocket myServerSocket; 060 private ServerSocket myServerSocket2; 061 private final ApplicationRouter myRouter; 062 private final SafeStorage myStorage; 063 064 private boolean myIsRunning = false; 065 private List<Processor> 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<Processor> processors = myProcessors; 105 Thread cleaner = new Thread(() -> { 106 try { 107 Thread.sleep(1000); 108 } catch (InterruptedException ignored) { 109 } 110 111 synchronized (processors) { 112 Iterator<Processor> it = processors.iterator(); 113 while (it.hasNext()) { 114 Processor proc = it.next(); 115 if (!proc.getContext().getLocallyDrivenTransportLayer().isConnected() 116 || !proc.getContext().getRemotelyDrivenTransportLayer().isConnected()) { 117 it.remove(); 118 } 119 } 120 } 121 }); 122 cleaner.start(); 123 } 124 125 /** 126 * Accepts a single inbound connection if the same ServerSocket is used for 127 * all message exchanges, or a connection from each if two ServerSockets are 128 * being used. 129 * 130 * @param theAddress the IP address from which to accept connections (null means 131 * accept from any address). Connection attempts from other addresses will 132 * be ignored. 133 * @return a <code>Processor</code> connected to the given address 134 * @throws TransportException 135 */ 136 public Processor accept(String theAddress) throws TransportException { 137 TransportLayer transport = getTransport(myServerSocket, theAddress); 138 ProcessorContext context; 139 140 if (myServerSocket2 == null) { //we're doing inbound & outbound on the same port 141 transport.connect(); 142 context = new ProcessorContextImpl(myRouter, transport, myStorage); 143 } else { 144 TransportLayer transport2 = getTransport(myServerSocket2, theAddress); 145 DualTransportConnector connector = new DualTransportConnector(transport, transport2); 146 connector.connect(); 147 148 context = new ProcessorContextImpl(myRouter, transport, transport2, myStorage); 149 } 150 return new ProcessorImpl(context, true); 151 } 152 153 private static TransportLayer getTransport(ServerSocket theServerSocket, String theAddress) throws TransportException { 154 ServerSocketStreamSource ss = new ServerSocketStreamSource(theServerSocket, theAddress); 155 return new MLLPTransport(ss); 156 } 157 158 /** 159 * Starts accepting connections in a new Thread. Note that this can be 160 * called multiple times with separate addresses. The stop() method ends 161 * all Threads started here. 162 * 163 * @param theAddress IP address from which connections are accepted (null 164 * means any address is OK) 165 */ 166 public void start(final String theAddress) { 167 final HL7Server server = this; 168 Runnable acceptor = () -> { 169 while (server.isRunning()) { 170 try { 171 Processor p = server.accept(theAddress); 172 if (!myIsRunning) { 173 p.stop(); 174 } else { 175 server.newProcessor(p); 176 Thread.sleep(1); 177 } 178 } catch (TransportException e) { 179 log.error(e.getMessage(), e); 180 } catch (InterruptedException ignored) { 181 } 182 } 183 }; 184 185 myIsRunning = true; 186 187 Thread thd = new Thread(acceptor); 188 thd.start(); 189 } 190 191 private synchronized void newProcessor(Processor theProcessor) { 192 myProcessors.add(theProcessor); 193 } 194 195 /** 196 * Stops running after the next connection is made. 197 */ 198 public synchronized void stop() { 199 myIsRunning = false; 200 for (Processor next : myProcessors) { 201 next.stop(); 202 } 203 } 204 205 /** 206 * Returns <code>true</code> between when start() returns and when stop() is called. 207 * <p> 208 * Note that this is not the same as checking whether there are any active connections to 209 * this server. To determine this, call {@link #getProcessors()} and check whether the array 210 * returned is non-empty. 211 * 212 * @return true between when start() returns and when stop() is called. 213 */ 214 public boolean isRunning() { 215 return myIsRunning; 216 } 217 218 /** 219 * @return <code>Processor</code>s arising from connections to this server 220 */ 221 public Processor[] getProcessors() { 222 return myProcessors.toArray(new Processor[0]); 223 } 224 225 /** 226 * @param theUrlSpec a string specifying an URL, which can optionally begin with "classpath:" 227 * @return the resource specified after "classpath:", if that's how it starts, otherwise 228 * new URL(theUrlSpec) 229 * @throws MalformedURLException 230 */ 231 private static URL getURL(String theUrlSpec) throws MalformedURLException { 232 URL url; 233 if (theUrlSpec.startsWith("classpath:")) { 234 StringTokenizer tok = new StringTokenizer(theUrlSpec, ":", false); 235 tok.nextToken(); 236 String resource = tok.nextToken(); 237 url = Thread.currentThread().getContextClassLoader().getResource(resource); 238 } else { 239 url = new URL(theUrlSpec); 240 } 241 return url; 242 } 243 244 public static void main(String[] args) { 245 if (args.length < 1 || args.length > 3) { 246 System.out.println("Usage: HL7Server (shared_port | (locally_driven_port remotely_driven_port)) app_binding_URL"); 247 System.exit(1); 248 } 249 250 SafeStorage storage = new NullSafeStorage(); 251 ApplicationRouter router = new ApplicationRouterImpl(); 252 253 try { 254 HL7Server server; 255 String appURL; 256 if (args.length == 2) { 257 int port = Integer.parseInt(args[0]); 258 server = new HL7Server(new ServerSocket(port), router, storage); 259 appURL = args[1]; 260 } else { 261 int localPort = Integer.parseInt(args[0]); 262 int remotePort = Integer.parseInt(args[1]); 263 server = new HL7Server(new ServerSocket(localPort), new ServerSocket(remotePort), router, storage); 264 appURL = args[2]; 265 } 266 267 ApplicationLoader.loadApplications(router, getURL(appURL)); 268 269 server.start(null); //any address OK 270 271 } catch (NumberFormatException e) { 272 System.out.println("Port arguments must be integers"); 273 System.exit(2); 274 } catch (IOException e) { 275 e.printStackTrace(); 276 System.exit(3); 277 } catch (HL7Exception e) { 278 e.printStackTrace(); 279 System.exit(4); 280 } catch (ClassNotFoundException e) { 281 e.printStackTrace(); 282 System.exit(5); 283 } catch (InstantiationException e) { 284 e.printStackTrace(); 285 System.exit(6); 286 } catch (IllegalAccessException e) { 287 e.printStackTrace(); 288 System.exit(7); 289 } 290 291 } 292}