com.jbrisbin.vcloud.mbean.CloudInvokerListener.java Source code

Java tutorial

Introduction

Here is the source code for com.jbrisbin.vcloud.mbean.CloudInvokerListener.java

Source

/*
 * Copyright (c) 2010 by J. Brisbin <jon@jbrisbin.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.jbrisbin.vcloud.mbean;

import com.rabbitmq.client.*;
import org.apache.catalina.*;
import org.apache.catalina.mbeans.MBeanUtils;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Provide a consumer and listener/handlers for invoking JMX operations via RabbitMQ messages.
 *
 * @author J. Brisbin <jon@jbrisbin.com>
 */
@SuppressWarnings({ "unchecked" })
public class CloudInvokerListener implements ContainerListener, LifecycleListener {

    protected Log log = LogFactory.getLog(getClass());
    protected boolean DEBUG = log.isDebugEnabled();
    /**
     * Comma-delimited list of JMX domains to expose via messaging.
     */
    protected String exposeDomains = "Catalina";
    /**
     * Hostname of the RabbitMQ server we want to connect to.
     */
    protected String mqHost = "localhost";
    /**
     * RabbitMQ server's port number. Useful to set if running SSL or on a non-standard port for security reasons.
     */
    protected int mqPort = 5672;
    /**
     * A valid RabbitMQ user that has enough permissions to create Exchanges and Queues on the server.
     */
    protected String mqUser = "guest";
    /**
     * Plain-text password for the above user. It would be better to accept an encrypted password, but that's not a number
     * one priority, so I'll save that for later.
     */
    protected String mqPassword = "guest";
    /**
     * RabbitMQ virtual host to create our exchanges in. An entirely new "cluster" can be created by simply connecting to
     * a different virtual host.
     */
    protected String mqVirtualHost = "/";
    protected String instanceName = System.getenv("HOSTNAME");
    protected String eventsExchange = "amq.fanout";
    //protected String eventsQueue = "events";
    protected String mbeanEventsExchange = "amq.topic";
    protected String mbeanEventsQueue = "events.mbean";
    protected String mbeanEventsRoutingKey = "#";
    protected Connection connection;
    protected Channel channel;
    protected ExecutorService workerPool = Executors.newCachedThreadPool();

    protected MBeanServer mbeanServer;

    public String getExposeDomains() {
        return exposeDomains;
    }

    public void setExposeDomains(String exposeDomains) {
        this.exposeDomains = exposeDomains;
    }

    public String getMqHost() {
        return mqHost;
    }

    public void setMqHost(String mqHost) {
        this.mqHost = mqHost;
    }

    public int getMqPort() {
        return mqPort;
    }

    public void setMqPort(int mqPort) {
        this.mqPort = mqPort;
    }

    public String getMqUser() {
        return mqUser;
    }

    public void setMqUser(String mqUser) {
        this.mqUser = mqUser;
    }

    public String getMqPassword() {
        return mqPassword;
    }

    public void setMqPassword(String mqPassword) {
        this.mqPassword = mqPassword;
    }

    public String getMqVirtualHost() {
        return mqVirtualHost;
    }

    public void setMqVirtualHost(String mqVirtualHost) {
        this.mqVirtualHost = mqVirtualHost;
    }

    public String getInstanceName() {
        return instanceName;
    }

    public void setInstanceName(String instanceName) {
        this.instanceName = instanceName;
    }

    public String getEventsExchange() {
        return eventsExchange;
    }

    public void setEventsExchange(String eventsExchange) {
        this.eventsExchange = eventsExchange;
    }
    /*
      public String getEventsQueue() {
        return eventsQueue;
      }
        
      public void setEventsQueue(String eventsQueue) {
        this.eventsQueue = eventsQueue;
      }
    */

    public String getMbeanEventsExchange() {
        return mbeanEventsExchange;
    }

    public void setMbeanEventsExchange(String mbeanEventsExchange) {
        this.mbeanEventsExchange = mbeanEventsExchange;
    }

    public String getMbeanEventsQueue() {
        return mbeanEventsQueue;
    }

    public void setMbeanEventsQueue(String mbeanEventsQueue) {
        this.mbeanEventsQueue = mbeanEventsQueue;
    }

    public String getMbeanEventsRoutingKey() {
        return mbeanEventsRoutingKey;
    }

    public void setMbeanEventsRoutingKey(String mbeanEventsRoutingKey) {
        this.mbeanEventsRoutingKey = mbeanEventsRoutingKey;
    }

    public void containerEvent(ContainerEvent event) {
        log.info("Type: " + event.getType().toString());
        try {
            log.info("Data: " + event.getData().toString());
        } catch (Throwable t) {
        }
    }

