PageRenderTime 93ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/server/src/com/mirth/connect/connectors/tcp/TcpReceiver.java

https://github.com/get-vitamin-c/mirth-connect
Java | 923 lines | 699 code | 112 blank | 112 comment | 157 complexity | 5d2f64dca5317a1e06b463c28ed4aa4f MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, MPL-2.0-no-copyleft-exception, Apache-2.0, LGPL-2.0
  1. /*
  2. * Copyright (c) Mirth Corporation. All rights reserved.
  3. *
  4. * http://www.mirthcorp.com
  5. *
  6. * The software in this package is published under the terms of the MPL license a copy of which has
  7. * been included with this distribution in the LICENSE.txt file.
  8. */
  9. package com.mirth.connect.connectors.tcp;
  10. import java.io.BufferedOutputStream;
  11. import java.io.IOException;
  12. import java.io.UnsupportedEncodingException;
  13. import java.net.BindException;
  14. import java.net.InetAddress;
  15. import java.net.InetSocketAddress;
  16. import java.net.Socket;
  17. import java.net.SocketException;
  18. import java.net.SocketTimeoutException;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.Iterator;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import java.util.concurrent.Callable;
  25. import java.util.concurrent.ExecutionException;
  26. import java.util.concurrent.ExecutorService;
  27. import java.util.concurrent.Executors;
  28. import java.util.concurrent.Future;
  29. import java.util.concurrent.RejectedExecutionException;
  30. import java.util.concurrent.SynchronousQueue;
  31. import java.util.concurrent.ThreadPoolExecutor;
  32. import java.util.concurrent.TimeUnit;
  33. import java.util.concurrent.atomic.AtomicBoolean;
  34. import org.apache.commons.codec.binary.Base64;
  35. import org.apache.commons.lang3.math.NumberUtils;
  36. import org.apache.log4j.Logger;
  37. import com.mirth.connect.donkey.model.channel.DeployedState;
  38. import com.mirth.connect.donkey.model.event.ConnectionStatusEventType;
  39. import com.mirth.connect.donkey.model.event.ErrorEventType;
  40. import com.mirth.connect.donkey.model.message.RawMessage;
  41. import com.mirth.connect.donkey.server.DeployException;
  42. import com.mirth.connect.donkey.server.HaltException;
  43. import com.mirth.connect.donkey.server.StartException;
  44. import com.mirth.connect.donkey.server.StopException;
  45. import com.mirth.connect.donkey.server.UndeployException;
  46. import com.mirth.connect.donkey.server.channel.ChannelException;
  47. import com.mirth.connect.donkey.server.channel.DispatchResult;
  48. import com.mirth.connect.donkey.server.channel.SourceConnector;
  49. import com.mirth.connect.donkey.server.event.ConnectionStatusEvent;
  50. import com.mirth.connect.donkey.server.event.ConnectorCountEvent;
  51. import com.mirth.connect.donkey.server.event.ErrorEvent;
  52. import com.mirth.connect.donkey.util.ThreadUtils;
  53. import com.mirth.connect.model.transmission.StreamHandler;
  54. import com.mirth.connect.model.transmission.StreamHandlerException;
  55. import com.mirth.connect.model.transmission.batch.BatchStreamReader;
  56. import com.mirth.connect.model.transmission.batch.DefaultBatchStreamReader;
  57. import com.mirth.connect.model.transmission.batch.ER7BatchStreamReader;
  58. import com.mirth.connect.model.transmission.framemode.FrameModeProperties;
  59. import com.mirth.connect.plugins.BasicModeProvider;
  60. import com.mirth.connect.plugins.TransmissionModeProvider;
  61. import com.mirth.connect.server.controllers.ControllerFactory;
  62. import com.mirth.connect.server.controllers.EventController;
  63. import com.mirth.connect.server.util.TemplateValueReplacer;
  64. import com.mirth.connect.util.CharsetUtils;
  65. import com.mirth.connect.util.ErrorMessageBuilder;
  66. import com.mirth.connect.util.TcpUtil;
  67. public class TcpReceiver extends SourceConnector {
  68. // This determines how many client requests can queue up while waiting for the server socket to accept
  69. private static final int DEFAULT_BACKLOG = 256;
  70. private Logger logger = Logger.getLogger(this.getClass());
  71. private EventController eventController = ControllerFactory.getFactory().createEventController();
  72. protected TcpReceiverProperties connectorProperties;
  73. private TemplateValueReplacer replacer = new TemplateValueReplacer();
  74. private StateAwareServerSocket serverSocket;
  75. private StateAwareSocket clientSocket;
  76. private StateAwareSocket recoveryResponseSocket;
  77. private Thread thread;
  78. private ExecutorService executor;
  79. private Set<Future<Throwable>> results = new HashSet<Future<Throwable>>();
  80. private Set<TcpReader> clientReaders = new HashSet<TcpReader>();
  81. private AtomicBoolean disposing;
  82. private int maxConnections;
  83. private int timeout;
  84. private int bufferSize;
  85. private int reconnectInterval;
  86. TransmissionModeProvider transmissionModeProvider;
  87. @Override
  88. public void onDeploy() throws DeployException {
  89. connectorProperties = (TcpReceiverProperties) getConnectorProperties();
  90. maxConnections = NumberUtils.toInt(connectorProperties.getMaxConnections());
  91. timeout = NumberUtils.toInt(connectorProperties.getReceiveTimeout());
  92. bufferSize = NumberUtils.toInt(connectorProperties.getBufferSize());
  93. reconnectInterval = NumberUtils.toInt(connectorProperties.getReconnectInterval());
  94. String pluginPointName = (String) connectorProperties.getTransmissionModeProperties().getPluginPointName();
  95. if (pluginPointName.equals("Basic")) {
  96. transmissionModeProvider = new BasicModeProvider();
  97. } else {
  98. transmissionModeProvider = (TransmissionModeProvider) ControllerFactory.getFactory().createExtensionController().getServicePlugins().get(pluginPointName);
  99. }
  100. if (transmissionModeProvider == null) {
  101. throw new DeployException("Unable to find transmission mode plugin: " + pluginPointName);
  102. }
  103. disposing = new AtomicBoolean(false);
  104. eventController.dispatchEvent(new ConnectorCountEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.IDLE, null, maxConnections));
  105. }
  106. @Override
  107. public void onUndeploy() throws UndeployException {}
  108. @Override
  109. public void onStart() throws StartException {
  110. disposing.set(false);
  111. results.clear();
  112. clientReaders.clear();
  113. if (connectorProperties.isServerMode()) {
  114. // If we're in server mode, use the max connections property to initialize the thread pool
  115. executor = new ThreadPoolExecutor(0, maxConnections, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
  116. } else {
  117. // If we're in client mode, only a single thread is needed
  118. executor = Executors.newSingleThreadExecutor();
  119. }
  120. if (connectorProperties.isServerMode()) {
  121. try {
  122. createServerSocket();
  123. } catch (IOException e) {
  124. throw new StartException("Failed to create server socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  125. }
  126. }
  127. // Create the acceptor thread
  128. thread = new Thread() {
  129. @Override
  130. public void run() {
  131. while (getCurrentState() == DeployedState.STARTED) {
  132. StateAwareSocket socket = null;
  133. if (connectorProperties.isServerMode()) {
  134. // Server mode; wait to accept a client socket on the ServerSocket
  135. try {
  136. logger.debug("Waiting for new client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  137. socket = serverSocket.accept();
  138. logger.trace("Accepted new socket: " + socket.getRemoteSocketAddress().toString() + " -> " + socket.getLocalSocketAddress());
  139. } catch (java.io.InterruptedIOException e) {
  140. logger.debug("Interruption during server socket accept operation (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  141. } catch (Exception e) {
  142. logger.debug("Error accepting new socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  143. }
  144. } else {
  145. // Client mode, manually initiate a client socket
  146. try {
  147. logger.debug("Initiating for new client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  148. if (connectorProperties.isOverrideLocalBinding()) {
  149. socket = SocketUtil.createSocket(getLocalAddress(), getLocalPort());
  150. } else {
  151. socket = SocketUtil.createSocket();
  152. }
  153. clientSocket = socket;
  154. SocketUtil.connectSocket(socket, getRemoteAddress(), getRemotePort(), timeout);
  155. } catch (Exception e) {
  156. logger.error("Error initiating new socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  157. }
  158. }
  159. try {
  160. ThreadUtils.checkInterruptedStatus();
  161. if (socket != null) {
  162. try {
  163. synchronized (clientReaders) {
  164. // Only allow worker threads to be submitted if we're not currently trying to stop the connector
  165. if (disposing.get()) {
  166. return;
  167. }
  168. TcpReader reader = new TcpReader(socket);
  169. clientReaders.add(reader);
  170. results.add(executor.submit(reader));
  171. }
  172. } catch (RejectedExecutionException e) {
  173. logger.debug("Executor rejected new task (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  174. closeSocketQuietly(socket);
  175. } catch (SocketException e) {
  176. logger.debug("Error initializing socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  177. closeSocketQuietly(socket);
  178. }
  179. }
  180. if (connectorProperties.isServerMode()) {
  181. // Remove any completed tasks from the list, but don't try to retrieve currently running tasks
  182. cleanup(false, false, true);
  183. } else {
  184. // Wait until the TcpReader is done
  185. cleanup(true, false, true);
  186. String info = "Client socket finished, waiting " + connectorProperties.getReconnectInterval() + " ms...";
  187. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.INFO, info));
  188. // Use the reconnect interval to determine how long to wait until creating another socket
  189. sleep(reconnectInterval);
  190. }
  191. } catch (InterruptedException e) {
  192. return;
  193. }
  194. }
  195. }
  196. };
  197. thread.start();
  198. }
  199. @Override
  200. public void onStop() throws StopException {
  201. StopException firstCause = null;
  202. synchronized (clientReaders) {
  203. disposing.set(true);
  204. if (executor != null) {
  205. // Prevent any new client threads from being submitted to the executor
  206. executor.shutdown();
  207. }
  208. }
  209. if (connectorProperties.isServerMode()) {
  210. if (serverSocket != null) {
  211. // Close the server socket
  212. try {
  213. logger.debug("Closing server socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  214. serverSocket.close();
  215. } catch (IOException e) {
  216. firstCause = new StopException("Error closing server socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  217. }
  218. }
  219. } else {
  220. // Close the client socket
  221. try {
  222. SocketUtil.closeSocket(clientSocket);
  223. } catch (IOException e) {
  224. firstCause = new StopException("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  225. } finally {
  226. clientSocket = null;
  227. }
  228. }
  229. // Join the connector thread
  230. try {
  231. disposeThread(false);
  232. } catch (InterruptedException e) {
  233. Thread.currentThread().interrupt();
  234. throw new StopException("Thread join operation interrupted (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  235. }
  236. synchronized (clientReaders) {
  237. for (TcpReader reader : clientReaders) {
  238. try {
  239. synchronized (reader) {
  240. reader.setCanRead(false);
  241. /*
  242. * We only want to close the worker's socket if it's currently in the read()
  243. * method. If keep connection open is true and the receive timeout is zero,
  244. * that read() would have blocked forever, so we need to close the socket
  245. * here so it will throw an exception. However even if the worker was in the
  246. * middle of reading bytes from the input stream, we still want to close the
  247. * socket. That message would never have been dispatched to the channel
  248. * anyway because the connectors current state would not be equal to
  249. * STARTED.
  250. */
  251. if (reader.isReading()) {
  252. reader.getSocket().close();
  253. }
  254. }
  255. } catch (IOException e) {
  256. if (firstCause == null) {
  257. firstCause = new StopException("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  258. }
  259. }
  260. }
  261. clientReaders.clear();
  262. }
  263. // Wait for any remaining tasks to complete
  264. try {
  265. cleanup(true, false, false);
  266. } catch (InterruptedException e) {
  267. Thread.currentThread().interrupt();
  268. throw new StopException("Client thread disposal interrupted (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  269. }
  270. // Close all client sockets after canceling tasks in case a task failed to complete
  271. synchronized (clientReaders) {
  272. for (TcpReader reader : clientReaders) {
  273. try {
  274. reader.getSocket().close();
  275. } catch (IOException e) {
  276. if (firstCause == null) {
  277. firstCause = new StopException("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  278. }
  279. }
  280. try {
  281. SocketUtil.closeSocket(reader.getResponseSocket());
  282. } catch (IOException e) {
  283. if (firstCause == null) {
  284. firstCause = new StopException("Error closing response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  285. }
  286. }
  287. }
  288. clientReaders.clear();
  289. }
  290. // Close the recovery response socket, if applicable
  291. try {
  292. SocketUtil.closeSocket(recoveryResponseSocket);
  293. } catch (IOException e) {
  294. if (firstCause == null) {
  295. firstCause = new StopException("Error closing response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  296. }
  297. }
  298. if (firstCause != null) {
  299. throw firstCause;
  300. }
  301. }
  302. @Override
  303. public void onHalt() throws HaltException {
  304. HaltException firstCause = null;
  305. synchronized (clientReaders) {
  306. disposing.set(true);
  307. // Prevent any new client threads from being submitted to the executor
  308. executor.shutdownNow();
  309. }
  310. if (connectorProperties.isServerMode()) {
  311. if (serverSocket != null) {
  312. // Close the server socket
  313. try {
  314. logger.debug("Closing server socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  315. serverSocket.close();
  316. } catch (IOException e) {
  317. firstCause = new HaltException("Error closing server socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  318. }
  319. }
  320. } else {
  321. // Close the client socket
  322. try {
  323. SocketUtil.closeSocket(clientSocket);
  324. } catch (IOException e) {
  325. firstCause = new HaltException("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  326. } finally {
  327. clientSocket = null;
  328. }
  329. }
  330. // Join the connector thread
  331. try {
  332. disposeThread(true);
  333. } catch (InterruptedException e) {
  334. Thread.currentThread().interrupt();
  335. if (firstCause == null) {
  336. firstCause = new HaltException("Thread join operation interrupted (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  337. }
  338. }
  339. // Close all client sockets before interrupting tasks
  340. synchronized (clientReaders) {
  341. for (TcpReader reader : clientReaders) {
  342. try {
  343. reader.getSocket().close();
  344. } catch (IOException e) {
  345. if (firstCause == null) {
  346. logger.debug("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  347. firstCause = new HaltException("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  348. }
  349. }
  350. try {
  351. SocketUtil.closeSocket(reader.getResponseSocket());
  352. } catch (IOException e) {
  353. if (firstCause == null) {
  354. logger.debug("Error closing response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  355. firstCause = new HaltException("Error closing response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  356. }
  357. }
  358. }
  359. }
  360. // Close the recovery response socket, if applicable
  361. try {
  362. SocketUtil.closeSocket(recoveryResponseSocket);
  363. } catch (IOException e) {
  364. if (firstCause == null) {
  365. firstCause = new HaltException("Error closing response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  366. }
  367. }
  368. // Attempt to cancel any remaining tasks
  369. try {
  370. cleanup(false, true, false);
  371. } catch (InterruptedException e) {
  372. Thread.currentThread().interrupt();
  373. if (firstCause == null) {
  374. firstCause = new HaltException("Client thread disposal interrupted (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  375. }
  376. }
  377. synchronized (clientReaders) {
  378. clientReaders.clear();
  379. }
  380. if (firstCause != null) {
  381. throw firstCause;
  382. }
  383. }
  384. @Override
  385. public void handleRecoveredResponse(DispatchResult dispatchResult) {
  386. boolean attemptedResponse = false;
  387. String errorMessage = null;
  388. try {
  389. if (dispatchResult.getSelectedResponse() != null) {
  390. // Only if we're responding on a new connection can we handle recovered responses
  391. if (connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION || connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION_ON_RECOVERY) {
  392. BatchStreamReader batchStreamReader = new DefaultBatchStreamReader(null);
  393. StreamHandler streamHandler = transmissionModeProvider.getStreamHandler(null, null, batchStreamReader, connectorProperties.getTransmissionModeProperties());
  394. try {
  395. attemptedResponse = true;
  396. recoveryResponseSocket = createResponseSocket();
  397. connectResponseSocket(recoveryResponseSocket, streamHandler);
  398. sendResponse(dispatchResult.getSelectedResponse().getMessage(), recoveryResponseSocket, streamHandler, true);
  399. } catch (IOException e) {
  400. errorMessage = ErrorMessageBuilder.buildErrorMessage(connectorProperties.getName(), "Error sending response.", e);
  401. } finally {
  402. closeSocketQuietly(recoveryResponseSocket);
  403. recoveryResponseSocket = null;
  404. }
  405. } else {
  406. errorMessage = "Cannot respond on original connection during message recovery. In order to send a response, enable \"Respond on New Connection\" in Tcp Listener settings.";
  407. }
  408. }
  409. } finally {
  410. finishDispatch(dispatchResult, attemptedResponse, errorMessage);
  411. }
  412. }
  413. protected class TcpReader implements Callable<Throwable> {
  414. private StateAwareSocket socket = null;
  415. private StateAwareSocket responseSocket = null;
  416. private AtomicBoolean reading = null;
  417. private AtomicBoolean canRead = null;
  418. public TcpReader(StateAwareSocket socket) throws SocketException {
  419. this.socket = socket;
  420. initSocket(socket);
  421. reading = new AtomicBoolean(false);
  422. canRead = new AtomicBoolean(true);
  423. }
  424. public StateAwareSocket getSocket() {
  425. return socket;
  426. }
  427. public StateAwareSocket getResponseSocket() {
  428. return responseSocket;
  429. }
  430. public boolean isReading() {
  431. return reading.get();
  432. }
  433. public void setCanRead(boolean canRead) {
  434. this.canRead.set(canRead);
  435. }
  436. @Override
  437. public Throwable call() {
  438. Throwable t = null;
  439. boolean done = false;
  440. eventController.dispatchEvent(new ConnectorCountEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.CONNECTED, SocketUtil.getLocalAddress(socket) + " -> " + SocketUtil.getInetAddress(socket), true));
  441. try {
  442. while (!done && getCurrentState() == DeployedState.STARTED) {
  443. ThreadUtils.checkInterruptedStatus();
  444. StreamHandler streamHandler = null;
  445. try {
  446. boolean streamDone = false;
  447. boolean firstMessage = true;
  448. // TODO: Put this on the DataType object; let it decide based on the properties which stream handler to use
  449. BatchStreamReader batchStreamReader = null;
  450. if (connectorProperties.isProcessBatch() && getInboundDataType().getType().equals("HL7V2")) {
  451. if (connectorProperties.getTransmissionModeProperties() instanceof FrameModeProperties) {
  452. batchStreamReader = new ER7BatchStreamReader(socket.getInputStream(), TcpUtil.stringToByteArray(((FrameModeProperties) connectorProperties.getTransmissionModeProperties()).getEndOfMessageBytes()));
  453. } else {
  454. batchStreamReader = new ER7BatchStreamReader(socket.getInputStream());
  455. }
  456. } else {
  457. batchStreamReader = new DefaultBatchStreamReader(socket.getInputStream());
  458. }
  459. streamHandler = transmissionModeProvider.getStreamHandler(socket.getInputStream(), socket.getOutputStream(), batchStreamReader, connectorProperties.getTransmissionModeProperties());
  460. if (connectorProperties.getRespondOnNewConnection() != TcpReceiverProperties.NEW_CONNECTION) {
  461. // If we're not responding on a new connection, then write to the output stream of the same socket
  462. responseSocket = socket;
  463. BufferedOutputStream bos = new BufferedOutputStream(responseSocket.getOutputStream(), bufferSize);
  464. streamHandler.setOutputStream(bos);
  465. }
  466. while (!streamDone && !done) {
  467. ThreadUtils.checkInterruptedStatus();
  468. /*
  469. * We need to keep track of whether the worker thread is currently
  470. * trying to read from the input stream because the read() method is not
  471. * interruptible. To do this we store two booleans, canRead and reading.
  472. * The canRead boolean is checked internally here and set externally
  473. * (e.g. by the onStop() or onHalt() methods). The reading boolean is
  474. * set in here when the thread is about to attempt to read from the
  475. * stream. After the read() method returns (or throws an exception),
  476. * reading is set to false.
  477. */
  478. synchronized (this) {
  479. if (canRead.get()) {
  480. reading.set(true);
  481. }
  482. }
  483. byte[] bytes = null;
  484. if (reading.get()) {
  485. logger.debug("Reading from socket input stream (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ")...");
  486. try {
  487. /*
  488. * Read from the socket's input stream. If we're keeping the
  489. * connection open, then bytes will be read until the socket
  490. * timeout is reached, or until an EOF marker or the ending
  491. * bytes are encountered. If we're not keeping the connection
  492. * open, then a socket timeout will not be silently caught, and
  493. * instead will be thrown from here and cause the worker thread
  494. * to abort.
  495. */
  496. bytes = streamHandler.read();
  497. } finally {
  498. reading.set(false);
  499. }
  500. }
  501. if (bytes != null) {
  502. logger.debug("Bytes returned from socket, length: " + bytes.length + " (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ")");
  503. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.RECEIVING, "Message received from " + SocketUtil.getLocalAddress(socket) + ", processing... "));
  504. RawMessage rawMessage = null;
  505. if (connectorProperties.isDataTypeBinary()) {
  506. // Store the raw bytes in the RawMessage object
  507. rawMessage = new RawMessage(bytes);
  508. } else {
  509. // Encode the bytes using the charset encoding property and store the string in the RawMessage object
  510. rawMessage = new RawMessage(new String(bytes, CharsetUtils.getEncoding(connectorProperties.getCharsetEncoding())));
  511. }
  512. // Add the socket information to the channelMap
  513. Map<String, Object> sourceMap = new HashMap<String, Object>();
  514. sourceMap.put("localAddress", socket.getLocalAddress().getHostAddress());
  515. sourceMap.put("localPort", socket.getLocalPort());
  516. if (socket.getRemoteSocketAddress() instanceof InetSocketAddress) {
  517. sourceMap.put("remoteAddress", ((InetSocketAddress) socket.getRemoteSocketAddress()).getAddress().getHostAddress());
  518. sourceMap.put("remotePort", ((InetSocketAddress) socket.getRemoteSocketAddress()).getPort());
  519. }
  520. rawMessage.setSourceMap(sourceMap);
  521. DispatchResult dispatchResult = null;
  522. // Keep attempting while the channel is still started
  523. while (dispatchResult == null && getCurrentState() == DeployedState.STARTED) {
  524. ThreadUtils.checkInterruptedStatus();
  525. boolean attemptedResponse = false;
  526. String errorMessage = null;
  527. // Send the message to the source connector
  528. try {
  529. dispatchResult = dispatchRawMessage(rawMessage);
  530. // Only send a response for the first message
  531. if (firstMessage) {
  532. streamHandler.commit(true);
  533. // Check to see if we have a response to send
  534. if (dispatchResult.getSelectedResponse() != null) {
  535. // Send the response
  536. attemptedResponse = true;
  537. try {
  538. // If the response socket hasn't been initialized, do that now
  539. if (connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION) {
  540. responseSocket = createResponseSocket();
  541. connectResponseSocket(responseSocket, streamHandler);
  542. }
  543. sendResponse(dispatchResult.getSelectedResponse().getMessage(), responseSocket, streamHandler, connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION);
  544. } catch (IOException e) {
  545. errorMessage = ErrorMessageBuilder.buildErrorMessage(connectorProperties.getName(), "Error sending response.", e);
  546. } finally {
  547. if (connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION || !connectorProperties.isKeepConnectionOpen()) {
  548. closeSocketQuietly(responseSocket);
  549. }
  550. }
  551. }
  552. }
  553. } catch (ChannelException e) {
  554. if (firstMessage) {
  555. streamHandler.commit(false);
  556. }
  557. } finally {
  558. firstMessage = false;
  559. finishDispatch(dispatchResult, attemptedResponse, errorMessage);
  560. }
  561. }
  562. eventController.dispatchEvent(new ConnectorCountEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.IDLE, SocketUtil.getLocalAddress(socket) + " -> " + SocketUtil.getInetAddress(socket), (Boolean) null));
  563. } else {
  564. // If no bytes were returned, then assume we have finished processing all possible messages from the input stream.
  565. logger.debug("Stream reader returned null (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  566. streamDone = true;
  567. }
  568. }
  569. logger.debug("Done with socket input stream (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  570. // If we're not keeping the connection open or if the remote side has already closed the connection, then we're done with the socket
  571. if (checkSocket(socket)) {
  572. done = true;
  573. }
  574. } catch (IOException e) {
  575. boolean timeout = e instanceof SocketTimeoutException || !(e instanceof StreamHandlerException) && e.getCause() != null && e.getCause() instanceof SocketTimeoutException;
  576. // If we're keeping the connection open and a timeout occurred, then continue processing. Otherwise, abort.
  577. if (!connectorProperties.isKeepConnectionOpen() || !timeout) {
  578. // If an exception occurred then abort, even if keep connection open is true
  579. done = true;
  580. if (timeout) {
  581. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.FAILURE, "Timeout waiting for message from " + SocketUtil.getLocalAddress(socket) + ". "));
  582. } else {
  583. // Set the return value and send an alert
  584. String errorMessage = "Error receiving message (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").";
  585. SocketException cause = null;
  586. if (e instanceof SocketException) {
  587. cause = (SocketException) e;
  588. } else if (e.getCause() != null && e.getCause() instanceof SocketException) {
  589. cause = (SocketException) e.getCause();
  590. }
  591. if (cause != null && cause.getMessage() != null && cause.getMessage().contains("Connection reset")) {
  592. logger.warn(errorMessage, e);
  593. } else {
  594. logger.error(errorMessage, e);
  595. }
  596. t = new Exception(errorMessage, e);
  597. eventController.dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), ErrorEventType.SOURCE_CONNECTOR, getSourceName(), connectorProperties.getName(), "Error receiving message", e));
  598. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.FAILURE, "Error receiving message from " + SocketUtil.getLocalAddress(socket) + ": " + e.getMessage()));
  599. }
  600. } else {
  601. logger.debug("Timeout reading from socket input stream (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  602. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.INFO, "Timeout waiting for message from " + SocketUtil.getLocalAddress(socket) + ". "));
  603. }
  604. }
  605. }
  606. } catch (InterruptedException e) {
  607. Thread.currentThread().interrupt();
  608. logger.error("Error receiving message (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  609. eventController.dispatchEvent(new ErrorEvent(getChannelId(), getMetaDataId(), ErrorEventType.SOURCE_CONNECTOR, getSourceName(), connectorProperties.getName(), "Error receiving message", e));
  610. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.FAILURE, "Error receiving message from " + SocketUtil.getLocalAddress(socket) + ": " + e.getMessage()));
  611. } finally {
  612. logger.debug("Done with socket, closing (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ")...");
  613. // We're done reading, so close everything up
  614. closeSocketQuietly(socket);
  615. if (connectorProperties.getRespondOnNewConnection() == TcpReceiverProperties.NEW_CONNECTION) {
  616. closeSocketQuietly(responseSocket);
  617. }
  618. eventController.dispatchEvent(new ConnectorCountEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.DISCONNECTED, SocketUtil.getLocalAddress(socket) + " -> " + SocketUtil.getInetAddress(socket), false));
  619. synchronized (clientReaders) {
  620. clientReaders.remove(this);
  621. }
  622. }
  623. return t;
  624. }
  625. }
  626. private void createServerSocket() throws IOException {
  627. // Create the server socket
  628. int backlog = DEFAULT_BACKLOG;
  629. String host = getLocalAddress();
  630. int port = getLocalPort();
  631. InetAddress hostAddress = InetAddress.getByName(host);
  632. int bindAttempts = 0;
  633. boolean success = false;
  634. // If an error occurred during binding, try again. If the JVM fails to bind ten times, throw the exception.
  635. while (!success) {
  636. try {
  637. bindAttempts++;
  638. if (hostAddress.equals(InetAddress.getLocalHost()) || hostAddress.isLoopbackAddress() || host.trim().equals("localhost")) {
  639. serverSocket = new StateAwareServerSocket(port, backlog);
  640. } else {
  641. serverSocket = new StateAwareServerSocket(port, backlog, hostAddress);
  642. }
  643. success = true;
  644. } catch (BindException e) {
  645. if (bindAttempts >= 10) {
  646. throw e;
  647. } else {
  648. try {
  649. Thread.sleep(1000);
  650. } catch (InterruptedException e2) {
  651. Thread.currentThread().interrupt();
  652. }
  653. }
  654. }
  655. }
  656. }
  657. private StateAwareSocket createResponseSocket() throws IOException {
  658. logger.debug("Creating response socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  659. return SocketUtil.createSocket();
  660. }
  661. private void connectResponseSocket(StateAwareSocket responseSocket, StreamHandler streamHandler) throws IOException {
  662. int responsePort = NumberUtils.toInt(replacer.replaceValues(connectorProperties.getResponsePort(), getChannelId()));
  663. SocketUtil.connectSocket(responseSocket, replacer.replaceValues(connectorProperties.getResponseAddress(), getChannelId()), responsePort, timeout);
  664. initSocket(responseSocket);
  665. BufferedOutputStream bos = new BufferedOutputStream(responseSocket.getOutputStream(), bufferSize);
  666. streamHandler.setOutputStream(bos);
  667. }
  668. private void sendResponse(String response, StateAwareSocket responseSocket, StreamHandler streamHandler, boolean newConnection) throws IOException {
  669. try {
  670. if (responseSocket != null && streamHandler != null) {
  671. // Send the response
  672. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.INFO, "Sending response to " + (newConnection ? SocketUtil.getInetAddress(responseSocket) : SocketUtil.getLocalAddress(responseSocket)) + "... "));
  673. streamHandler.write(getBytes(response));
  674. } else {
  675. throw new IOException((responseSocket == null ? "Response socket" : "Stream handler") + " is null.");
  676. }
  677. } catch (IOException e) {
  678. if (responseSocket != null && responseSocket.remoteSideHasClosed()) {
  679. e = new IOException("Remote socket has closed.");
  680. }
  681. logger.error("Error sending response (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  682. eventController.dispatchEvent(new ConnectionStatusEvent(getChannelId(), getMetaDataId(), getSourceName(), ConnectionStatusEventType.FAILURE, "Error sending response to " + (newConnection ? SocketUtil.getInetAddress(responseSocket) : SocketUtil.getLocalAddress(responseSocket)) + ": " + e.getMessage() + " "));
  683. throw e;
  684. }
  685. }
  686. private boolean checkSocket(StateAwareSocket socket) throws IOException {
  687. return !connectorProperties.isKeepConnectionOpen() || socket.isClosed() || socket.remoteSideHasClosed();
  688. }
  689. private void closeSocketQuietly(StateAwareSocket socket) {
  690. try {
  691. if (socket != null) {
  692. logger.trace("Closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  693. SocketUtil.closeSocket(socket);
  694. }
  695. } catch (IOException e) {
  696. logger.debug("Error closing client socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", e);
  697. }
  698. }
  699. private void disposeThread(boolean interrupt) throws InterruptedException {
  700. if (thread != null && thread.isAlive()) {
  701. if (interrupt) {
  702. logger.trace("Interrupting thread (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  703. thread.interrupt();
  704. }
  705. logger.trace("Joining thread (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  706. try {
  707. thread.join();
  708. } catch (InterruptedException e) {
  709. Thread.currentThread().interrupt();
  710. throw e;
  711. }
  712. }
  713. }
  714. /**
  715. * Attempts to get the result of any Future tasks which may still be running. Any completed
  716. * tasks are removed from the Future list.
  717. *
  718. * This can ensure that all client socket threads are disposed, so that a remote client wouldn't
  719. * be able to still send a message after a channel has been stopped or undeployed (even though
  720. * it wouldn't be processed through the channel anyway).
  721. *
  722. * @param block
  723. * - If true, then each Future task will be joined to this one, blocking until the
  724. * task thread dies.
  725. * @param interrupt
  726. * - If true, each currently running task thread will be interrupted in an attempt to
  727. * stop the task. Any interrupted exceptions will be caught and not thrown, in a best
  728. * effort to ensure that all results are taken care of.
  729. * @param remove
  730. * - If true, each completed result will be removed from the Future set during
  731. * iteration.
  732. */
  733. private void cleanup(boolean block, boolean interrupt, boolean remove) throws InterruptedException {
  734. for (Iterator<Future<Throwable>> it = results.iterator(); it.hasNext();) {
  735. Future<Throwable> result = it.next();
  736. if (interrupt) {
  737. // Cancel the task, with the option of whether or not to forcefully interrupt it
  738. result.cancel(true);
  739. }
  740. if (block) {
  741. // Attempt to get the result (which blocks until it returns)
  742. Throwable t = null;
  743. try {
  744. // If the return value is not null, then an exception was raised somewhere in the client socket thread
  745. if ((t = result.get()) != null) {
  746. logger.debug("Client socket thread returned unsuccessfully (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").", t);
  747. }
  748. } catch (Exception e) {
  749. logger.debug("Error retrieving client socket thread result for " + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ".", e);
  750. Throwable cause;
  751. if (t instanceof ExecutionException) {
  752. cause = t.getCause();
  753. } else {
  754. cause = t;
  755. }
  756. if (cause instanceof InterruptedException) {
  757. Thread.currentThread().interrupt();
  758. if (!interrupt) {
  759. throw (InterruptedException) cause;
  760. }
  761. }
  762. }
  763. }
  764. if (remove) {
  765. // Remove the task from the list if it's done, or if it's been cancelled
  766. if (result.isDone()) {
  767. it.remove();
  768. }
  769. }
  770. }
  771. }
  772. private String getLocalAddress() {
  773. return TcpUtil.getFixedHost(replacer.replaceValues(connectorProperties.getListenerConnectorProperties().getHost(), getChannelId()));
  774. }
  775. private int getLocalPort() {
  776. return NumberUtils.toInt(replacer.replaceValues(connectorProperties.getListenerConnectorProperties().getPort(), getChannelId()));
  777. }
  778. private String getRemoteAddress() {
  779. return TcpUtil.getFixedHost(replacer.replaceValues(connectorProperties.getRemoteAddress(), getChannelId()));
  780. }
  781. private int getRemotePort() {
  782. return NumberUtils.toInt(replacer.replaceValues(connectorProperties.getRemotePort(), getChannelId()));
  783. }
  784. /*
  785. * Sets the socket settings using the connector properties.
  786. */
  787. private void initSocket(Socket socket) throws SocketException {
  788. logger.debug("Initializing socket (" + connectorProperties.getName() + " \"Source\" on channel " + getChannelId() + ").");
  789. socket.setReceiveBufferSize(bufferSize);
  790. socket.setSendBufferSize(bufferSize);
  791. socket.setSoTimeout(timeout);
  792. socket.setKeepAlive(connectorProperties.isKeepConnectionOpen());
  793. socket.setReuseAddress(true);
  794. socket.setTcpNoDelay(true);
  795. }
  796. /*
  797. * Converts a string to a byte array using the connector properties to determine whether or not
  798. * to encode in Base64, and what charset to use.
  799. */
  800. private byte[] getBytes(String str) throws UnsupportedEncodingException {
  801. byte[] bytes = new byte[0];
  802. if (str != null) {
  803. if (connectorProperties.isDataTypeBinary()) {
  804. bytes = Base64.decodeBase64(str);
  805. } else {
  806. bytes = str.getBytes(CharsetUtils.getEncoding(connectorProperties.getCharsetEncoding()));
  807. }
  808. }
  809. return bytes;
  810. }
  811. }