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