/**
* 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.jmx.mbeanserver;
import org.helios.apmrouter.OpCode;
import org.helios.apmrouter.jmx.JMXHelper;
import org.helios.apmrouter.jmx.connector.protocol.mxl.MXLocalJMXConnector;
import org.helios.apmrouter.jmx.mbeanserver.proxy.MBeanServerConnectionProxy;
import org.helios.apmrouter.util.SimpleLogger;
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.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import javax.management.*;
import javax.management.remote.JMXConnector;
import java.io.*;
import java.lang.reflect.*;
import java.net.SocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* <p>Title: AgentMBeanServerConnectionFactory</p>
* <p>Description: A utility class and factory to create {@link MBeanServerConnection}s to supporting agents using the custom APMRouter agent protocol</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.jmx.mbeanserver.AgentMBeanServerConnectionFactory</code></p>
*/
public class AgentMBeanServerConnectionFactory implements InvocationHandler, MBeanServerConnectionAdmin {
/** A map of byte op codes keyed by the method represented */
protected static final Map<Method, Byte> methodToKey;
/** A map of methods keyed by the byte op code */
protected static final Map<Byte, Method> keyToMethod;
/** A map of asynch response methods keyed by the byte op code */
protected static final Map<Byte, Method> keyToAsynchMethod;
/** The default request timeout in ms. */
public static long DEFAULT_TIMEOUT = 2000;
/** The timeout map for asynchronous invocations */
protected static final TimeoutQueueMap<Integer, AsynchJMXResponseListener> asynchTimeoutMap = new TimeoutQueueMap<Integer, AsynchJMXResponseListener>(DEFAULT_TIMEOUT);
/** The timeout map for synchronous invocations */
protected static final TimeoutQueueMap<Integer, CountDownLatch> synchTimeoutMap = new TimeoutQueueMap<Integer, CountDownLatch>(DEFAULT_TIMEOUT);
/** The timeout map for synchronous invocation results */
protected static final TimeoutQueueMap<Integer, Object> synchResultMap = new TimeoutQueueMap<Integer, Object>(DEFAULT_TIMEOUT);
/** A map of created MBeanServerConnection instances keyed by the builder key */
protected static final Map<String, MBeanServerConnection> INSTANCES = new ConcurrentHashMap<String, MBeanServerConnection>();
/** A request serial number that allows a response to be matched up with a request */
protected static final AtomicInteger requestId = new AtomicInteger(0);
/** The channel on which the marshalled op is sent */
protected final Channel channel;
/** The remote address where the channel should write to */
protected final SocketAddress remoteAddress;
/** The request timeout in ms. */
protected final long timeout;
/** The asynch request handler */
protected final AsynchJMXResponseListener listener;
/** The jmx domain of the target MBeanServer */
protected final String domain;
/** The byte encoded domain name */
protected final byte[] domainInfoData;
/** The channel handler to capture and process JMX responses */
protected final MBeanServerConnectionInvocationResponseHandler responseHandler = new MBeanServerConnectionInvocationResponseHandler(this);
/** A map of listener registration request Ids keyed by the registered notification that was registered */
protected final Map<Integer, NotificationListener> registeredListeners = new ConcurrentHashMap<Integer, NotificationListener>();
/**
* Creates a new MBeanServerConnection builder
* @param channel The channel that JMX ops are issued through
* @return an MBeanServerConnection builder
*/
public static Builder builder(Channel channel) {
return new Builder(channel);
}
/**
* Quickie init test of the static initializer and print out of asynch response mappings
* @param args none
*/
public static void main(String[] args) {
SimpleLogger.info("Initialized");
for(Map.Entry<Byte, Method> entry: keyToMethod.entrySet()) {
Method am = keyToAsynchMethod.get(entry.getKey());
SimpleLogger.info("\t", entry.getValue().getName(), " --> ", am.getName());
}
StringBuilder b = new StringBuilder("Mappings");
for(Map.Entry<Byte, Method> entry: keyToMethod.entrySet()) {
b.append("\n\t[").append(entry.getKey()).append("]").append(" --").append(entry.getValue().getName()).append("-- ").append("[").append(methodToKey.get(entry.getValue())).append("]");
}
SimpleLogger.info(b);
}
static {
try {
Map<String, Method> orderedMethods = new TreeMap<String, Method>();
for(Method m: MBeanServerConnection.class.getDeclaredMethods()) {
orderedMethods.put(m.toGenericString(), m);
}
Method[] methods = orderedMethods.values().toArray(new Method[orderedMethods.size()]);
Map<String, Method> asynchMethods = new TreeMap<String, Method>();
for(Method asynchMethod: AsynchJMXResponseListener.class.getDeclaredMethods()) {
asynchMethods.put(asynchMethod.getName(), asynchMethod);
}
Map<Method, Byte> m2k = new HashMap<Method, Byte>(methods.length);
Map<Byte, Method> k2m = new HashMap<Byte, Method>(methods.length);
Map<Byte, Method> k2am = new HashMap<Byte, Method>(methods.length);
for(int i = 0; i < methods.length; i++) {
m2k.put(methods[i], (byte)i);
k2m.put((byte)i, methods[i]);
Method asynchMethod = asynchMethods.get(methods[i].getName() + "Response");
if(asynchMethod==null) throw new RuntimeException("Failed to find asynch handler for [" + methods[i].toGenericString() + "]", new Throwable());
k2am.put((byte)i, asynchMethod);
}
methodToKey = Collections.unmodifiableMap(m2k);
keyToMethod = Collections.unmodifiableMap(k2m);
keyToAsynchMethod = Collections.unmodifiableMap(k2am);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* <p>Title: Builder</p>
* <p>Description: A fluent style builder for channel based MBeanServerConnections.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.jmx.mbeanserver.AgentMBeanServerConnectionFactory.Builder</code></p>
*/
public static class Builder {
/** The channel on which the marshalled op is sent */
private final Channel channel;
/** The remote address where the channel should write to */
private SocketAddress remoteAddress = null;
/** The request timeout in ms. */
private long timeout = DEFAULT_TIMEOUT;
/** The asynch request handler */
private AsynchJMXResponseListener listener = null;
/** The name of the default JMX domain for the target MBeanServer */
private String domain = "DefaultDomain";
/**
* Builds the key that uniquely identifies the MBeanServerConnection that would be created by this builder
* @return the unique MBeanServerConnection key
*/
private String buildKey() {
StringBuilder b = new StringBuilder();
b.append(channel.getId());
b.append(domain);
if(remoteAddress!=null) {
b.append(remoteAddress.toString());
}
return b.toString();
}
/**
* Creates a new Builder
* @param channel The connector channel
*/
private Builder(Channel channel) {
this.channel = channel;
}
/**
* Builds a new AgentMBeanServerConnectionFactory and returns the underlying MBeanServerConnection
* @return a new MBeanServerConnection
*/
public MBeanServerConnection build() {
final String key = buildKey();
MBeanServerConnection conn = INSTANCES.get(key);
if(conn==null) {
synchronized(INSTANCES) {
conn = INSTANCES.get(key);
if(conn==null) {
conn = (MBeanServerConnection)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{MBeanServerConnection.class}, new AgentMBeanServerConnectionFactory(this));
INSTANCES.put(key, conn);
channel.getCloseFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
INSTANCES.remove(key);
}
});
}
}
}
return conn;
}
/**
* Sets the builder's remote address which overrides the channel's
* @param remoteAddress the remoteAddress to set
* @return this builder
*/
public Builder remoteAddress(SocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
return this;
}
/**
* Sets the JMX operation timeout in ms
* @param timeout the timeout to set
* @return this builder
*/
public Builder timeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* Sets the asynchronous response listener for the JMX connection
* @param listener the listener to set
* @return this builder
*/
public Builder listener(AsynchJMXResponseListener listener) {
this.listener = listener;
return this;
}
/**
* Sets the JMX domain of the target MBeanServer for the JMX connection
* @param domain the domain to set
* @return this builder
*/
public Builder domain(String domain) {
this.domain = domain;
return this;
}
}
/**
* Creates a new AgentMBeanServerConnectionFactory
* @param builder The AgentMBeanServerConnectionFactory builder
*/
protected AgentMBeanServerConnectionFactory(Builder builder) {
this.channel = builder.channel;
this.remoteAddress = builder.remoteAddress==null ? this.channel.getRemoteAddress() : builder.remoteAddress;
this.timeout = builder.timeout;
this.listener = builder.listener;
this.domain = builder.domain;
if("DefaultDomain".equals(domain)) {
domainInfoData = new byte[]{0};
} else {
byte[] domainBytes = domain.getBytes();
domainInfoData = new byte[domainBytes.length + 1];
domainInfoData[0] = (byte) domainBytes.length;
System.arraycopy(domainBytes, 0, domainInfoData, 1, domainBytes.length);
}
if(channel.getPipeline().get(getClass().getSimpleName())==null) {
this.channel.getPipeline().addFirst(getClass().getSimpleName(), responseHandler);
// LoggingHandler logg = new LoggingHandler(InternalLogLevel.ERROR, true);
// this.channel.getPipeline().addFirst("logger", logg);
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#closeMBeanServerConnection()
*/
@Override
public void closeMBeanServerConnection() {
channel.getPipeline().remove(getClass().getSimpleName());
if(listener!=null) {
listener.onClose();
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#waitForSynchronousResponse(int, long)
*/
@Override
public void waitForSynchronousResponse(int requestId, long timeout) {
CountDownLatch latch = new CountDownLatch(1);
synchTimeoutMap.put(requestId, latch);
try {
SimpleLogger.debug("Waiting for response to [", requestId, "]....");
boolean timedout = latch.await(timeout, TimeUnit.MILLISECONDS);
SimpleLogger.debug("Result of Waiting for response to [", requestId, "]:", timedout);
} catch (InterruptedException iex) {
synchResultMap.putIfAbsent(requestId, new IOException("Thread was interrupted while waiting on Operation completion", new Throwable()));
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#onSynchronousResponse(int, java.lang.Object)
*/
@Override
public void onSynchronousResponse(int requestId, Object value) {
SimpleLogger.debug("Response for req [", requestId, "]:[", value, "]");
synchResultMap.putIfAbsent(requestId, value);
CountDownLatch latch = synchTimeoutMap.remove(requestId);
if(latch!=null) {
latch.countDown();
SimpleLogger.debug("Counted Down Latch for req [", requestId, "]:[", value, "]");
}
}
/**
* {@inheritDoc}
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(MBeanServerConnection.class!=method.getDeclaringClass()) {
return method.invoke(Modifier.isStatic(method.getModifiers()) ? null : this, args);
}
if(channel.getPipeline().get(getClass().getSimpleName())==null) {
throw new IOException("This MBeanServerConnection has been closed", new Throwable());
}
//SimpleLogger.debug("MBeanServerConnection [", method.getName(), "] Payload Size [", sargs.length+6+4, "]");
final int reqId = requestId.incrementAndGet();
if("addNotificationListener".equals(method.getName()) && !method.getParameterTypes()[1].equals(ObjectName.class)) {
NotificationListener listener = (NotificationListener)args[1];
args[1] = reqId;
addRegisteredListener(reqId, listener);
} else if("removeNotificationListener".equals(method.getName()) && !method.getParameterTypes()[1].equals(ObjectName.class)) {
removeRegisteredListener((NotificationListener)args[1]);
args = new Object[0];
}
byte[] sargs = getOutput(args);
ChannelBuffer cb = ChannelBuffers.directBuffer(1 + domainInfoData.length + 4 +1 + 4 + sargs.length);
cb.writeByte(OpCode.JMX_REQUEST.op()); // 1
cb.writeBytes(domainInfoData); // domain data
cb.writeInt(reqId); // 4
cb.writeByte(methodToKey.get(method)); // 1
cb.writeInt(sargs.length); // 4
cb.writeBytes(sargs); // sargs.length
if(listener==null) {
synchTimeoutMap.addListener(new TimeoutListener<Integer, CountDownLatch>() {
@Override
public void onTimeout(Integer key, CountDownLatch value) {
if(reqId == key) {
synchTimeoutMap.remove(key);
synchTimeoutMap.removeListener(this);
onSynchronousResponse(reqId, new IOException("Operation timed out after [" + timeout + "] ms.", new Throwable()));
}
}
});
} else {
asynchTimeoutMap.put(reqId, listener, timeout);
asynchTimeoutMap.addListener(new TimeoutListener<Integer, AsynchJMXResponseListener>() {
@Override
public void onTimeout(Integer key, AsynchJMXResponseListener value) {
if(reqId == key) {
asynchTimeoutMap.remove(key);
listener.onTimeout(reqId, timeout);
asynchTimeoutMap.removeListener(this);
}
}
});
}
channel.write(cb, remoteAddress).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
SimpleLogger.debug("Sent JMX Request to [", remoteAddress, "]");
} else {
SimpleLogger.error("Failed to send JMX Request to [", remoteAddress, "]", future.getCause());
}
}
});
if(listener==null) {
waitForSynchronousResponse(reqId, timeout);
Object result = synchResultMap.get(reqId);
if(result!=null && result instanceof Throwable) {
throw (Throwable)result;
}
return result;
}
return null;
}
/**
* Returns a string array containing the default JMX domains of all available MBeanServers in this JVM.
* @return a string array of JMX default domains
*/
public static String[] getMBeanServerDomains() {
Set<String> domains = new HashSet<String>();
for(MBeanServer mbs: MBeanServerFactory.findMBeanServer(null)) {
String domain = mbs.getDefaultDomain();
if(domain==null) domain = "DefaultDomain";
domains.add(domain);
}
return domains.toArray(new String[domains.size()]);
}
/**
* Sends the available MBeanServer domains in this JVM through the passed channel to the passed remote address in response to a {@link OpCode#JMX_MBS_INQUIRY} request.
* @param channel The channel to write to
* @param remoteAddress The remote address to send to
*/
public static void sendMBeanServerDomains(Channel channel, SocketAddress remoteAddress) {
String[] domains = getMBeanServerDomains();
int size = 4 + (domains.length*4) + 1;
for(String s: domains) {
size += s.getBytes().length;
}
ChannelBuffer cb = ChannelBuffers.directBuffer(size);
cb.writeByte(OpCode.JMX_MBS_INQUIRY_RESPONSE.op());
cb.writeInt(domains.length);
for(String s: domains) {
byte[] bytes = s.getBytes();
cb.writeInt(bytes.length);
cb.writeBytes(bytes);
}
SimpleLogger.info("Sending MBeanServer Domain List ", Arrays.toString(domains));
channel.write(cb, remoteAddress);
}
/**
* Creates and registers a remote MBeanServerProxy MBean for each of the passed MBeanServer default domains passed
* @param channel The underlying comm channel
* @param remoteAddress The remote address of the agent
* @param host The host of the agent
* @param agent The agent name
* @param protocol The protocol name
* @param jmxDomains An array of MBeanServer default domains to register MBeanServerProxys for
*/
public static void registerRemoteMBeanServerConnections(Channel channel, SocketAddress remoteAddress, String host, String agent, String protocol, String...jmxDomains) {
for(String domain: jmxDomains) {
try {
JMXConnector connector = new MXLocalJMXConnector(host, agent, protocol, domain);
MBeanServerConnectionProxy proxy = new MBeanServerConnectionProxy(connector, host, agent, protocol, domain);
} catch (Exception ex) {
SimpleLogger.error("Failed to register MBeanServerConnection Proxy for:\n\tHost:", host, "\n\tAgent:", agent, "\n\tDomain:", domain, "\n\tRemoteAddress:", remoteAddress, ex);
}
}
}
/**
* Writes a notification back to the originating listener registrar
* @param channel The channel to write on
* @param remoteAddress The remote address to write to
* @param requestId The request ID of the original listener registration
* @param notification The notification to write
* @param handback The optional contextual handback
*/
public static void writeNotification(Channel channel, SocketAddress remoteAddress, int requestId, Notification notification, Object handback) {
byte[] payload = getOutput(notification, handback);
int size = payload.length + 1 + 4 + 4; // size is <payload size> + <OpCode> + <requestId> + <<payload length>
ChannelBuffer cb = ChannelBuffers.directBuffer(size);
cb.writeByte(OpCode.JMX_NOTIFICATION.op());
cb.writeInt(requestId);
cb.writeInt(payload.length);
cb.writeBytes(payload);
channel.write(cb, remoteAddress);
SimpleLogger.info("Wrote JMX Notification [", size, "] bytes");
}
/**
* Handles a received {@link MBeanServerConnection} invocation
* @param channel The channel the request was received on
* @param remoteAddress The remote address of the caller
* @param buffer THe buffer received
*
*/
public static void handleJMXRequest(Channel channel, SocketAddress remoteAddress, ChannelBuffer buffer) {
buffer.resetReaderIndex();
/* The request write */
// cb.writeByte(OpCode.JMX_REQUEST.op()); // 1
// cb.writeBytes(domainInfoData); // domain data
// cb.writeInt(reqId); // 4
// cb.writeByte(methodToKey.get(method)); // 1
// cb.writeInt(sargs.length); // 4
// cb.writeBytes(sargs); // sargs.length
Object result = null;
MBeanServerConnection server = null;
buffer.skipBytes(1);
byte domainIndicator = buffer.readByte();
if(domainIndicator==0) {
server = JMXHelper.getHeliosMBeanServer();
} else {
byte[] domainBytes = new byte[domainIndicator];
buffer.readBytes(domainBytes);
String domain = new String(domainBytes);
server = JMXHelper.getLocalMBeanServer(true, domain);
if(server==null) {
result = new SmallException("Failed to locate MBeanServer for domain [" + domain + "]");
}
}
int reqId = buffer.readInt();
byte methodId = buffer.readByte();
if(result==null) {
int payloadSize = buffer.readInt();
byte[] payload = new byte[payloadSize];
buffer.readBytes(payload);
Object[] params = getInput(payload);
Method targetMethod = null;
try {
targetMethod = keyToMethod.get(methodId);
if(targetMethod==null) {
result = new SmallException("Failed to handle MBeanServerConnection invocation because method Op Code [" + methodId + "] was not recognized");
} else {
if("addNotificationListener".equals(targetMethod.getName()) && !targetMethod.getParameterTypes()[1].equals(ObjectName.class)) {
} else if("removeNotificationListener".equals(targetMethod.getName()) && !targetMethod.getParameterTypes()[1].equals(ObjectName.class)) {
} else {
result = targetMethod.invoke(server, params);
}
}
} catch (Throwable t) {
SimpleLogger.warn("Failed to invoke [", targetMethod, "]", t);
result = new SmallException(t.toString());
}
}
writeJMXResponse(reqId, methodId, channel, remoteAddress, result);
}
/**
* <p>Title: SmallThrowable</p>
* <p>Description: Extension of {@link Exception} that minimizes it's size by removing the stack trace.</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.jmx.mbeanserver.AgentMBeanServerConnectionFactory.SmallException</code></p>
*/
public static class SmallException extends Exception {
/** */
private static final long serialVersionUID = -6927976563869115032L;
/**
* Creates a new SmallThrowable
* @param message the error message
*/
public SmallException(String message) {
super(message);
setStackTrace(new StackTraceElement[0]);
}
}
/**
* Writes a JMX invocation response back to the caller
* @param requestId The original request id
* @param methodId The {@link MBeanServerConnection} method ID byte
* @param channel The channel to write to
* @param remoteAddress The remote address that the channel will write to
* @param response AN array of object responses
*/
public static void writeJMXResponse(int requestId, byte methodId, Channel channel, SocketAddress remoteAddress, Object...response) {
byte[] payload = getOutput(response);
int size = payload.length + 1 + 4 + 1 + 4; // size is <payload size> + <OpCode> + <requestId> + <method ID byte> + <payload length>
ChannelBuffer cb = ChannelBuffers.directBuffer(size);
cb.writeByte(OpCode.JMX_RESPONSE.op());
cb.writeInt(requestId);
cb.writeByte(methodId);
cb.writeInt(payload.length);
cb.writeBytes(payload);
channel.write(cb, remoteAddress);
SimpleLogger.info("Wrote JMX Response [", size, "] bytes");
}
/**
* Deserializes an array of arguments from a byte array
* @param bytes The byte array containing the serialized arguments
* @return an object array
*/
public static Object[] getInput(byte[] bytes) {
if(bytes.length==0 || (bytes.length==1 && bytes[0]==0)) return new Object[0];
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = null;
GZIPInputStream gzis = null;
Object[] args = null;
try {
gzis = new GZIPInputStream(bais);
ois = new ObjectInputStream(gzis);
Object obj = ois.readObject();
if(obj.getClass().isArray()) {
args = new Object[Array.getLength(obj)];
System.arraycopy(obj, 0, args, 0, args.length);
} else {
args = new Object[]{obj};
}
return args;
} catch (Exception ex) {
throw new RuntimeException("Failed to decode MBeanServerConnection Invocation arguments", ex);
} finally {
try { bais.close(); } catch (Exception ex) {/* No Op */}
if(ois!=null) try { ois.close(); } catch (Exception ex) {/* No Op */}
if(gzis!=null) try { gzis.close(); } catch (Exception ex) {/* No Op */}
}
}
// /**
// * Serializes an attribute list to a byte array.
// * Handled differently from {@link #getOutput(Object...)} in that it discards attributes that fail to serialize.
// * Consequently, it requires a custom {@link #getInput(byte[])}
// * @param attrList The attribute list to serialize
// * @return a byte array
// */
// public static byte[] getOutput(AttributeList attrList) {
// if(attrList==null || attrList.size()==0) return new byte[]{};
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// GZIPOutputStream gzos = null;
// try {
// int writes = 0;
// gzos= new GZIPOutputStream(baos);
// for(Attribute attr: attrList.asList()) {
// byte[] attrBytes = serializeNoCompress(attr);
// if(attrBytes.length>0) {
// gzos.write(attrBytes);
// writes++;
// }
// }
// gzos.finish();
// gzos.flush();
// baos.flush();
// byte[] payload = baos.toByteArray();
// byte[] sizedPayload = new byte[payload.length+1];
// byte[] writeBytes = ByteBuffer.allocate(4).putInt(writes).array();
// System.arraycopy(writeBytes, 0, sizedPayload, 0, 4);
// System.arraycopy(payload, 0, sizedPayload, 4, sizedPayload.length);
// return sizedPayload;
// } catch (Exception ex) {
// //throw new RuntimeException("Failed to encode MBeanServerConnection Invocation arguments " + Arrays.toString(args), ex);
// return new byte[0];
// } finally {
// try { baos.close(); } catch (Exception ex) {/* No Op */}
// if(gzos!=null) try { gzos.close(); } catch (Exception ex) {/* No Op */}
// }
// }
//
// public static AttributeList getAttributeListInput(byte[] bytes) {
//
// }
/**
* Serializes the passed object to a byte array, returning a zero byte array if the passed object is null, or fails serialization
* @param arg The object to serialize
* @return the serialized object
*/
public static byte[] serializeNoCompress(Object arg) {
if(arg==null) return new byte[]{};
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(baos);
oos.writeObject(arg);
oos.flush();
baos.flush();
return baos.toByteArray();
} catch (Exception ex) {
return new byte[]{};
} finally {
try { baos.close(); } catch (Exception ex) {/* No Op */}
if(oos!=null) try { oos.close(); } catch (Exception ex) {/* No Op */}
}
}
/**
* Serializes an array of invocation arguments to a byte array
* @param args The arguments to marshall
* @return a byte array
*/
public static byte[] getOutput(Object...args) {
if(args==null || args.length==0) return new byte[]{};
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzos = null;
ObjectOutputStream oos = null;
try {
gzos= new GZIPOutputStream(baos);
oos = new ObjectOutputStream(gzos);
oos.writeObject(args);
oos.flush();
gzos.finish();
gzos.flush();
baos.flush();
return baos.toByteArray();
} catch (Exception ex) {
throw new RuntimeException("Failed to encode MBeanServerConnection Invocation arguments " + Arrays.toString(args), ex);
} finally {
try { baos.close(); } catch (Exception ex) {/* No Op */}
if(oos!=null) try { oos.close(); } catch (Exception ex) {/* No Op */}
if(gzos!=null) try { gzos.close(); } catch (Exception ex) {/* No Op */}
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#addRegisteredListener(int, javax.management.NotificationListener)
*/
@Override
public void addRegisteredListener(int requestId, NotificationListener listener) {
registeredListeners.put(requestId, listener);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#removeRegisteredListener(javax.management.NotificationListener)
*/
@Override
public void removeRegisteredListener(NotificationListener listener) {
if(listener!=null) {
registeredListeners.remove(listener);
}
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.jmx.mbeanserver.MBeanServerConnectionAdmin#onNotification(int, javax.management.Notification, java.lang.Object)
*/
@Override
public void onNotification(int requestId, Notification notification, Object handback) {
NotificationListener listener = registeredListeners.get(requestId);
if(listener!=null) {
listener.handleNotification(notification, handback);
}
}
}