    public void lifecycleEvent(LifecycleEvent event) {
        if (null == mbeanServer) {
            mbeanServer = MBeanUtils.createServer();
        }
        try {
            getChannel();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return;
        }

        // Let cloud know about this Lifecycle event
        AMQP.BasicProperties props = new AMQP.BasicProperties();
        try {
            if (DEBUG) {
                log.debug("Attempting to notify cloud of " + event.getType() + " event...");
            }
            channel.basicPublish(eventsExchange, event.getType() + "." + instanceName, props,
                    event.getType().getBytes());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }

        if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
            workerPool.submit(new EventsHandler(mbeanEventsQueue));
        } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {
            try {
                if (DEBUG) {
                    log.debug("Closing RabbitMQ channel...");
                }
                synchronized (channel) {
                    channel.close();
                }
            } catch (IOException e) {
            }
            try {
                if (DEBUG) {
                    log.debug("Closing RabbitMQ connection...");
                }
                connection.close();
            } catch (IOException e) {
            }
        }

    }

    protected class EventsHandler implements Callable<EventsHandler> {
        protected String queue;
        protected QueueingConsumer consumer;
        protected Channel channel;
        protected boolean active = true;

        public EventsHandler(String queue) {
            this.queue = queue;
            try {
                this.channel = connection.createChannel();
                consumer = new QueueingConsumer(channel);
                if (DEBUG) {
                    log.debug("Consuming events on q: " + queue);
                }
                channel.basicConsume(queue, true, consumer);
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }

        public boolean isActive() {
            return active;
        }

        public synchronized void setActive(boolean active) {
            this.active = active;
        }

        public EventsHandler call() throws Exception {
            while (active) {
                if (DEBUG) {
                    log.debug("Waiting for delivery...");
                }
                QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                if (DEBUG) {
                    log.debug("Processing delivery: " + delivery.toString());
                }

                CloudMBeanInvoker invoker = new StandardMBeanInvoker();
                invoker.setReplyTo(delivery.getProperties().getReplyTo());
                invoker.setCorrelationId(delivery.getProperties().getCorrelationId());
                ObjectMapper omapper = new ObjectMapper();
                Map<String, Object> request = omapper
                        .readValue(new ByteArrayInputStream(delivery.getBody().clone()), Map.class);
                if (DEBUG) {
                    log.debug("Request: " + request.toString());
                }
                if (request.containsKey("mbean")) {
                    invoker.setName(request.get("mbean").toString());
                }
                if (request.containsKey("attribute")) {
                    invoker.setAttributeName(request.get("attribute").toString());
                }
                if (request.containsKey("operation")) {
                    invoker.setOperation(request.get("operation").toString());
                    if (request.containsKey("parameters")) {
                        Object o = request.get("parameters");
                        if (DEBUG) {
                            log.debug("params: " + String.valueOf(o));
                        }
                        if (null != o && o instanceof List) {
                            List<List<Object>> params = (List<List<Object>>) o;
                            String[] argTypes = new String[params.size()];
                            Object[] args = new Object[params.size()];
                            for (int i = 0; i < params.size(); i++) {
                                List<Object> param = params.get(i);
                                argTypes[i] = param.get(0).toString();
                                args[i] = param.get(1);
                            }
                            invoker.setArgs(args);
                            invoker.setArgTypes(argTypes);
                        }
                    }
                }

                workerPool.submit(invoker);
            }
            return this;
        }
    }

    protected class StandardMBeanInvoker implements CloudMBeanInvoker {

        protected String name;
        protected String operation = null;
        protected String attributeName = null;
        protected Object[] args = new Object[0];
        protected String[] argTypes = new String[0];
        protected ObjectName oname;
        protected Channel channel;
        protected String correlationId;
        protected String replyTo;

        public StandardMBeanInvoker() {
            try {
                this.channel = connection.createChannel();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }

        public void setName(String name) throws MalformedObjectNameException {
            this.name = name;
            this.oname = new ObjectName(name);
        }

        public String getName() {
            return this.name;
        }

        public ObjectName getObjectName() {
            return oname;
        }

        public void setOperation(String name) {
            this.operation = name;
        }

        public String getOperation() {
            return this.operation;
        }

        public void setAttributeName(String name) {
            this.attributeName = name;
        }

        public String getAttributeName() {
            return this.attributeName;
        }

        public void setCorrelationId(String id) {
            this.correlationId = id;
        }

        public String getCorrelationId() {
            return this.correlationId;
        }

        public void setReplyTo(String replyTo) {
            this.replyTo = replyTo;
        }

        public String getReplyTo() {
            return this.replyTo;
        }

        public void setArgs(Object[] args) {
            this.args = args;
        }

        public Object[] getArgs() {
            return this.args;
        }

        public void setArgTypes(String[] argTypes) {
            this.argTypes = argTypes;
        }

        public String[] getArgTypes() {
            return this.argTypes;
        }

        public Object getAttributeValue(String attributeName) {
            Object o = null;
            try {
                o = mbeanServer.getAttribute(oname, attributeName);
            } catch (Throwable t) {
                log.error(t.getMessage(), t);
            }
            return o;
        }

        public Object invoke() {
            Object o = null;
            try {
                o = mbeanServer.invoke(oname, operation, args, argTypes);
            } catch (Throwable t) {
                log.error(t.getMessage(), t);
            }
            return o;
        }

        public Object call() throws Exception {
            AMQP.BasicProperties props = new AMQP.BasicProperties();
            props.setCorrelationId(correlationId);
            Object o = null;
            if (null != operation) {
                o = invoke();
            } else if (null != attributeName) {
                o = mbeanServer.getAttribute(oname, attributeName);
            }
            if (null != o) {
                if (DEBUG) {
                    log.debug("Invocation returned: " + o.toString());
                    log.debug("Class: " + o.getClass().toString());
                }
            }
            ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
            JsonGenerator json = new JsonFactory().createJsonGenerator(bytesOut, JsonEncoding.UTF8);
            json.writeStartObject();
            // We're getting runtime JVM information
            if (o instanceof CompositeDataSupport) {
                CompositeDataSupport data = (CompositeDataSupport) o;
                for (String key : data.getCompositeType().keySet()) {
                    if (DEBUG) {
                        log.debug(key + ": " + data.get(key));
                    }
                    Object item = data.get(key);
                    if (item instanceof Long) {
                        json.writeNumberField(key, (Long) item);
                    } else if (item instanceof String) {
                        json.writeStringField(key, (String) item);
                    } else {
                        json.writeStringField(key, item.toString());
                    }
                }
            } else if (o instanceof Long) {
                json.writeNumberField(attributeName, (Long) o);
            } else if (o instanceof String) {
                json.writeStringField(attributeName, (String) o);
            } else if (o.getClass().isArray()) {
                if (DEBUG) {
                    log.debug("Writing array to JSON...");
                }
                json.writeArrayFieldStart(operation);
                for (String s : (String[]) o) {
                    if (DEBUG) {
                        log.debug("Object: " + s);
                    }
                    json.writeObject(s);
                }
                json.writeEndArray();
            } else {
                if (DEBUG) {
                    log.debug("Not sure what to do with " + attributeName);
                }
            }
            json.writeEndObject();
            json.flush();

            synchronized (this.channel) {
                this.channel.basicPublish("", getReplyTo(), props, bytesOut.toByteArray());
            }

            return null; //To change body of implemented methods use File | Settings | File Templates.
        }
    }

    protected Connection getConnection() throws IOException {
        if (null == connection) {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost(mqHost);
            factory.setPort(mqPort);
            factory.setUsername(mqUser);
            factory.setPassword(mqPassword);
            factory.setVirtualHost(mqVirtualHost);
            if (DEBUG) {
                log.debug("Connecting to RabbitMQ server...");
            }
            connection = factory.newConnection();
        }
        return connection;
    }

    protected Channel getChannel() throws IOException {
        if (null == channel) {
            channel = getConnection().createChannel();
            // For generic cloud events (membership, etc...)
            if (DEBUG) {
                log.debug("Declaring exch: " + eventsExchange);
            }
            synchronized (channel) {
                //channel.exchangeDelete( eventsExchange );
                channel.exchangeDeclare(eventsExchange, "topic", true);
                //channel.queueDeclare(eventsQueue, false, true, false, true, null);
                //channel.queueBind(eventsQueue, eventsExchange, "");
                // For mbean events
                if (DEBUG) {
                    log.debug("Declaring/binding exch: " + mbeanEventsExchange + ", q: " + mbeanEventsQueue
                            + ", key: " + mbeanEventsRoutingKey);
                }
                //channel.exchangeDelete( mbeanEventsExchange );
                channel.exchangeDeclare(mbeanEventsExchange, "topic", true);
                channel.queueDeclare(mbeanEventsQueue, true, false, false, null);
                channel.queueBind(mbeanEventsQueue, mbeanEventsExchange, mbeanEventsRoutingKey);
            }
        }
        return channel;
    }

}