/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.tcp; import static org.quartz.CronScheduleBuilder.cronSchedule; import static org.quartz.DateBuilder.futureDate; import static org.quartz.JobBuilder.newJob; import static org.quartz.TriggerBuilder.newTrigger; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.NoConnectionPendingException; import java.nio.channels.NotYetConnectedException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.TypeParser; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.quartz.DateBuilder.IntervalUnit; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.impl.StdSchedulerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * This is the base for all "Socket" connection-oriented network based connectivity and communication.It * requires a ChannelBindingProvider based binding provider. Data is pushed around using ByteBuffers with an indicator * for blocking/non-blocking (synchronous/asynchronous) communication * * @author Karel Goderis * @since 1.1.0 * */ public abstract class AbstractSocketChannelBinding<P extends ChannelBindingProvider> extends AbstractActiveBinding<P> implements ManagedService { private static final Logger logger = LoggerFactory.getLogger(AbstractSocketChannelBinding.class); // Configurable parameters protected Selector selector; // maximum size of buffer whilst reading from a channel protected int maximumBufferSize = 1024; // cron-style string to define time between reconnects protected String reconnectCron = "0 0 0 * * ?"; // time to wait to attempt a reconnection of an interval, in case of channel failure protected int reconnectInterval = 5; // queue data received for a given channel until the connection is restored from a previous error protected boolean queueUntilConnected = true; // default port to listen on for incoming connections protected int listenerPort = 0; // share channels between within an item definition protected boolean itemShareChannels = true; // share channels between items definitions protected boolean bindingShareChannels = true; // share channels between "outbound" and "inbound" item definitions protected boolean directionsShareChannels = false; // allow *:* host:port definitions protected boolean useAddressMask = true; // refresh interval for the worker thread protected long refreshInterval = 250; protected ServerSocketChannel listenerChannel = null; protected SelectionKey listenerKey = null; // Queue to store BufferElements that need to be written to the network protected List<WriteBufferElement> writeQueue = Collections.synchronizedList(new ArrayList<WriteBufferElement>()); // Simple datastructure to track the state of Channels protected ChannelTracker<Channel> channels = new ChannelTracker<Channel>(); /** * Datastructure to represent that state of a communications channel * * @author Karel Goderis * @since 1.4.0 **/ protected class Channel { // the Item that is bound to this channel public String item; // the Command that is bound to this channel public Command command; // the resolved remote address this channel is connected to public InetSocketAddress remote; // the direction, in our out, of this channel public Direction direction; // flag to indicate if the channel is in a blocking write/read operation public boolean isBlocking; // placeholder to store the received data as the result of a blocking write/read operation public ByteBuffer buffer; // flag to indicate if the channel is reconnecting / recovering from a previous communication error public boolean isReconnecting; // reference to the underlying Java NIO SocketChannel that represents this TCP/IP connection public SocketChannel channel; // remote host name to use. Could be "*" when using masked addresses public String host; // remote port number to use. Could be "*" when using masked addresses public String port; public Channel(String item, Command command, InetSocketAddress remote, Direction direction, boolean isBlocking, ByteBuffer buffer, boolean isReconnecting, SocketChannel channel) { super(); this.item = item; this.command = command; this.remote = remote; this.direction = direction; this.isBlocking = isBlocking; this.buffer = buffer; this.isReconnecting = isReconnecting; this.channel = channel; this.host = remote.getHostString(); this.port = Integer.toString(remote.getPort()); } public Channel(String item, Command command, String host, String port, Direction direction, boolean isBlocking, ByteBuffer buffer, boolean isReconnecting, SocketChannel channel) { super(); this.item = item; this.command = command; this.direction = direction; this.isBlocking = isBlocking; this.buffer = buffer; this.isReconnecting = isReconnecting; this.channel = channel; this.host = host; this.port = port; } @Override public String toString() { try { String response = null; response = "Channel [item=" + item + ", command=" + command + ", direction=" + direction + ", remote=" + remote + ", buffer="; if (buffer != null) { response = response + new String(buffer.array()); } response = response + ", isBlocking=" + isBlocking + ", isReconnecting=" + isReconnecting; if (channel != null) { response = response + ", channel="; try { if (channel.getLocalAddress() != null) { response = response + channel.getLocalAddress(); } } catch (Exception e) { response = response + "N/A"; } try { if (channel.getRemoteAddress() != null) { response = response + "::" + channel.getRemoteAddress(); } } catch (Exception e) { response = response + "::N/A"; } } if (useAddressMask) { response = response + ", host=" + host + ", port=" + port; } response = response + "]"; return response; } catch (Exception e) { logger.error("An exception occurred while converting Channel to String {}", e.getMessage()); } return ""; } } /** * The ChannelTracker acts as a little dB that stores all the information on the state of the * underlying NIO SocketChannels in use. It comes with a bunch of get... methods that allow a caller to * query Channels. * * get() - get the channel that matches the provided criteria for the given {Item,Command} * getFirst() - get the first channel that matches the provided criteria * getFirstServed() - return the first Channel that matches the criteria AND that is currently bound to a Java NIO * channel * getAll() - return a collection of all the Channels that match the given criteria * contains() - return true if a channel that matches the provided criteria exists in the ChannelTracker * replace() - replaces the underlying Java NIO channel on the Channels that match the provided criteria * * @author Karel Goderis * @since 1.4.0 * **/ protected class ChannelTracker<C extends Channel> extends ArrayList<C> { private static final long serialVersionUID = 1543958347565096785L; public boolean contains(String item, Command command, Direction direction, InetSocketAddress remote) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (item.equals(aChannel.item) && command.equals(aChannel.command) && direction.equals(aChannel.direction) && remote.equals(aChannel.remote)) { return true; } } return false; } } public Channel get(String item, Command command, Direction direction, InetSocketAddress remote) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (item.equals(aChannel.item) && command.equals(aChannel.command) && direction.equals(aChannel.direction) && remote.equals(aChannel.remote)) { return aChannel; } } return null; } } public Channel get(String item, Command command, Direction direction, String host, String port) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (item.equals(aChannel.item) && command.equals(aChannel.command) && direction.equals(aChannel.direction)) { if (aChannel.host.equals(host) && aChannel.port.equals(port)) { return aChannel; } } } return null; } } public Channel get(SocketChannel theChannel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theChannel.equals(aChannel.channel)) { return aChannel; } } return null; } } public Channel getFirst(Direction direction, InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (remoteAddress.equals(aChannel.remote) && aChannel.channel == null && direction.equals(aChannel.direction)) { return aChannel; } } Iterator<C> it2 = iterator(); while (it2.hasNext()) { C aChannel = it2.next(); if (remoteAddress.equals(aChannel.remote)) { return aChannel; } } return null; } } public Channel getFirst(String itemName, Direction direction, InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (itemName.equals(aChannel.item) && remoteAddress.equals(aChannel.remote) && direction.equals(aChannel.direction)) { return aChannel; } } return null; } } public Channel getFirstServed(String itemName, Direction direction, InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (itemName.equals(aChannel.item) && remoteAddress.equals(aChannel.remote) && aChannel.channel != null && direction.equals(aChannel.direction)) { return aChannel; } } return null; } } public void replace(String itemName, Direction direction, SocketChannel oldSocketChannel, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (itemName.equals(aChannel.item) && oldSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { aChannel.channel = channel; } } } } public void replace(String itemName, Direction direction, InetSocketAddress remoteAddress, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (useAddressMask && (aChannel.host.equals("*") || aChannel.port.equals("*")) && direction.equals(aChannel.direction) && itemName.equals(aChannel.item) && !channel.equals(aChannel.channel)) { if (aChannel.host.equals("*") && aChannel.port.equals(Integer.toString(remoteAddress.getPort()))) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals(remoteAddress.getHostString())) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals("*")) { aChannel.channel = channel; } } else if (itemName.equals(aChannel.item) && remoteAddress.equals(aChannel.remote) && direction.equals(aChannel.direction) && !channel.equals(aChannel.channel)) { aChannel.channel = channel; } } } } public ArrayList<Channel> getAll(String itemName, Direction direction, SocketChannel theSocketChannel) { synchronized (this) { ArrayList<Channel> selectedChannels = new ArrayList<Channel>(); Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (itemName.equals(aChannel.item) && theSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { selectedChannels.add(aChannel); } } return selectedChannels; } } public void setAllBlocking(String itemName, Direction direction, SocketChannel theSocketChannel, boolean b) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (itemName.equals(aChannel.item) && theSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { aChannel.isBlocking = b; } } } } public Channel getFirstServed(InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (remoteAddress.equals(aChannel.remote) && aChannel.channel != null) { return aChannel; } } return null; } } public void replace(Direction direction, SocketChannel oldSocketChannel, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (oldSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { aChannel.channel = channel; } } } } public void replace(Direction direction, InetSocketAddress remoteAddress, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (useAddressMask && (aChannel.host.equals("*") || aChannel.port.equals("*")) && remoteAddress.equals(aChannel.remote) && direction.equals(aChannel.direction) && !channel.equals(aChannel.channel)) { if (aChannel.host.equals("*") && aChannel.port.equals(Integer.toString(remoteAddress.getPort()))) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals(remoteAddress.getHostString())) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals("*")) { aChannel.channel = channel; } } else if (remoteAddress.equals(aChannel.remote) && direction.equals(aChannel.direction) && !channel.equals(aChannel.channel)) { aChannel.channel = channel; } } } } public ArrayList<Channel> getAll(Direction direction, SocketChannel theSocketChannel) { synchronized (this) { ArrayList<Channel> selectedChannels = new ArrayList<Channel>(); Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { selectedChannels.add(aChannel); } } return selectedChannels; } } public void setAllBlocking(Direction direction, SocketChannel theSocketChannel, boolean b) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel) && direction.equals(aChannel.direction)) { aChannel.isBlocking = b; } } } } public Channel getFirstServed(Direction direction, InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (remoteAddress.equals(aChannel.remote) && aChannel.channel != null && direction.equals(aChannel.direction)) { return aChannel; } } return null; } } public void replace(SocketChannel oldSocketChannel, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (oldSocketChannel.equals(aChannel.channel)) { aChannel.channel = channel; } } } } public void replace(InetSocketAddress remoteAddress, SocketChannel channel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (useAddressMask && (aChannel.host.equals("*") || aChannel.port.equals("*")) && !channel.equals(aChannel.channel)) { if (aChannel.host.equals("*") && aChannel.port.equals(Integer.toString(remoteAddress.getPort()))) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals(remoteAddress.getHostString())) { aChannel.channel = channel; } else if (aChannel.port.equals("*") && aChannel.host.equals("*")) { aChannel.channel = channel; } } else if (remoteAddress.equals(aChannel.remote) && !channel.equals(aChannel.channel)) { aChannel.channel = channel; } } } } public ArrayList<Channel> getAll(SocketChannel theSocketChannel) { synchronized (this) { ArrayList<Channel> selectedChannels = new ArrayList<Channel>(); Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel)) { selectedChannels.add(aChannel); } } return selectedChannels; } } public void setAllBlocking(SocketChannel theSocketChannel, boolean b) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel)) { aChannel.isBlocking = b; } } } } public void setAllReconnecting(SocketChannel theSocketChannel, boolean b) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel)) { aChannel.isReconnecting = b; } } } } public Channel getFirstNotServed(Direction direction, InetSocketAddress remoteAddress) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (useAddressMask && (aChannel.host.equals("*") || aChannel.port.equals("*")) && direction.equals(aChannel.direction) && (aChannel.channel == null || !aChannel.channel.isOpen())) { if (aChannel.host.equals("*") && aChannel.port.equals(Integer.toString(remoteAddress.getPort()))) { return aChannel; } else if (aChannel.port.equals("*") && aChannel.host.equals(remoteAddress.getHostString())) { return aChannel; } else if (aChannel.port.equals("*") && aChannel.host.equals("*")) { return aChannel; } } else if (remoteAddress.equals(aChannel.remote) && direction.equals(aChannel.direction) && (aChannel.channel == null || !aChannel.channel.isOpen())) { return aChannel; } } return null; } } public boolean isBlocking(SocketChannel theSocketChannel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel) && aChannel.isBlocking) { return true; } } return false; } } public Channel getBlocking(SocketChannel theSocketChannel) { synchronized (this) { Iterator<C> it = iterator(); while (it.hasNext()) { C aChannel = it.next(); if (theSocketChannel.equals(aChannel.channel) && aChannel.isBlocking) { return aChannel; } } return null; } } } /** * Simple helper class to store data that needs to be sent over a given channel * * * @author Karel Goderis * @since 1.4.0 * **/ protected class WriteBufferElement { public Channel channel; public ByteBuffer buffer; public boolean isBlocking; public WriteBufferElement(Channel channel, ByteBuffer buffer, boolean isBlocking) { super(); this.channel = channel; this.buffer = buffer; this.isBlocking = isBlocking; } @Override public String toString() { String response = null; response = "WriteBufferElement [Channel="; if (channel != null) { response = response + channel.toString(); } response = response + ", buffer=" + new String(buffer.array()) + ", isblocking=" + isBlocking + "]"; return response; } } /** * Instantiates a new abstract channel event subscriber binding. */ public AbstractSocketChannelBinding() { } protected void configureListenerChannel() { // open the listener port try { listenerChannel = ServerSocketChannel.open(); listenerChannel.socket().bind(new InetSocketAddress(listenerPort)); listenerChannel.configureBlocking(false); logger.info("Listening for incoming connections on {}", listenerChannel.getLocalAddress()); synchronized (selector) { selector.wakeup(); try { listenerKey = listenerChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (ClosedChannelException e1) { logger.error("An exception occurred while registering a selector: {}", e1.getMessage()); } } } catch (Exception e3) { logger.error("An exception occurred while creating the Listener Channel on port number {} ({})", listenerPort, e3.getMessage()); } } /** * Activate. */ @Override public void activate() { // register the selectors try { selector = Selector.open(); } catch (IOException e) { logger.error("An exception occurred while registering the selector: {}", e.getMessage()); } } /** * Deactivate. */ @Override public void deactivate() { try { selector.close(); } catch (IOException e) { logger.error("An exception occurred while closing the selector: {}", e.getMessage()); } try { listenerChannel.close(); } catch (IOException e) { logger.error("An exception occurred while closing the Listener Channel on port number {} ({})", listenerPort, e.getMessage()); } } /** * Find the first matching {@link ChannelBindingProvider} * according to <code>itemName</code> * * @param itemName * * @return the matching binding provider or <code>null</code> if no binding * provider could be found */ protected P findFirstMatchingBindingProvider(String itemName) { P firstMatchingProvider = null; for (P provider : this.providers) { if (!useAddressMask) { List<InetSocketAddress> socketAddresses = provider.getInetSocketAddresses(itemName); if (socketAddresses != null && socketAddresses.size() > 0) { firstMatchingProvider = provider; break; } } else { List<Command> commands = provider.getAllCommands(itemName); if (commands != null && commands.size() > 0) { firstMatchingProvider = provider; break; } } } return firstMatchingProvider; } /** * {@inheritDoc} */ @SuppressWarnings("rawtypes") @Override public void updated(Dictionary config) throws ConfigurationException { if (config != null) { String bufferString = (String) config.get("buffersize"); if (StringUtils.isNotBlank(bufferString)) { maximumBufferSize = Integer.parseInt((bufferString)); } else { logger.info("The maximum buffer will be set to the default value of {}", maximumBufferSize); } String reconnectString = (String) config.get("retryinterval"); if (StringUtils.isNotBlank(reconnectString)) { reconnectInterval = Integer.parseInt((reconnectString)); } else { logger.info("The interval to retry connection setups will be set to the default value of {}", reconnectInterval); } String cronString = (String) config.get("reconnectcron"); if (StringUtils.isNotBlank(cronString)) { reconnectCron = cronString; } else { logger.info("The cron job to reset connections will be set to the default value of {}", reconnectCron); } String queueString = (String) config.get("queue"); if (StringUtils.isNotBlank(queueString)) { queueUntilConnected = Boolean.parseBoolean(queueString); } else { logger.info( "The setting to queue write operation until a channel gets connected will be set to the default value of {}", queueUntilConnected); } String portString = (String) config.get("port"); if (StringUtils.isNotBlank(portString)) { listenerPort = Integer.parseInt((portString)); } else { logger.info("The port to listen for incoming connections will be set to the default value of {}", listenerPort); } String shareString = (String) config.get("itemsharedconnections"); if (StringUtils.isNotBlank(shareString)) { itemShareChannels = Boolean.parseBoolean(shareString); } else { logger.info("The setting to share channels within an Item will be set to the default value of {}", itemShareChannels); } String shareString2 = (String) config.get("bindingsharedconnections"); if (StringUtils.isNotBlank(shareString2)) { bindingShareChannels = Boolean.parseBoolean(shareString2); } else { logger.info( "The setting to share channels between the items with the same direction will be set to the default value of {}", bindingShareChannels); } String shareString3 = (String) config.get("directionssharedconnections"); if (StringUtils.isNotBlank(shareString3)) { directionsShareChannels = Boolean.parseBoolean(shareString3); } else { logger.info("The setting to share channels between directions will be set to the default value of {}", bindingShareChannels); } String shareString4 = (String) config.get("addressmask"); if (StringUtils.isNotBlank(shareString4)) { useAddressMask = Boolean.parseBoolean(shareString4); } else { logger.info( "The setting to use address masks for incoming connections will be set to the default value of {}", useAddressMask); } if (useAddressMask && directionsShareChannels) { logger.warn( "The setting to share channels between directions is not compatible with the setting to use address masks. We will override the setting to share between directions"); directionsShareChannels = false; } if (bindingShareChannels && !itemShareChannels) { logger.warn( "The setting to share channels in the binding is not compatible with the setting to share channels within items. We will override the setting to share between items"); itemShareChannels = true; } if (directionsShareChannels && (!bindingShareChannels || !itemShareChannels)) { logger.warn( "The setting to share channels between directions is not compatible with the setting to share channels between items or within items. We will override the settings"); itemShareChannels = true; bindingShareChannels = true; } String refreshString = (String) config.get("refreshinterval"); if (StringUtils.isNotBlank(refreshString)) { refreshInterval = Long.parseLong((refreshString)); } else { logger.info("The refresh interval of the worker thread will be set to the default value of {}", refreshInterval); } if (listenerPort != 0) { configureListenerChannel(); } setProperlyConfigured(true); } } /** * {@inheritDoc} */ @Override protected void internalReceiveCommand(String itemName, Command command) { P provider = findFirstMatchingBindingProvider(itemName); if (provider == null) { logger.warn("cannot find matching binding provider [itemName={}, command={}]", itemName, command); return; } if (command != null) { List<Command> commands = provider.getQualifiedCommands(itemName, command); for (Command someCommand : commands) { Channel theChannel = null; if (useAddressMask && (provider.getHost(itemName, someCommand).equals("*") || provider.getPortAsString(itemName, someCommand).equals("*"))) { theChannel = channels.get(itemName, someCommand, provider.getDirection(itemName, someCommand), provider.getHost(itemName, someCommand), provider.getPortAsString(itemName, someCommand)); } else { theChannel = channels.get(itemName, someCommand, provider.getDirection(itemName, someCommand), new InetSocketAddress(provider.getHost(itemName, someCommand), provider.getPort(itemName, someCommand))); } SocketChannel theSocketChannel = null; if (theChannel != null) { theSocketChannel = theChannel.channel; } if (theSocketChannel != null) { boolean result = internalReceiveChanneledCommand(itemName, someCommand, theChannel, command.toString()); if (!theSocketChannel.isConnected() && !(useAddressMask && (provider.getHost(itemName, someCommand).equals("*") || provider.getPortAsString(itemName, someCommand).equals("*")))) { logger.warn( "The channel for {} has a connection problem. Data will queued to the new channel when it is successfully set up.", theChannel.remote); if (!theSocketChannel.isConnectionPending() || !theSocketChannel.isOpen()) { Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", theChannel); map.put("Binding", this); JobDetail job = newJob(ReconnectJob.class) .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .startNow().build(); try { if (job != null && trigger != null) { if (!theChannel.isReconnecting) { theChannel.isReconnecting = true; scheduler.scheduleJob(job, trigger); } } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } } if (result) { List<Class<? extends State>> stateTypeList = provider.getAcceptedDataTypes(itemName, someCommand); State newState = createStateFromString(stateTypeList, command.toString()); if (newState != null) { eventPublisher.postUpdate(itemName, newState); } } } else { logger.error("there is no channel that services [itemName={}, command={}]", itemName, command); } } } } /** * This function should be implemented and used to "setup" the * ASCII protocol after that the socket channel has been created. This because some ASCII * protocols need to first emit a certain sequence of characters to * configure or setup the communication channel with the remote end **/ abstract protected void configureChannel(Channel channel); /** * The actual implementation for receiving a command from the openhab runtime should go here * * @param itemName the item name * @param command the command * @param channel the network channel * @param commandAsString the command as String * @return true, if successful */ abstract protected boolean internalReceiveChanneledCommand(String itemName, Command command, Channel reference, String commandAsString); /** * Returns a {@link State} which is inherited from provide list of DataTypes. The call is delegated to the * {@link TypeParser}. If * <code>stateTypeList</code> is <code>null</code> the {@link StringType} is used. * * @param stateTypeList - a list of state types that we should parse against * @param transformedResponse - the string to be parsed * * @return a {@link State} which type is inherited by the {@link TypeParser} * or a {@link StringType} if <code>stateTypeList</code> is <code>null</code> */ protected State createStateFromString(List<Class<? extends State>> stateTypeList, String transformedResponse) { if (stateTypeList != null) { return TypeParser.parseState(stateTypeList, transformedResponse); } else { return StringType.valueOf(transformedResponse); } } /** * Parses the buffer received from the Channel * * @param networkChannel the network channel * @param byteBuffer the byte buffer */ protected void parseChanneledBuffer(Channel theChannel, ByteBuffer byteBuffer) { if (theChannel != null && byteBuffer != null && byteBuffer.limit() != 0) { parseBuffer(theChannel.item, theChannel.command, theChannel.direction, byteBuffer); } } /** * * Callback that will be called when data is received on a given channel. * This method should deal with the actual details of the protocol being implemented */ abstract protected void parseBuffer(String itemName, Command aCommand, Direction theDirection, ByteBuffer byteBuffer); /** * Queues (writes) a ByteBuffer to a channel * * @param theChannel the network channel * @param byteBuffer the byte buffer * @param isBlockingWriteRead set to true if we have to wait for a response from the remote end (e.g. ACK message or * alike) * @param timeOut time to wait for a response from the remote end * @return a ByteBuffer with the response, if blocking operation, or the original ByteBuffer in all other cases */ protected ByteBuffer writeBuffer(ByteBuffer theBuffer, Channel theChannel, boolean isBlockingWriteRead, long timeOut) { SocketChannel theSocketChannel = theChannel.channel; if (isBlockingWriteRead) { if (theBuffer != null) { if (theSocketChannel.isConnected() || queueUntilConnected) { writeQueue.add(new WriteBufferElement(theChannel, theBuffer, true)); } long currentElapsedTimeMillis = System.currentTimeMillis(); while (theChannel.buffer == null && (System.currentTimeMillis() - currentElapsedTimeMillis) < timeOut) { try { Thread.sleep(100); } catch (InterruptedException e) { logger.warn("Exception occurred while waiting during a blocking buffer write"); } } ByteBuffer responseBuffer = null; synchronized (this) { responseBuffer = theChannel.buffer; theChannel.buffer = null; theChannel.isBlocking = false; } return responseBuffer; } else { return theBuffer; } } else { if (theBuffer != null) { if (theSocketChannel.isConnected() || queueUntilConnected) { writeQueue.add(new WriteBufferElement(theChannel, theBuffer, false)); } } return theBuffer; } } /** * Quartz Job to reconnect a channel * * @author Karel Goderis * @since 1.2.0 * */ public static class ReconnectJob implements Job { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); AbstractSocketChannelBinding theBinding = (AbstractSocketChannelBinding) dataMap.get("Binding"); AbstractSocketChannelBinding.Channel theChannel = (AbstractSocketChannelBinding.Channel) dataMap .get("Channel"); if (theChannel.isReconnecting) { if (theChannel.remote != null && !theChannel.channel.isOpen()) { SelectionKey sKey = theChannel.channel.keyFor(theBinding.selector); if (sKey != null) { sKey.cancel(); } try { theChannel.channel.close(); } catch (IOException e) { logger.error("An exception occurred while closing a channel: {}", e.getMessage()); } try { theChannel.channel = SocketChannel.open(); } catch (IOException e) { logger.error("An exception occurred while opening a channel: {}", e.getMessage()); } theChannel.isBlocking = false; theChannel.buffer = null; try { theChannel.channel.configureBlocking(false); // setKeepAlive(true); } catch (Exception e) { logger.error("An exception occurred while configuring a channel: {}", e.getMessage()); } synchronized (theBinding.selector) { theBinding.selector.wakeup(); int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT; try { if (theChannel.channel != null) { theChannel.channel.register(theBinding.selector, interestSet); } } catch (ClosedChannelException e1) { logger.error("An exception occurred while registering a selector: {}", e1.getMessage()); } } try { if (theChannel.channel != null) { theChannel.channel.connect(theChannel.remote); logger.info("Attempting to reconnect the channel for {}", theChannel.remote); } } catch (Exception e) { logger.error("An exception occurred while connecting a channel: {}", e.getMessage()); } } else { logger.debug("I cannot proceed without remote address"); } } else { logger.warn("Already reconnecting the channel for {}", theChannel.remote); } } } /** * Quartz Job to configure a channel * * @author Karel Goderis * @since 1.4.0 * */ public static class ConfigureJob implements Job { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); AbstractSocketChannelBinding theBinding = (AbstractSocketChannelBinding) dataMap.get("Binding"); AbstractSocketChannelBinding.Channel theChannel = (AbstractSocketChannelBinding.Channel) dataMap .get("Channel"); if (theChannel.channel.isConnected()) { theBinding.configureChannel(theChannel); } } } /** * @{inheritDoc} */ @Override protected void execute() { // Cycle through the Items and setup channels if required for (P provider : providers) { for (String itemName : provider.getItemNames()) { for (Command aCommand : provider.getAllCommands(itemName)) { String remoteHost = provider.getHost(itemName, aCommand); String remotePort = provider.getPortAsString(itemName, aCommand); Direction direction = provider.getDirection(itemName, aCommand); InetSocketAddress remoteAddress = null; if (!(remoteHost.equals("*") || remotePort.equals("*"))) { remoteAddress = new InetSocketAddress(remoteHost, Integer.parseInt(remotePort)); } Channel newChannel = null; Channel existingChannel = null; if (useAddressMask && (remoteHost.equals("*") || remotePort.equals("*"))) { newChannel = new Channel(itemName, aCommand, remoteHost, remotePort, provider.getDirection(itemName, aCommand), false, null, false, null); existingChannel = channels.get(itemName, aCommand, direction, remoteHost, remotePort); } else { newChannel = new Channel(itemName, aCommand, remoteAddress, provider.getDirection(itemName, aCommand), false, null, false, null); existingChannel = channels.get(itemName, aCommand, direction, remoteAddress); } if (existingChannel == null) { if (direction == Direction.IN) { boolean assigned = false; if (useAddressMask && (remoteHost.equals("*") || remotePort.equals("*"))) { logger.warn( "When using address masks we will not verify if we are already listening to similar incoming connections"); logger.info("We will accept data coming from the remote end {}:{}", remoteHost, remotePort); channels.add(newChannel); } else { if (itemShareChannels) { Channel firstChannel = channels.getFirstServed(itemName, direction, remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (bindingShareChannels) { Channel firstChannel = channels.getFirstServed(direction, remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (directionsShareChannels) { Channel firstChannel = channels.getFirstServed(remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (!assigned || newChannel.channel == null) { if (channels.contains(itemName, aCommand, Direction.IN, remoteAddress)) { logger.warn("We already listen for incoming connections from {}", remoteAddress); } else { logger.debug("Setting up the inbound channel {}", newChannel); channels.add(newChannel); logger.info("We will accept data coming from the remote end {}", remoteAddress); } } } } else if (direction == Direction.OUT) { boolean assigned = false; if (useAddressMask && (remoteHost.equals("*") || remotePort.equals("*"))) { logger.error( "We do not accept outgoing connections for Items that do use address masks"); } else { channels.add(newChannel); if (newChannel.channel == null) { if (itemShareChannels) { Channel firstChannel = channels.getFirstServed(itemName, direction, remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (bindingShareChannels) { Channel firstChannel = channels.getFirstServed(direction, remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (directionsShareChannels) { Channel firstChannel = channels.getFirstServed(remoteAddress); if (firstChannel != null) { newChannel.channel = firstChannel.channel; assigned = true; } } if (assigned) { logger.debug("Setting up the outbound assigned channel {} ", newChannel); } synchronized (this) { if (!assigned || newChannel.channel == null) { SocketChannel newSocketChannel = null; try { newSocketChannel = SocketChannel.open(); } catch (IOException e2) { logger.error("An exception occurred while opening a channel: {}", e2.getMessage()); } try { newSocketChannel.socket().setKeepAlive(true); newSocketChannel.configureBlocking(false); } catch (IOException e) { logger.error("An exception occurred while configuring a channel: {}", e.getMessage()); } synchronized (selector) { selector.wakeup(); int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT; try { newSocketChannel.register(selector, interestSet); } catch (ClosedChannelException e1) { logger.error( "An exception occurred while registering a selector: {}", e1.getMessage()); } } newChannel.channel = newSocketChannel; logger.debug("Setting up the outbound channel {}", newChannel); try { logger.info("Connecting the channel {} ", newChannel); newSocketChannel.connect(remoteAddress); } catch (IOException e) { logger.error("An exception occurred while connecting a channel: {}", e.getMessage()); } } } } else { logger.info("There is already an active channel {} for the remote end {}", newChannel.channel, newChannel.remote); } } } } } } } // Check on channels for which we have to process data synchronized (selector) { try { // Wait for an event selector.selectNow(); } catch (IOException e) { logger.error("An exception occurred while Selecting ({})", e.getMessage()); } } // Get list of selection keys with pending events Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // Process each key at a time while (it.hasNext()) { SelectionKey selKey = it.next(); it.remove(); if (selKey.isValid()) { if (selKey == listenerKey) { if (selKey.isAcceptable()) { try { SocketChannel newChannel = listenerChannel.accept(); logger.info("Received connection request from {}", newChannel.getRemoteAddress()); Channel firstChannel = channels.getFirstNotServed(Direction.IN, (InetSocketAddress) newChannel.getRemoteAddress()); if (firstChannel != null) { if (firstChannel.direction == Direction.IN) { if (useAddressMask && (firstChannel.host.equals("*") || firstChannel.port.equals("*"))) { logger.info( "{}:{} is an allowed masked remote end. The channel will now be configured", firstChannel.host, firstChannel.port); } else { logger.info("{} is an allowed remote end. The channel will now be configured", firstChannel.remote); } if (firstChannel.channel == null || !firstChannel.channel.isOpen()) { firstChannel.channel = newChannel; firstChannel.isBlocking = false; firstChannel.buffer = null; if (itemShareChannels) { channels.replace(firstChannel.item, firstChannel.direction, (InetSocketAddress) newChannel.getRemoteAddress(), firstChannel.channel); } if (bindingShareChannels) { channels.replace(firstChannel.direction, (InetSocketAddress) newChannel.getRemoteAddress(), firstChannel.channel); } if (directionsShareChannels) { channels.replace((InetSocketAddress) newChannel.getRemoteAddress(), firstChannel.channel); } try { newChannel.configureBlocking(false); // setKeepAlive(true); } catch (IOException e) { logger.error("An exception occurred while configuring a channel: {}", e.getMessage()); } synchronized (selector) { selector.wakeup(); try { newChannel.register(selector, newChannel.validOps()); } catch (ClosedChannelException e1) { logger.error("An exception occurred while registering a selector: {}", e1.getMessage()); } } Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", firstChannel); map.put("Binding", this); JobDetail job = newJob(ConfigureJob.class) .withIdentity( Integer.toHexString(hashCode()) + "-Configure-" + Long.toString(System.currentTimeMillis()), this.toString()) .usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity( Integer.toHexString(hashCode()) + "-Configure-" + Long.toString(System.currentTimeMillis()), this.toString()) .startNow().build(); try { if (job != null && trigger != null && selKey != listenerKey) { scheduler.scheduleJob(job, trigger); } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } else { logger.info( "We previously already accepted a connection from the remote end {} for this channel. Goodbye", firstChannel.remote); newChannel.close(); } } else { logger.info( "Disconnecting the remote end {} that tries to connect an outbound only port", newChannel.getRemoteAddress()); newChannel.close(); } } else { logger.info("Disconnecting the unallowed remote end {}", newChannel.getRemoteAddress()); newChannel.close(); } } catch (IOException e) { logger.error("An exception occurred while configuring a channel: {}", e.getMessage()); } } } else { SocketChannel theSocketChannel = (SocketChannel) selKey.channel(); Channel theChannel = channels.get(theSocketChannel); if (selKey.isConnectable()) { channels.setAllReconnecting(theSocketChannel, false); boolean result = false; boolean error = false; try { result = theSocketChannel.finishConnect(); } catch (NoConnectionPendingException e) { // this channel is not connected and a connection operation // has not been initiated logger.warn("The channel {} has no connection pending ({})", theSocketChannel, e.getMessage()); error = true; } catch (ClosedChannelException e) { // If some other I/O error occurs logger.warn("The channel {} is closed ({})", theSocketChannel, e.getMessage()); error = true; } catch (IOException e) { // If some other I/O error occurs logger.warn("The channel {} has encountered an unknown IO Exception: {}", theSocketChannel, e.getMessage()); error = true; } if (error) { Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", theChannel); map.put("Binding", this); JobDetail job = newJob(ReconnectJob.class) .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .startAt(futureDate(reconnectInterval, IntervalUnit.SECOND)).build(); try { if (job != null && trigger != null && selKey != listenerKey) { if (!theChannel.isReconnecting) { channels.setAllReconnecting(theSocketChannel, true); scheduler.scheduleJob(job, trigger); } } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } else { if (result) { InetSocketAddress remote = null; try { remote = (InetSocketAddress) theSocketChannel.getRemoteAddress(); } catch (IOException e) { logger.error( "An exception occurred while getting the remote address of channel {} ({})", theSocketChannel, e.getMessage()); } logger.info("The channel for {} is now connected", remote); if (itemShareChannels) { channels.replace(theChannel.item, theChannel.direction, remote, theChannel.channel); } if (bindingShareChannels) { channels.replace(theChannel.direction, remote, theChannel.channel); } if (directionsShareChannels) { channels.replace(remote, theChannel.channel); } Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", theChannel); map.put("Binding", this); JobDetail job = newJob(ConfigureJob.class) .withIdentity(Integer.toHexString(hashCode()) + "-Configure-" + Long.toString(System.currentTimeMillis()), this.toString()) .usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity(Integer.toHexString(hashCode()) + "-Configure-" + Long.toString(System.currentTimeMillis()), this.toString()) .startNow().build(); try { if (job != null && trigger != null && selKey != listenerKey) { scheduler.scheduleJob(job, trigger); } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } job = newJob(ReconnectJob.class) .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .usingJobData(map).build(); trigger = newTrigger() .withIdentity(Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), this.toString()) .withSchedule(cronSchedule(reconnectCron)).build(); try { if (job != null && trigger != null && selKey != listenerKey) { scheduler.scheduleJob(job, trigger); } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } } } else if (selKey.isReadable()) { ByteBuffer readBuffer = ByteBuffer.allocate(maximumBufferSize); int numberBytesRead = 0; boolean error = false; try { // TODO: Additional code to split readBuffer in multiple parts, in case the data send by the // remote end is not correctly fragemented. Could be handed of to implementation class if // for example, the buffer needs to be split based on a special character like line feed or // carriage return numberBytesRead = theSocketChannel.read(readBuffer); } catch (NotYetConnectedException e) { logger.warn("The channel for {} has no connection pending ({})", theChannel.remote, e.getMessage()); if (!theSocketChannel.isConnectionPending()) { error = true; } } catch (IOException e) { // If some other I/O error occurs logger.warn("The channel for {} has encountered an unknown IO Exception: {}", theChannel.remote, e.getMessage()); error = true; } if (numberBytesRead == -1) { try { theSocketChannel.close(); } catch (IOException e) { logger.warn("The channel for {} is closed ({})", theChannel.remote, e.getMessage()); } error = true; } if (error) { if (theChannel.direction == Direction.OUT) { Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", theChannel); map.put("Binding", this); JobDetail job = newJob(ReconnectJob.class).withIdentity( Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), "AbstractSocketChannelBinding").usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity( Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), "AbstractSocketChannelBinding") .startAt(futureDate(reconnectInterval, IntervalUnit.SECOND)).build(); try { if (job != null && trigger != null && selKey != listenerKey) { if (!theChannel.isReconnecting) { channels.setAllReconnecting(theSocketChannel, true); scheduler.scheduleJob(job, trigger); } } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } else { theChannel.channel = null; } } else { ArrayList<Channel> channelsToServe = new ArrayList<Channel>(); channelsToServe = channels.getAll(theSocketChannel); if (channelsToServe.size() > 0) { readBuffer.flip(); boolean isBlocking = channels.isBlocking(theSocketChannel); if (isBlocking) { // if we are in a blocking operation, we get are now finished and we have to reset // the flag. The read buffer will be returned to the instance // that initiated the write opreation - it has to parse the buffer itself theChannel = channels.getBlocking(theSocketChannel); theChannel.buffer = readBuffer; theChannel.isBlocking = false; } else { for (Channel aChannel : channelsToServe) { // if not, then we parse the buffer as ususal parseChanneledBuffer(aChannel, readBuffer); } } } else { try { logger.warn( "No channel is active or defined for the data we received from {}. It will be discarded.", theSocketChannel.getRemoteAddress()); } catch (IOException e) { logger.error( "An exception occurred while getting the remote address of the channel {} ({})", theSocketChannel, e.getMessage()); } } } } else if (selKey.isWritable()) { boolean isBlocking = channels.isBlocking(theSocketChannel); if (isBlocking) { // if this channel is already flagged as being in a blocked write/read operation, we skip // this selKey } else { // pick up a QueueElement for this channel, if any WriteBufferElement theElement = null; Iterator<WriteBufferElement> iterator = writeQueue.iterator(); while (iterator.hasNext()) { WriteBufferElement anElement = iterator.next(); if (anElement.channel.channel.equals(theSocketChannel)) { theElement = anElement; break; } } if (theElement != null && theElement.buffer != null) { logger.debug("Picked {} from the queue", theElement); if (theElement.isBlocking) { theElement.channel.isBlocking = true; } boolean error = false; theElement.buffer.rewind(); try { logger.debug("Sending {} for the outbound channel {}->{}", new Object[] { new String(theElement.buffer.array()), theElement.channel.channel.getLocalAddress(), theElement.channel.channel.getRemoteAddress() }); theSocketChannel.write(theElement.buffer); } catch (NotYetConnectedException e) { logger.warn("The channel for {} has no connection pending ({})", theChannel.remote, e.getMessage()); if (!theSocketChannel.isConnectionPending()) { error = true; } } catch (ClosedChannelException e) { // If some other I/O error occurs logger.warn("The channel for {} is closed ({})", theChannel.remote, e.getMessage()); error = true; } catch (IOException e) { // If some other I/O error occurs logger.warn("The channel for {} has encountered an unknown IO Exception: {}", theChannel.remote, e.getMessage()); error = true; } if (error) { if (theElement.channel.direction == Direction.OUT) { Scheduler scheduler = null; try { scheduler = StdSchedulerFactory.getDefaultScheduler(); } catch (SchedulerException e1) { logger.error("An exception occurred while getting the Quartz scheduler: {}", e1.getMessage()); } JobDataMap map = new JobDataMap(); map.put("Channel", theElement.channel); map.put("Binding", this); JobDetail job = newJob(ReconnectJob.class).withIdentity( Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), "AbstractSocketChannelBinding").usingJobData(map).build(); Trigger trigger = newTrigger() .withIdentity( Integer.toHexString(hashCode()) + "-Reconnect-" + Long.toString(System.currentTimeMillis()), "AbstractSocketChannelBinding") .startAt(futureDate(reconnectInterval, IntervalUnit.SECOND)).build(); try { if (job != null && trigger != null && selKey != listenerKey) { if (!theElement.channel.isReconnecting) { channels.setAllReconnecting(theSocketChannel, true); scheduler.scheduleJob(job, trigger); } } } catch (SchedulerException e) { logger.error( "An exception occurred while scheduling a job with the Quartz Scheduler {}", e.getMessage()); } } else { theElement.channel.channel = null; } } else { if (theElement != null) { writeQueue.remove(theElement); } } } } } } } } } /** * @{inheritDoc} */ @Override protected long getRefreshInterval() { return refreshInterval; } }