/** * 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.net.listener.netty.handlers; import java.net.SocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.helios.apmrouter.OpCode; import org.helios.apmrouter.metric.AgentIdentity; import org.helios.apmrouter.server.ServerComponentBean; import org.helios.apmrouter.server.net.listener.netty.ChannelGroupAware; import org.helios.apmrouter.server.net.listener.netty.group.ManagedChannelGroup; import org.helios.apmrouter.server.services.session.ChannelType; import org.helios.apmrouter.server.services.session.SharedChannelGroup; import org.helios.apmrouter.server.services.session.VirtualUDPChannel; 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.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.handler.logging.LoggingHandler; import org.jboss.netty.logging.InternalLogLevel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jmx.export.annotation.ManagedMetric; import org.springframework.jmx.support.MetricType; /** * <p>Title: AbstractAgentRequestHandler</p> * <p>Description: Base class </p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.server.net.listener.netty.handlers.AbstractAgentRequestHandler</code></p> */ public abstract class AbstractAgentRequestHandler extends ServerComponentBean implements AgentRequestHandler, ChannelGroupAware { /** The channel group */ protected ManagedChannelGroup channelGroup = null; /** The synchronous request timeout map */ protected static final TimeoutQueueMap<String, CountDownLatch> timeoutMap = new TimeoutQueueMap<String, CountDownLatch>(2000); /** The logical session timeout map */ protected static final TimeoutQueueMap<String, SocketAddress> sessionTimeoutMap = new TimeoutQueueMap<String, SocketAddress>(15000); /** Logging handler */ protected static final LoggingHandler clientConnLogHandler = new LoggingHandler("org.helios.AgentMetricHandler", InternalLogLevel.DEBUG, true); /** A timeout map of socket addresses to which OpCodes such as {@link OpCode#WHO} requests have been sent to but for which a response has not been received */ private static final TimeoutQueueMap<String, OpCode> pendingOps = new TimeoutQueueMap<String, OpCode>(5000); /** The downstream handler for marshalling json responses */ @Autowired(required=true) protected JsonResponseDownstreamHandler jsonResponseDownstreamHandler = null; /** * Removes a pending operation * @param remoteAddress The remote address the op was sent to * @param opCode The op code of the request that was sent */ protected static void removePendingOp(SocketAddress remoteAddress, OpCode opCode) { if(remoteAddress==null) throw new IllegalArgumentException("The passed remote address was null", new Throwable()); if(opCode==null) throw new IllegalArgumentException("The passed op code was null", new Throwable()); String key = new StringBuilder(remoteAddress.toString()).append("|").append(opCode.name()).toString(); pendingOps.remove(key); } /** * Adds a pending operation * @param remoteAddress The remote address the op was sent to * @param opCode The op code of the request that was sent */ protected static void addPendingOp(SocketAddress remoteAddress, OpCode opCode) { if(remoteAddress==null) throw new IllegalArgumentException("The passed remote address was null", new Throwable()); if(opCode==null) throw new IllegalArgumentException("The passed op code was null", new Throwable()); String key = new StringBuilder(remoteAddress.toString()).append("|").append(opCode.name()).toString(); pendingOps.put(key, opCode); } /** * Determines if there is a pending op for the passed address and op code * @param remoteAddress The remote address the op was sent to * @param opCode The op code of the request that was sent * @return true if there is a pending op for the passed address and op code, false otherwise */ protected static boolean containsPendingOp(SocketAddress remoteAddress, OpCode opCode) { if(remoteAddress==null) throw new IllegalArgumentException("The passed remote address was null", new Throwable()); if(opCode==null) throw new IllegalArgumentException("The passed op code was null", new Throwable()); String key = new StringBuilder(remoteAddress.toString()).append("|").append(opCode.name()).toString(); return pendingOps.containsValue(key); } /** * Returns the total number of pending {@link OpCode#WHO} requests in Whoville * @return the total number of pending {@link OpCode#WHO} requests */ @ManagedMetric(category="UDPOpRequests", metricType=MetricType.COUNTER, description="total number of pending op requests") public int getPendingOps() { return pendingOps.size(); } /** * Sets the channel group * @param channelGroup the injected channel group */ @Override public void setChannelGroup(ManagedChannelGroup channelGroup) { this.channelGroup = channelGroup; } /** * Acquires a channel connected to the provided remote address * @param incoming The incoming channel to acquire a new channel from, if required * @param remoteAddress The remote address to connect to * @return a channel connected to the remote address */ protected Channel getChannelForRemote(final Channel incoming, final SocketAddress remoteAddress) { Channel channel = SharedChannelGroup.getInstance().getByRemote(remoteAddress); if(channel==null) { synchronized(SharedChannelGroup.getInstance()) { channel = SharedChannelGroup.getInstance().getByRemote(remoteAddress); if(channel==null) { channel = new VirtualUDPChannel(incoming, remoteAddress); if(channel.getPipeline().get(JsonResponseDownstreamHandler.class)==null) { channel.getPipeline().addFirst("JsonResponseDownstreamHandler", jsonResponseDownstreamHandler); info("Added JsonResponseDownstreamHandler to channel [", channel, "]"); } SharedChannelGroup.getInstance().add(channel, ChannelType.UDP_AGENT, "UDPAgent"); // try { // } catch (Exception e) { // throw new RuntimeException("Failed to acquire remote connection to [" + remoteAddress + "]", e); // } } } } return channel; } /** * Sends a {@link OpCode#WHO} request to a newly connected channel * @param channel The newly connected channel * @param remoteAddress Thre remote address of the newly connected channel */ protected void sendWho(Channel channel, final SocketAddress remoteAddress) { byte[] bytes = remoteAddress.toString().getBytes(); ChannelBuffer cb = ChannelBuffers.directBuffer(bytes.length+5); cb.writeByte(OpCode.WHO.op()); cb.writeInt(bytes.length); cb.writeBytes(bytes); info("Sending Who Request to [", remoteAddress, "]"); addPendingOp(remoteAddress, OpCode.WHO); channel.write(cb, remoteAddress).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { if(f.isSuccess()) { info("Confirmed Send Of Who Request to [", remoteAddress, "]"); } else { error("Failed to send Who request to [", remoteAddress, "]", f.getCause()); removePendingOp(remoteAddress, OpCode.WHO); } } }); } // protected void registerProxyMBeanServerConnection(String protocol, Channel channel, SocketAddress remoteAddress) { // // } /** * Sends a ping request to the passed address * @param channel The channel to the client to ping * @param timeout the timeout in ms. * @return true if ping was confirmed within the timeout, false otherwise */ public boolean ping(Channel channel, long timeout) { try { StringBuilder key = new StringBuilder(); ChannelBuffer ping = encodePing(key); channel.write(ping,channel.getRemoteAddress()); CountDownLatch latch = new CountDownLatch(1); timeoutMap.put(key.toString(), latch, timeout); return latch.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return false; } } /** * 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(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; } /** * Decodes a ping from the passed channel buffer, and if the resulting key locates a latch in the timeout map, counts it down. * @param cb The ChannelBuffer to read the ping from */ protected void decodePing(ChannelBuffer cb) { int byteCount = cb.readInt(); byte[] bytes = new byte[byteCount]; cb.readBytes(bytes); String key = new String(bytes); CountDownLatch latch = timeoutMap.remove(key); if(latch!=null) latch.countDown(); } }