/* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.processors.gettcp; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * Implementation of receiving network client. */ class ReceivingClient extends AbstractSocketHandler { private final ScheduledExecutorService connectionScheduler; private volatile int reconnectAttempts; private volatile long delayMillisBeforeReconnect; private volatile MessageHandler messageHandler; private volatile InetSocketAddress connectedAddress; public ReceivingClient(InetSocketAddress address, ScheduledExecutorService connectionScheduler, int readingBufferSize, byte endOfMessageByte) { super(address, readingBufferSize, endOfMessageByte); this.connectionScheduler = connectionScheduler; } public void setReconnectAttempts(int reconnectAttempts) { this.reconnectAttempts = reconnectAttempts; } public void setDelayMillisBeforeReconnect(long delayMillisBeforeReconnect) { this.delayMillisBeforeReconnect = delayMillisBeforeReconnect; } public void setMessageHandler(MessageHandler messageHandler) { this.messageHandler = messageHandler; } /** * Connects to the endpoint specified and if that fails, will attempt to connect to the * secondary endpoint up to the number of reconnection attempts (inclusive) using the * configured delay between attempts. */ @Override InetSocketAddress connect() throws Exception { CountDownLatch latch = new CountDownLatch(1); AtomicInteger attempt = new AtomicInteger(); AtomicReference<Exception> connectionError = new AtomicReference<Exception>(); this.connectionScheduler.execute(new Runnable() { @Override public void run() { try { rootChannel = doConnect(address); latch.countDown(); connectedAddress = address; } catch (Exception e) { if (logger.isInfoEnabled()) { logger.info("Failed to connect to primary endpoint '" + address + "'."); } if (attempt.incrementAndGet() <= reconnectAttempts) { if (logger.isInfoEnabled()) { logger.info("Will attempt to reconnect to '" + address + "'."); } connectionScheduler.schedule(this, delayMillisBeforeReconnect, TimeUnit.MILLISECONDS); } else { connectionError.set(e); logger.error("Failed to connect to secondary endpoint."); latch.countDown(); } } } }); try { boolean finishedTask = latch.await(this.reconnectAttempts * delayMillisBeforeReconnect + 2000, TimeUnit.MILLISECONDS); if (finishedTask){ if (connectionError.get() != null) { throw connectionError.get(); } } else { logger.error("Exceeded wait time to connect. Possible deadlock, please report!. Interrupting."); // should never happen! } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException("Current thread is interrupted"); } return this.connectedAddress; } private SocketChannel doConnect(InetSocketAddress addressToConnect) throws IOException { SocketChannel channel = SocketChannel.open(); if (channel.connect(addressToConnect)) { channel.configureBlocking(false); channel.register(this.selector, SelectionKey.OP_READ); } else { throw new IllegalStateException("Failed to connect to Server at: " + addressToConnect); } return channel; } /** * Process the message that has arrived off the wire. */ @Override void processData(SelectionKey selectionKey, ByteBuffer messageBuffer) throws IOException { byte[] message = new byte[messageBuffer.limit()]; logger.debug("Received message(size=" + message.length + ")"); messageBuffer.get(message); byte lastByteValue = message[message.length - 1]; boolean partialMessage = false; if (lastByteValue != this.endOfMessageByte) { partialMessage = true; selectionKey.attach(1); } else { Integer wasLastPartial = (Integer) selectionKey.attachment(); if (wasLastPartial != null) { if (wasLastPartial.intValue() == 1) { partialMessage = true; selectionKey.attach(0); } } } if (this.messageHandler != null) { this.messageHandler.handle(this.connectedAddress, message, partialMessage); } } }