/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2013, 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.destination.subdestination;
import java.net.SocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.helios.apmrouter.OpCode;
import org.helios.apmrouter.collections.ConcurrentLongSortedSet;
import org.helios.apmrouter.destination.BaseDestination;
import org.helios.apmrouter.destination.event.DestinationStartedEvent;
import org.helios.apmrouter.destination.event.DestinationStoppedEvent;
import org.helios.apmrouter.metric.IMetric;
import org.helios.apmrouter.metric.IMetricListener;
import org.helios.apmrouter.server.net.listener.netty.handlers.AbstractAgentRequestHandler;
import org.helios.apmrouter.server.net.listener.netty.handlers.AgentRequestHandler;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
/**
* <p>Title: SubDestinationService</p>
* <p>Description: A service to create/register/unregister/stop sub-destinations on demand.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.destination.subdestination.SubDestinationService</code></p>
*/
public class SubDestinationService extends AbstractAgentRequestHandler implements ChannelFutureListener {
/** A serial number generator for new subscriptions */
protected final AtomicLong subscriberSerial = new AtomicLong(0L);
/** A map of sub-destinations keyed by the sub id */
protected final Map<Long, SubDestination> subDestinations = new ConcurrentHashMap<Long, SubDestination>();
/** A map of longs representing subIds keyed by the subscribed channel ID */
protected final Map<Integer, ConcurrentLongSortedSet> channelSubs = new ConcurrentHashMap<Integer, ConcurrentLongSortedSet>();
/** The OpCodes supported by this {@link AgentRequestHandler} */
protected static final OpCode[] SUPPORTED_OP_CODES = new OpCode[]{OpCode.START_SUB_DEST, OpCode.STOP_SUB_DEST};
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.net.listener.netty.handlers.AgentRequestHandler#processAgentRequest(org.helios.apmrouter.OpCode, org.jboss.netty.buffer.ChannelBuffer, java.net.SocketAddress, org.jboss.netty.channel.Channel)
*/
@Override
public void processAgentRequest(OpCode opCode, ChannelBuffer buff, SocketAddress remoteAddress, Channel channel) {
Channel connectedChannel = getChannelForRemote(channel, remoteAddress);
if(opCode==OpCode.START_SUB_DEST) {
buff.skipBytes(1);
int patternCount = buff.readInt();
String[] patterns = new String[patternCount];
for(int i = 0; i < patternCount; i++) {
int patternLength = buff.readInt();
byte[] patternBytes = new byte[patternLength];
buff.readBytes(patternBytes);
patterns[i] = new String(patternBytes);
}
ConcurrentLongSortedSet subIds = channelSubs.get(connectedChannel.getId());
if(subIds==null) {
synchronized(channelSubs) {
subIds = channelSubs.get(connectedChannel.getId());
if(subIds==null) {
connectedChannel.getCloseFuture().addListener(this);
subIds = new ConcurrentLongSortedSet();
channelSubs.put(connectedChannel.getId(), subIds);
AgentSubDestinationListener agentListener = new AgentSubDestinationListener(connectedChannel, patterns);
startSubDestination(agentListener);
}
}
}
} else if(opCode==OpCode.STOP_SUB_DEST) {
/* Not implemented yet */
} else {
warn("Unsupported OpCode [", opCode, "]");
}
}
/**
* <p>Terminates a channel's subscriptions when it closes.</p>
* {@inheritDoc}
* @see org.jboss.netty.channel.ChannelFutureListener#operationComplete(org.jboss.netty.channel.ChannelFuture)
*/
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Channel ch = future.getChannel();
Integer channelId = ch.getId();
ConcurrentLongSortedSet subIds = channelSubs.remove(channelId);
if(subIds!=null && !subIds.isEmpty()) {
for(int i = 0; i < subIds.size(); i++) {
stopSubDestination(subIds.get(i));
}
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.server.net.listener.netty.handlers.AgentRequestHandler#getHandledOpCodes()
*/
@Override
public OpCode[] getHandledOpCodes() {
return SUPPORTED_OP_CODES;
}
/**
* Creates and registers a subscription for the passed listener
* @param metricListener The metric listener
* @return the subscription id
*/
public long startSubDestination(IMetricListener metricListener) {
if(metricListener==null) return -1L;
final long subId = subscriberSerial.incrementAndGet();
metricListener.setSubscriptionId(subId);
final SubDestination sd = new SubDestination(metricListener);
subDestinations.put(subId, sd);
applicationContext.publishEvent(new DestinationStartedEvent(sd, "SubDestination#" + subId));
info("Started SubDestination [", subId, "]");
return subId;
}
/**
* Stops the sub destination for the passed listener
* @param metricListener The listener to stop the sub for
*/
public void stopSubDestination(IMetricListener metricListener) {
if(metricListener!=null) {
stopSubDestination(metricListener.getSubscriptionId());
}
}
/**
* Stops the sub destination for the listener with the passed id.
* @param subId The subId of the listener to stop the sub for
*/
public void stopSubDestination(long subId) {
final SubDestination sd = subDestinations.remove(subId);
if(sd!=null) {
applicationContext.publishEvent(new DestinationStoppedEvent(sd, "SubDestination#" + subId));
info("Stopped SubDestination [", subId, "]");
} else {
warn("No SubDestination Found to Stop for [", subId, "]");
}
}
/**
* <p>Title: SubDestination</p>
* <p>Description: A sub-destination created and registered when a subscriber subscribes</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.destination.subdestination.SubDestinationService.SubDestination</code></p>
*/
private class SubDestination extends BaseDestination {
/** the listener this sub will forward matrching metrics to */
private final IMetricListener listener;
/**
* Creates a new SubDestination
* @param listener the listener this sub will forward matrching metrics to
*/
protected SubDestination(IMetricListener listener) {
super(listener.getPatterns());
this.listener = listener;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.destination.BaseDestination#doAcceptRoute(org.helios.apmrouter.metric.IMetric)
*/
@Override
protected void doAcceptRoute(IMetric routable) {
listener.onMetric(routable);
}
/**
* Returns this sub's listener
* @return this sub's listener
*/
public IMetricListener getListener() {
return listener;
}
}
}