1 package ca.uhn.hl7v2.hoh.raw.client;
2
3 import static ca.uhn.hl7v2.hoh.util.StringUtils.isBlank;
4
5 import java.io.BufferedInputStream;
6 import java.io.BufferedOutputStream;
7 import java.io.IOException;
8 import java.io.OutputStream;
9 import java.net.InetSocketAddress;
10 import java.net.MalformedURLException;
11 import java.net.Socket;
12 import java.net.URI;
13 import java.net.URISyntaxException;
14 import java.net.URL;
15 import java.nio.charset.Charset;
16
17 import ca.uhn.hl7v2.hoh.api.DecodeException;
18 import ca.uhn.hl7v2.hoh.api.EncodeException;
19 import ca.uhn.hl7v2.hoh.api.IAuthorizationClientCallback;
20 import ca.uhn.hl7v2.hoh.api.IClient;
21 import ca.uhn.hl7v2.hoh.api.IReceivable;
22 import ca.uhn.hl7v2.hoh.api.ISendable;
23 import ca.uhn.hl7v2.hoh.api.MessageMetadataKeys;
24 import ca.uhn.hl7v2.hoh.encoder.Hl7OverHttpRequestEncoder;
25 import ca.uhn.hl7v2.hoh.encoder.Hl7OverHttpResponseDecoder;
26 import ca.uhn.hl7v2.hoh.encoder.NoMessageReceivedException;
27 import ca.uhn.hl7v2.hoh.raw.api.RawReceivable;
28 import ca.uhn.hl7v2.hoh.sign.ISigner;
29 import ca.uhn.hl7v2.hoh.sign.SignatureVerificationException;
30 import ca.uhn.hl7v2.hoh.sockets.ISocketFactory;
31 import ca.uhn.hl7v2.hoh.sockets.StandardSocketFactory;
32 import ca.uhn.hl7v2.hoh.sockets.TlsSocketFactory;
33
34 public abstract class AbstractRawClient implements IClient {
35
36
37
38
39 public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
40
41
42
43
44 public static final int DEFAULT_CONNECTION_TIMEOUT = 10000;
45
46
47
48
49
50 public static final int DEFAULT_RESPONSE_TIMEOUT = 60000;
51
52 private static final StandardSocketFactory DEFAULT_SOCKET_FACTORY = new StandardSocketFactory();
53
54 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HohRawClientSimple.class);
55
56 private IAuthorizationClientCallback myAuthorizationCallback;
57 private Charset myCharset = DEFAULT_CHARSET;
58 private int myConnectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
59 private String myHost;
60 private BufferedInputStream myInputStream;
61 private boolean myKeepAlive = true;
62 private OutputStream myOutputStream;
63 private String myPath;
64 private int myPort;
65 private long myResponseTimeout = DEFAULT_RESPONSE_TIMEOUT;
66 private ISigner mySigner;
67 private ISocketFactory mySocketFactory = DEFAULT_SOCKET_FACTORY;
68
69
70
71 private int mySoTimeout = 5000;
72 private URL myUrl;
73
74
75
76 public AbstractRawClient() {
77
78 }
79
80
81
82
83
84
85
86
87
88
89
90 public AbstractRawClient(String theHost, int thePort, String thePath) {
91 setHost(theHost);
92 setPort(thePort);
93 setUriPath(thePath);
94 }
95
96
97
98
99
100
101
102
103
104
105 public AbstractRawClient(URL theUrl) {
106 setUrl(theUrl);
107 }
108
109 protected void closeSocket(Socket theSocket) {
110 ourLog.debug("Closing socket");
111 try {
112 theSocket.close();
113 } catch (IOException e) {
114 ourLog.warn("Problem closing socket", e);
115 }
116 }
117
118 protected Socket connect() throws IOException {
119 ourLog.debug("Creating new connection to {}:{} for URI {}", new Object[] { myHost, myPort, myPath });
120
121 Socket socket = mySocketFactory.createClientSocket();
122 socket.connect(new InetSocketAddress(myHost, myPort), myConnectionTimeout);
123 socket.setSoTimeout(mySoTimeout);
124 socket.setKeepAlive(myKeepAlive);
125 ourLog.trace("Connection established to {}:{}", myHost, myPort);
126 myOutputStream = new BufferedOutputStream(socket.getOutputStream());
127 myInputStream = new BufferedInputStream(socket.getInputStream());
128 return socket;
129 }
130
131 private IReceivable<String> doSendAndReceiveInternal(ISendable<?> theMessageToSend, Socket socket) throws IOException, DecodeException, SignatureVerificationException, EncodeException {
132 ourLog.trace("Entering doSendAndReceiveInternal()");
133
134 Hl7OverHttpRequestEncoder enc = new Hl7OverHttpRequestEncoder();
135 enc.setPath(myPath);
136 enc.setHost(myHost);
137 enc.setPort(myPort);
138 enc.setCharset(myCharset);
139 if (myAuthorizationCallback != null) {
140 enc.setUsername(myAuthorizationCallback.provideUsername(myPath));
141 enc.setPassword(myAuthorizationCallback.providePassword(myPath));
142 }
143 enc.setSigner(mySigner);
144 enc.setDataProvider(theMessageToSend);
145
146 ourLog.debug("Writing message to OutputStream");
147 enc.encodeToOutputStream(myOutputStream);
148 myOutputStream.flush();
149
150 ourLog.debug("Reading response from OutputStream");
151
152 RawReceivable response = null;
153 long endTime = System.currentTimeMillis() + myResponseTimeout;
154 do {
155 try {
156 Hl7OverHttpResponseDecoder d = new Hl7OverHttpResponseDecoder();
157 d.setSigner(mySigner);
158 d.setReadTimeout(myResponseTimeout);
159 d.readHeadersAndContentsFromInputStreamAndDecode(myInputStream);
160
161 response = new RawReceivable(d.getMessage());
162 InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
163 String hostAddress = remoteSocketAddress.getAddress() != null ? remoteSocketAddress.getAddress().getHostAddress() : null;
164 response.addMetadata(MessageMetadataKeys.REMOTE_HOST_ADDRESS.name(), hostAddress);
165
166 if (d.isConnectionCloseHeaderPresent()) {
167 ourLog.debug("Found Connection=close header, closing socket");
168 closeSocket(socket);
169 }
170
171 } catch (NoMessageReceivedException ex) {
172 ourLog.debug("No message received yet");
173 } catch (IOException e) {
174 throw new DecodeException("Failed to read response from remote host", e);
175 }
176 } while (response == null && System.currentTimeMillis() < endTime);
177
178 ourLog.trace("Leaving doSendAndReceiveInternal()");
179 return response;
180 }
181
182
183
184
185
186
187 public String getHost() {
188 return myHost;
189 }
190
191
192
193
194
195
196 public int getPort() {
197 return myPort;
198 }
199
200
201
202
203
204
205 public ISocketFactory getSocketFactory() {
206 return mySocketFactory;
207 }
208
209 public int getSoTimeout() {
210 return mySoTimeout;
211 }
212
213
214
215
216
217
218 public String getUriPath() {
219 return myPath;
220 }
221
222
223
224
225 public URL getUrl() {
226 return myUrl;
227 }
228
229
230
231
232 public String getUrlString() {
233 return getUrl().toExternalForm();
234 }
235
236 public boolean isKeepAlive() {
237 return myKeepAlive;
238 }
239
240 boolean isSocketConnected(Socket socket) {
241 return socket != null && !socket.isClosed() && !socket.isInputShutdown() && !socket.isOutputShutdown();
242 }
243
244
245
246
247 protected abstract Socket provideSocket() throws IOException;
248
249
250
251
252
253 protected abstract void returnSocket(Socket theSocket);
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275 public synchronized IReceivable<String> sendAndReceive(ISendable<?> theMessageToSend) throws DecodeException, IOException, EncodeException {
276
277 Socket socket = provideSocket();
278 try {
279 return doSendAndReceiveInternal(theMessageToSend, socket);
280 } catch (DecodeException e) {
281 ourLog.debug("Decode exception, going to close socket", e);
282 closeSocket(socket);
283 throw e;
284 } catch (IOException e) {
285 ourLog.debug("Caught IOException, going to close socket", e);
286 closeSocket(socket);
287 throw e;
288 } catch (SignatureVerificationException e) {
289 ourLog.debug("Failed to verify message signature", e);
290 throw new DecodeException("Failed to verify message signature", e);
291 } finally {
292 returnSocket(socket);
293 }
294
295 }
296
297
298
299
300
301
302
303
304 public void setAuthorizationCallback(IAuthorizationClientCallback theAuthorizationCallback) {
305 myAuthorizationCallback = theAuthorizationCallback;
306 }
307
308
309
310
311 public void setCharset(Charset theCharset) {
312 if (theCharset == null) {
313 throw new NullPointerException("Charset can not be null");
314 }
315 myCharset = theCharset;
316 }
317
318
319
320
321 public void setHost(String theHost) {
322 myHost = theHost;
323 if (isBlank(theHost)) {
324 throw new IllegalArgumentException("Host can not be blank/null");
325 }
326 }
327
328
329
330
331 public void setKeepAlive(boolean theKeepAlive) {
332 myKeepAlive = theKeepAlive;
333 }
334
335
336
337
338 public void setPort(int thePort) {
339 myPort = thePort;
340 if (thePort <= 0) {
341 throw new IllegalArgumentException("Port must be a positive integer");
342 }
343 }
344
345
346
347
348
349 public void setResponseTimeout(long theResponseTimeout) {
350 if (theResponseTimeout <= 0) {
351 throw new IllegalArgumentException("Timeout can not be <= 0");
352 }
353 myResponseTimeout = theResponseTimeout;
354 }
355
356
357
358
359 public void setSigner(ISigner theSigner) {
360 mySigner = theSigner;
361 }
362
363
364
365
366 public void setSocketFactory(ISocketFactory theSocketFactory) {
367 if (theSocketFactory == null) {
368 throw new NullPointerException("Socket factory can not be null");
369 }
370 mySocketFactory = theSocketFactory;
371 }
372
373
374
375
376 public void setSoTimeout(int theSoTimeout) {
377 mySoTimeout = theSoTimeout;
378 }
379
380
381
382
383 public void setUriPath(String thePath) {
384 myPath = thePath;
385
386 if (isBlank(thePath)) {
387 myPath = "/";
388 }
389 if (!thePath.startsWith("/")) {
390 throw new IllegalArgumentException("Invalid URI (must start with '/'): " + thePath);
391 } else if (thePath.contains(" ")) {
392 throw new IllegalArgumentException("Invalid URI: " + thePath);
393 }
394
395
396 try {
397 new URI("http://localhost" + thePath);
398 } catch (URISyntaxException e) {
399 throw new IllegalArgumentException("Invalid URI: " + thePath);
400 }
401
402 }
403
404
405
406
407 public void setUrl(URL theUrl) {
408 setHost(extractHost(theUrl));
409 setPort(extractPort(theUrl));
410 setUriPath(extractUri(theUrl));
411
412 myUrl = theUrl;
413
414 if (getSocketFactory() == DEFAULT_SOCKET_FACTORY && theUrl.getProtocol().toLowerCase().equals("https")) {
415 setSocketFactory(new TlsSocketFactory());
416 }
417 }
418
419
420
421
422 public void setUrlString(String theString) {
423 try {
424 URL url = new URL(theString);
425 setUrl(url);
426 } catch (MalformedURLException e) {
427 throw new IllegalArgumentException("URL is not valid. Must be in the form http[s]:");
428 }
429 String protocol = myUrl.getProtocol().toLowerCase();
430 if (!protocol.equals("http") && !protocol.equals("https")) {
431 throw new IllegalStateException("URL protocol must be http or https");
432 }
433
434 }
435
436 private static String extractHost(URL theUrl) {
437 return theUrl.getHost();
438 }
439
440 private static int extractPort(URL theUrl) {
441 return theUrl.getPort() != -1 ? theUrl.getPort() : theUrl.getDefaultPort();
442 }
443
444 private static String extractUri(URL theUrl) {
445 return theUrl.getPath();
446 }
447 }