/**
* 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.dataservice.json;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.apmrouter.server.ServerComponentBean;
import org.helios.apmrouter.util.StringHelper;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* <p>Title: WSInvokeTestJSONDataService</p>
* <p>Description: A testing service providing various different wsinvoke invocation endpoints.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.dataservice.json.WSInvokeTestJSONDataService</code></p>
*/
@JSONRequestHandler(name="wsinvoke")
public class WSInvokeTestJSONDataService extends ServerComponentBean {
/** The scheduler to generate subscriber events */
protected final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, new ThreadFactory(){
protected final AtomicInteger serial = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "WSInvokeTestJSONDataServiceSubThread#" + serial.incrementAndGet());
t.setDaemon(true);
return t;
}
});
/** A map of subscriptions keyed by the channel ID */
protected final Map<Integer, Subscription> subscriptions = new ConcurrentHashMap<Integer, Subscription>();
/** A map of subscriptions keyed by the rid */
protected final Map<Integer, Subscription> subscriptionsByRid = new ConcurrentHashMap<Integer, Subscription>();
private class Subscription implements ChannelFutureListener, Runnable, NotificationListener, NotificationFilter {
/** The original JsonRequest for this sub */
private final JsonRequest jsonRequest;
/** The ObjectName subscribed to */
private final ObjectName objectName;
/** The schedule handle */
private final ScheduledFuture<?> schedule;
/** The channel owning the subscription */
private final Channel channel;
/** the names of the atributes we'll be retrieving for each matching ObjectName */
private final Map<ObjectName, String[]> attributeNames;
private long sentMessages = 0;
/**
* Creates a new Subscription
* @param jsonRequest The original JsonRequest for this sub
* @param objectName The ObjectName subscribed to
* @param channel The channel to write events to
* @param frequency the frequency of the JMX poll in ms.
*/
public Subscription(JsonRequest jsonRequest, ObjectName objectName, Channel channel, long frequency) {
this.jsonRequest = jsonRequest;
this.objectName = objectName;
this.channel = channel;
this.channel.getCloseFuture().addListener(this);
Set<ObjectName> matches = JMXHelper.getHeliosMBeanServer().queryNames(objectName, null);
attributeNames = new HashMap<ObjectName, String[]>(matches.size());
for(ObjectName on: matches) {
try {
Set<String> numericAttrs = new HashSet<String>();
for(Map.Entry<String, Object> attr : JMXHelper.getAttributes(on).entrySet()) {
if(attr.getValue()!=null && Number.class.isInstance(attr.getValue())) {
numericAttrs.add(attr.getKey());
}
}
info("Will poll [", on, "] for these attributes:", numericAttrs.toString().replace("[", "[\n\t").replace("]", "\n]").replace(",", "\n\t"));
attributeNames.put(on, numericAttrs.toArray(new String[numericAttrs.size()]));
} catch (Exception ex) {}
}
this.schedule = scheduler.scheduleWithFixedDelay(this, frequency, frequency, TimeUnit.MILLISECONDS);
subscriptions.put(channel.getId(), this);
}
/**
* {@inheritDoc}
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
try {
Map<String, Object> map = new HashMap<String, Object>();
map.put("subkey", objectName.toString());
for(Map.Entry<ObjectName, String[]> entry: attributeNames.entrySet()) {
Map<String, Object> onmap = JMXHelper.getAttributes(entry.getKey(), entry.getValue());
map.put(entry.getKey().toString(), onmap);
}
jsonRequest.subResponse(objectName.toString()).setContent(map).send(channel);
sentMessages++;
} catch (Exception ex) {
error("Subscription for channel [", channel, "] on ObjectName [", objectName, "] encountered error ", ex);
jsonRequest.error(StringHelper.fastConcat("Subscription for channel [", channel.toString(), "] and ON [", objectName.toString(), "] encountered error "), ex).send(channel);
}
}
/**
* Cancels this subscription and deallocates any associated resources
* @param internal true if this cancel is being called internally (i.e. triggered by the channel closes
*/
public void cancel(boolean internal) {
schedule.cancel(true);
if(internal) {
subscriptions.remove(channel.getId());
}
}
/**
* <p>Cancels this subscription.</p>
* {@inheritDoc}
* @see org.jboss.netty.channel.ChannelFutureListener#operationComplete(org.jboss.netty.channel.ChannelFuture)
*/
@Override
public void operationComplete(ChannelFuture future) throws Exception {
info("Cancelling Subscription " , this);
cancel(true);
}
/**
* {@inheritDoc}
* @see javax.management.NotificationFilter#isNotificationEnabled(javax.management.Notification)
*/
@Override
public boolean isNotificationEnabled(Notification notification) {
// TODO Auto-generated method stub
return false;
}
/**
* {@inheritDoc}
* @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
*/
@Override
public void handleNotification(Notification notification,
Object handback) {
// TODO Auto-generated method stub
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format(
"Subscription [\\n\\tobjectName:%s, channel:%s]",
objectName, channel);
}
}
/**
* Simplified event subscription endpoint. The request contains a <b><code>java.lang</code></b> domain MXBean
* and a frequency of updates in ms. When the caller is subscribed, a JSON dump of all the named MBean's attributes
* are published to the client on the specified frequency.
* @param request The subscribe request
* @param channel The channel to write the subscribe confirm and subsequent events.
*/
@JSONRequestHandler(name="subscribe")
public void subscribeEvents(final JsonRequest request, final Channel channel) {
info("Processing Subscription Request:", request);
try {
final String objName = request.getArgument("objectname");
final long frequency = request.getArgument("freq", 10000L);
new Subscription(request, JMXHelper.objectName(objName), channel, frequency);
ChannelFutureListener completionListener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
info("SubReg Future:\n\tIsCancelled:", future.isCancelled(),
"\n\tIsDone:", future.isDone(),
"\n\tIsSuccess:", future.isSuccess(),
"\n\tChannel:", future.getChannel(),
"\n\tCause:", future.getCause()
);
if(!future.isDone()) {
info("Subscription Request Incomplete");
}
if(future.isSuccess()) {
info("Subscription Request Complete:", request);
} else {
request.error(StringHelper.fastConcat("Subscription request for channel [", channel.toString(), "] on ObjectName [", objectName.toString(), "] failed"), future.getCause()).send(channel);
error("Failed to send ", future.getCause());
}
}
};
request.subConfirm(objName).send(completionListener, channel);
} catch (Exception ex) {
error("Failed to initiate subscription with [", request, "] from channel [", channel, "] ", ex);
request.error(StringHelper.fastConcat("Subscription request for channel [", channel.toString(), "] on ObjectName [", objectName.toString(), "] failed"), ex).send(channel);
}
}
/**
* Client diagnostic helper. Echos the passed content in <b>args</b> keyed by <b>msg</b>.
* If a numeric arg is passed keyed by <b>sleep</b>, the service will sleep that number of ms. before responding.
* @param request The JSON request
* @param channel The channel to write the response to
*/
@JSONRequestHandler(name="echo")
public void echo(final JsonRequest request, final Channel channel) {
final String message = request.getArgument("msg");
final long sleep = getSleep(request.getArgument("sleep"));
channel.getPipeline().execute(new Runnable(){
@Override
public void run() {
info("Echo Request [", message, "] sleeping for [", sleep, "] ms.");
try { Thread.currentThread().join(sleep); } catch (Exception es) {}
try {
channel.write(request.response().setContent(message)).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
info("Returned response for Echo Request [", message, "] sleeping for [", sleep, "] ms.");
} else {
error("Failed to Echo Request [", message, "] sleeping for [", sleep, "] ms. ", future.getCause());
}
}
});
} catch (Exception ex) {
error("Failed to send response on echo request ", ex);
}
}
});
}
private long getSleep(String sleep) {
try {
return Math.abs(Long.parseLong(sleep.trim()));
} catch (Exception ex) {
return 0;
}
}
}