/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.server.services.session;
import java.net.SocketAddress;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.helios.apmrouter.OpCode;
import org.helios.apmrouter.metric.AgentIdentity;
import org.helios.apmrouter.util.TimeoutListener;
import org.helios.apmrouter.util.TimeoutQueueMap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelConfig;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelLocal;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.Channels;
/**
* <p>Title: VirtualUDPChannel</p>
* <p>Description: Emulates a child channel for UDP, which of course does not exist, but we're mimicking a session</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.services.session.VirtualUDPChannel</code></p>
* FIXME: Ping related stuff should be configurable
*/
public class VirtualUDPChannel implements Channel, TimeoutListener<Integer, Integer> {
/** Serial number generator */
protected static final AtomicInteger serial = new AtomicInteger(0);
/** The real parent channel */
protected final Channel parent;
/** The remote address of the client this channel represents */
protected final SocketAddress remoteAddress;
/** The virtual channel ID */
protected final int id;
/** Indicates if the channel is considered "open" */
protected final AtomicBoolean connected = new AtomicBoolean(true);
/** The virtual channel local */
protected final ChannelLocal<Object> channelLocal;
/** The close future */
protected final ChannelFuture myCloseFuture;
// ===============================================================
// Ping related stuff
// ===============================================================
/** The ping timeout in ms. */
protected long pingTimeout = 2000;
/** The maximum number of consecutive ping fails */
protected int maxPingFails = 3;
/** The number of consecutive ping fails */
protected final AtomicInteger consecutivePingFails = new AtomicInteger(0);
/** The ping schedule handle (to cancel on close) */
protected ScheduledFuture<?> pingSchedule;
/** The pending ping requests */
protected static final TimeoutQueueMap<Integer, Integer> timeoutMap = new TimeoutQueueMap<Integer, Integer>(5000);
// ===============================================================
/**
* Creates a new VirtualUDPChannel
* @param parent The real parent channel
* @param remoteAddress The remote address of the client this channel represents
*/
public VirtualUDPChannel(Channel parent, SocketAddress remoteAddress) {
this.parent = parent;
this.remoteAddress = remoteAddress;
id = serial.decrementAndGet();
if(id==Integer.MIN_VALUE) {
synchronized(serial) {
int i = serial.intValue();
if(i == Integer.MIN_VALUE || i > 0) {
serial.set(0);
}
}
}
myCloseFuture = Channels.future(this);
channelLocal = new ChannelLocal<Object>();
timeoutMap.addListener(this);
}
/**
* Sets the ping scheduler to be canceled on close
* @param pingSchedule The ping schedule handle (to cancel on close)
*/
public void setPingSchedule(ScheduledFuture<?> pingSchedule) {
this.pingSchedule = pingSchedule;
}
/**
* <p>This callback occurs when a ping times out.
* {@inheritDoc}
* @see org.helios.apmrouter.util.TimeoutListener#onTimeout(java.lang.Object, java.lang.Object)
*/
@Override
public void onTimeout(Integer key, Integer value) {
if(key.equals(getId())) {
int timeouts = consecutivePingFails.incrementAndGet();
if(timeouts>=maxPingFails) {
close();
}
}
}
/**
* Sends a ping request to the agent at the end of this channel
*/
public void ping() {
StringBuilder key = new StringBuilder();
ChannelBuffer ping = encodePing(key);
write(ping);
timeoutMap.put(getId(), getId());
}
/**
* Clears the current pending ping
*/
public void pingResponse() {
timeoutMap.remove(getId());
}
/**
* Creates a ping channel buffer and appends the key to the passed buffer
* @param key The buffer to place the key in
* @return the ping ChannelBuffer
*/
protected ChannelBuffer encodePing(final StringBuilder key) {
String _key = new StringBuilder(AgentIdentity.ID.getHostName()).append("-").append(AgentIdentity.ID.getAgentName()).append("-").append(System.nanoTime()).toString();
key.append(_key);
byte[] bytes = _key.getBytes();
ChannelBuffer ping = ChannelBuffers.buffer(1+4+bytes.length);
ping.writeByte(OpCode.PING.op());
ping.writeInt(bytes.length);
ping.writeBytes(bytes);
return ping;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#close()
*/
@Override
public ChannelFuture close() {
pingSchedule.cancel(true);
timeoutMap.removeListener(this);
channelLocal.remove(this);
Channels.fireChannelClosed(this);
myCloseFuture.setSuccess();
return myCloseFuture;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getCloseFuture()
*/
@Override
public ChannelFuture getCloseFuture() {
return myCloseFuture;
}
/**
* {@inheritDoc}
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Channel o) {
return parent.compareTo(o);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getId()
*/
@Override
public Integer getId() {
return id;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getFactory()
*/
@Override
public ChannelFactory getFactory() {
return parent.getFactory();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getParent()
*/
@Override
public Channel getParent() {
return parent;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getConfig()
*/
@Override
public ChannelConfig getConfig() {
return parent.getConfig();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getPipeline()
*/
@Override
public ChannelPipeline getPipeline() {
return parent.getPipeline();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#isOpen()
*/
@Override
public boolean isOpen() {
return connected.get();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#isBound()
*/
@Override
public boolean isBound() {
return parent.isBound();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#isConnected()
*/
@Override
public boolean isConnected() {
return connected.get();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getLocalAddress()
*/
@Override
public SocketAddress getLocalAddress() {
return parent.getLocalAddress();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getRemoteAddress()
*/
@Override
public SocketAddress getRemoteAddress() {
return remoteAddress;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#write(java.lang.Object)
*/
@Override
public ChannelFuture write(Object message) {
return parent.write(message, remoteAddress);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#write(java.lang.Object, java.net.SocketAddress)
*/
@Override
public ChannelFuture write(Object message, SocketAddress remoteAddress) {
return parent.write(message, remoteAddress);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#bind(java.net.SocketAddress)
*/
@Override
public ChannelFuture bind(SocketAddress localAddress) {
return parent.bind(localAddress);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#connect(java.net.SocketAddress)
*/
@Override
public ChannelFuture connect(SocketAddress remoteAddress) {
return parent.connect(remoteAddress);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#disconnect()
*/
@Override
public ChannelFuture disconnect() {
return null;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#unbind()
*/
@Override
public ChannelFuture unbind() {
return null;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getInterestOps()
*/
@Override
public int getInterestOps() {
return parent.getInterestOps();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#isReadable()
*/
@Override
public boolean isReadable() {
return parent.isReadable();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#isWritable()
*/
@Override
public boolean isWritable() {
return parent.isWritable();
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#setInterestOps(int)
*/
@Override
public ChannelFuture setInterestOps(int interestOps) {
return parent.setInterestOps(interestOps);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#setReadable(boolean)
*/
@Override
public ChannelFuture setReadable(boolean readable) {
return parent.setReadable(readable);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#getAttachment()
*/
@Override
public Object getAttachment() {
return channelLocal.get(this);
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.Channel#setAttachment(java.lang.Object)
*/
@Override
public void setAttachment(Object attachment) {
channelLocal.set(this, attachment);
}
}