/**
* 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;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import org.helios.apmrouter.server.ServerComponentBean;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogLevel;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Log4JLoggerFactory;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
/**
* <p>Title: BaseAgentListener</p>
* <p>Description: Base class for agent listeners that listen for agent connections, disconnections and requests</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.server.net.listener.netty.BaseAgentListener</code></p>
* TODO: Migrate to generic Netty class structure
*/
public class BaseAgentListener extends ServerComponentBean implements ChannelPipelineFactory {
/** The netty channel factory's worker thread pool */
protected ExecutorService workerPool = null;
/** The interface that this listener will bind to */
protected String bindHost = null;
/** The port that this listener will bind to */
protected int bindPort = -1;
// /** The netty bootstrap */
// protected Bootstrap bootstrap = null;
// /** The netty channel factory */
// protected ChannelFactory channelFactory = null;
/** The channel pipeline initial handler bean names */
protected final SortedMap<Integer, String> channelHandlers = new ConcurrentSkipListMap<Integer, String>();
/** The channel options */
protected final Map<String, Object> channelOptions = new ConcurrentHashMap<String, Object>();
/** The socket address this listener is bound to */
protected InetSocketAddress socketAddress = null;
/** The resolved channel handlers in insertion order */
protected LinkedHashMap<String, ChannelHandler> resolvedHandlers = new LinkedHashMap<String, ChannelHandler>();
/** Indicates if a logging handler is installed in the pipelines created for this listener */
protected final AtomicBoolean loggingHandlerInstalled = new AtomicBoolean(false);
/** The relative location of the logging handler */
protected String loggingHandlerLocation = null;
/** The channel close future */
protected ChannelFuture closeFuture = null;
/** Indicates if the main channel is open */
protected final AtomicBoolean connected = new AtomicBoolean(false);
/**
* Creates a new BaseAgentListener
*/
protected BaseAgentListener() {
InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory());
}
/**
* Starts this listener
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#doStart()
*/
@Override
protected void doStart() throws Exception {
info("Resolving Channel Handlers");
resolvedHandlers.clear();
try {
for(Map.Entry<Integer, String> entry: channelHandlers.entrySet()) {
ChannelHandler handler = applicationContext.getBean(entry.getValue(), ChannelHandler.class);
debug("Resolved Channel Handler [", entry.getValue(), "]");
resolvedHandlers.put(beanName, handler);
}
info("Resolved [", resolvedHandlers.size(), "] Channel Handlers");
socketAddress = new InetSocketAddress(bindHost, bindPort);
info("Socket Address:", socketAddress);
} catch (Exception e) {
error("Failed to resolve channel handlers", e);
throw e;
}
}
/**
* Stops this listener
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponentBean#doStop()
*/
@Override
protected void doStop() {
for(ChannelHandler handler: resolvedHandlers.values()) {
if(handler instanceof ExecutionHandler) {
info("Stopping Execution Handler....");
((ExecutionHandler)handler).releaseExternalResources();
}
}
resolvedHandlers.clear();
socketAddress = null;
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline()
*/
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
LinkedHashMap<String, ChannelHandler> tmpHandlers = null;
synchronized(resolvedHandlers) {
tmpHandlers = new LinkedHashMap<String, ChannelHandler>(resolvedHandlers);
}
for(Map.Entry<String, ChannelHandler> entry: tmpHandlers.entrySet()) {
pipeline.addLast(entry.getKey(), entry.getValue());
}
return pipeline;
}
/**
* Returns the interface this listener is bound to
* @return the interface this listener is bound to
*/
@ManagedAttribute
public String getBindHost() {
return bindHost;
}
/**
* Sets the interface this listener is bound to
* @param bindHost the bindHost to set
*/
@ManagedAttribute
public void setBindHost(String bindHost) {
if(isStarted()) throw new IllegalStateException("Cannot set the bind host once listener is bound", new Throwable());
this.bindHost = bindHost;
}
/**
* Returns the port this listener is bound to
* @return the port this listener is bound to
*/
@ManagedAttribute
public int getBindPort() {
return bindPort;
}
/**
* Sets the port this listener will bind to
* @param bindPort the port this listener will bind to
*/
@ManagedAttribute
public void setBindPort(int bindPort) {
if(isStarted()) throw new IllegalStateException("Cannot set the bind port once listener is bound", new Throwable());
this.bindPort = bindPort;
}
/**
* Returns the channel handler bean names in the order they are bound into the pipelines created for this listener
* @return an array of channel handler bean names
*/
@ManagedAttribute
public String[] getChannelHandlerNames() {
List<String> names = new ArrayList<String>();
if(isStarted()) {
synchronized(resolvedHandlers) {
names.addAll(resolvedHandlers.keySet());
}
} else {
names.addAll(channelHandlers.values());
}
return names.toArray(new String[names.size()]);
}
/**
* Sets the channel handlers bound into the pipelines created for this listener
* @param channelHandlers A map of channel handler bean names keyed by the ordering int of the handlers in the pipeline
*/
public void setChannelHandlers(Map<Integer, String> channelHandlers) {
if(channelHandlers!=null) {
synchronized(this.channelHandlers) {
this.channelHandlers.clear();
this.channelHandlers.putAll(channelHandlers);
}
}
}
/**
* Returns the channel options applied to channels created by this listener
* @return the channel options applied
*/
public Map<String, Object> getChannelOptions() {
return channelOptions;
}
/**
* Returns the channel options in string format
* @return the channel options in string format
*/
@ManagedAttribute(description="The channel options")
public Map<String, String> getChannelOptionNames() {
Map<String, String> map = new HashMap<String, String>(channelOptions.size());
for(Map.Entry<String, Object> entry: channelOptions.entrySet()) {
map.put(entry.getKey(), entry.getValue().toString());
}
return map;
}
/**
* Adds the passed map of channel options to the listener's channel options
* @param options A map of channel options
*/
public void setChannelOptions(Map<String, Object> options) {
if(options!=null) {
channelOptions.putAll(options);
}
}
/**
* Sets the worker pool for this listener
* @param workerPool the workerPool to set
*/
public void setWorkerPool(ExecutorService workerPool) {
this.workerPool = workerPool;
}
/**
* Indicates if a logging handler is installed in the pipeline
* @return true if a logging handler is installed, false otherwise
*/
@ManagedAttribute
public boolean isLoggingHandlerInstalled() {
return loggingHandlerInstalled.get();
}
/**
* Indicates if the main channel is connected
* @return true if the main channel is connected, false otherwise
*/
@ManagedAttribute
public boolean isConnected() {
return connected.get();
}
/**
* Returns the logging handler location
* @return the logging handler location
*/
@ManagedAttribute
public String getLoggingHandlerLocation() {
return loggingHandlerLocation;
}
/**
* Adds a logging handler to the pipeline map if not already present and if the listener is started.
* @param after The name of the handler after which the logger should be added. Adds first in the pipeline if this name is null or empty.
* @param level The level at which the logging handler should log, based on the names in {@link InternalLogLevel}.
* @param hex true if and only if the hex dump of the received message is logged
*/
@ManagedOperation
public void addLoggingHandler(String after, String level, boolean hex) {
if(loggingHandlerInstalled.get()) return;
if(!isStarted()) throw new IllegalStateException("This operation can only be executed once the listener is started", new Throwable());
if(after==null || after.trim().isEmpty()) {
after = "first";
} else {
if(!resolvedHandlers.containsKey(after)) throw new IllegalArgumentException("Invalid handler location [" + after + "]", new Throwable());
}
InternalLogLevel logLevel = InternalLogLevel.valueOf(level.trim().toUpperCase());
String handlerName = getClass().getSimpleName() + "." + beanName + "Logger";
LoggingHandler loggingHandler = new LoggingHandler(handlerName, logLevel, hex);
synchronized(resolvedHandlers) {
LinkedHashMap<String, ChannelHandler> tmp = new LinkedHashMap<String, ChannelHandler>(resolvedHandlers);
resolvedHandlers.clear();
if(after.equals("first")) {
resolvedHandlers.put(handlerName, loggingHandler);
resolvedHandlers.putAll(tmp);
} else {
for(Map.Entry<String, ChannelHandler> entry: tmp.entrySet()) {
String loc = entry.getKey();
resolvedHandlers.put(loc, entry.getValue());
if(after.equals(loc)) {
resolvedHandlers.put(handlerName, loggingHandler);
}
}
}
loggingHandlerInstalled.set(true);
}
}
/**
* Removes the logging handler from the pipeline if it is installed and the listener is started
*/
@ManagedOperation
public void removeLoggingHandler() {
if(!loggingHandlerInstalled.get()) return;
if(!isStarted()) throw new IllegalStateException("This operation can only be executed once the listener is started", new Throwable());
String handlerName = getClass().getSimpleName() + "." + beanName + "Logger";
synchronized(resolvedHandlers) {
resolvedHandlers.remove(handlerName);
loggingHandlerInstalled.set(false);
}
}
/**
* Returns the total number of channels created
* @return the total number of channels created
*/
@ManagedAttribute
public long getChannelsCreated() {
return getMetricValue("channelsCreated");
}
/**
* Returns the total number of channels closed
* @return the total number of channels closed
*/
@ManagedAttribute
public long getChannelsClosed() {
return getMetricValue("channelsClosed");
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.ServerComponent#getSupportedMetricNames()
*/
@Override
public Set<String> getSupportedMetricNames() {
Set<String> metrics = new HashSet<String>(super.getSupportedMetricNames());
metrics.add("channelsCreated");
metrics.add("channelsClosed");
return metrics;
}
// /**
// * Sets the channel group
// * @param channelGroup the channelGroup to set
// */
// @Autowired(required=true)
// public void setChannelGroup(ManagedChannelGroup channelGroup) {
// this.channelGroup = channelGroup;
// }
}