org.hyperic.lather.server.LatherServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.lather.server.LatherServlet.java

Source

/*
 * NOTE: This copyright does *not* cover user programs that use HQ
 * program services by normal system calls through the application
 * program interfaces provided as part of the Hyperic Plug-in Development
 * Kit or the Hyperic Client Development Kit - this is merely considered
 * normal use of the program, and does *not* fall under the heading of
 * "derived work".
 * 
 * Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
 * This file is part of HQ.
 * 
 * HQ is free software; you can redistribute it and/or modify
 * it under the terms version 2 of the GNU General Public License as
 * published by the Free Software Foundation. This program 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 General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

package org.hyperic.lather.server;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.appdef.shared.AIPlatformValue;
import org.hyperic.hq.appdef.shared.AgentUnauthorizedException;
import org.hyperic.hq.bizapp.server.session.LatherDispatcher;
import org.hyperic.hq.bizapp.shared.lather.AiPlatformLatherValue;
import org.hyperic.hq.bizapp.shared.lather.CommandInfo;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.measurement.server.session.DataInserterException;
import org.hyperic.lather.LatherContext;
import org.hyperic.lather.LatherRemoteException;
import org.hyperic.lather.LatherValue;
import org.hyperic.lather.NullLatherValue;
import org.hyperic.lather.client.LatherHTTPClient;
import org.hyperic.lather.xcode.LatherXCoder;
import org.hyperic.util.encoding.Base64;

/**
 * The purpose of this class is to take servlet requests for
 * remote execution, and translate them into service calls.
 *
 * The servlet understands POST requests which must have the
 * following data:
 *
 * method    = method name
 * args      = an encoded LatherValue object
 * argsClass = the class which the 'args' is encoded for
 *
 * The response is an encoded LatherValue object.
 */
@SuppressWarnings("serial")
public class LatherServlet extends HttpServlet {
    private static final AtomicLong ids = new AtomicLong();
    private static final String PROP_PREFIX = ConnManager.PROP_PREFIX;
    private static final String PROP_MAXCONNS = ConnManager.PROP_MAXCONNS;
    private static final String PROP_EXECTIMEOUT = PROP_PREFIX + "execTimeout";
    private static final String PROP_CONNID = PROP_PREFIX + "connID";

    private static final Log log = LogFactory.getLog(LatherServlet.class);

    private Random rand;
    private int execTimeout;
    private static final AtomicReference<ConnManager> connManager = new AtomicReference<ConnManager>();

    private String getReqCfg(ServletConfig cfg, String prop) throws ServletException {
        String res;
        if ((res = cfg.getInitParameter(prop)) == null) {
            throw new ServletException("init-param '" + prop + "' not set");
        }
        return res;
    }

    public void init(ServletConfig cfg) throws ServletException {
        // Call super per the javadoc
        super.init(cfg);
        rand = new Random();
        if (connManager.get() == null) {
            connManager.compareAndSet(null, getConnManager(cfg));
        }
        execTimeout = Integer.parseInt(getReqCfg(cfg, PROP_EXECTIMEOUT));
    }

    private ConnManager getConnManager(ServletConfig cfg) throws ServletException {
        @SuppressWarnings("unchecked")
        final Enumeration<String> paramNames = cfg.getInitParameterNames();
        final Map<String, Semaphore> maxConnMap = new HashMap<String, Semaphore>();
        while (paramNames.hasMoreElements()) {
            final String name = paramNames.nextElement();
            if (!name.startsWith(PROP_PREFIX) || name.contains(PROP_EXECTIMEOUT)) {
                continue;
            }
            final String param = cfg.getInitParameter(name);
            try {
                final int value = Integer.parseInt(param);
                maxConnMap.put(name.replace(PROP_PREFIX, ""), new Semaphore(value));
            } catch (NumberFormatException e) {
                log.error("could not initialize max conn setting for " + name + " value=" + param);
            }
        }
        if (!maxConnMap.containsKey(PROP_MAXCONNS)) {
            throw new ServletException("init-params do not contain key=" + PROP_MAXCONNS + ")");
        }
        return new ConnManager(maxConnMap);
    }

    private static void issueErrorResponse(HttpServletResponse resp, String errMsg) throws IOException {
        resp.setContentType("text/raw");
        resp.setIntHeader(LatherHTTPClient.HDR_ERROR, 1);
        resp.getOutputStream().print(errMsg);
    }

    private static void issueSuccessResponse(HttpServletResponse resp, LatherXCoder xCoder, LatherValue res)
            throws IOException {
        ByteArrayOutputStream bOs;
        DataOutputStream dOs;
        byte[] rawData;

        resp.setContentType("text/latherValue");
        resp.setHeader(LatherHTTPClient.HDR_VALUECLASS, res.getClass().getName());

        bOs = new ByteArrayOutputStream();
        dOs = new DataOutputStream(bOs);
        xCoder.encode(res, dOs);
        rawData = bOs.toByteArray();
        resp.getOutputStream().print(Base64.encode(rawData));
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final boolean debug = log.isDebugEnabled();
        boolean gotConn = false;
        int connRnd;
        connRnd = this.rand.nextInt();
        String method = req.getParameter("method");
        try {
            gotConn = connManager.get().grabConn(method);
            if (!gotConn) {
                final String msg = new StringBuilder(128).append("Denied request from ").append(req.getRemoteAddr())
                        .append(" availablePermits=").append(connManager.get().getAvailablePermits(method))
                        .append(", method=").append(method).toString();
                if (debug)
                    log.debug(msg);
                resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, msg);
                return;
            }
            req.setAttribute(PROP_CONNID, new Integer(connRnd));
            if (debug) {
                log.debug("Accepting request from " + req.getRemoteAddr() + " availablePermits="
                        + connManager.get().getAvailablePermits(method) + " conID=" + connRnd);
            }
            super.service(req, resp);
        } finally {
            if (gotConn) {
                connManager.get().releaseConn(method);
                if (debug)
                    log.debug("Releasing request from " + req.getRemoteAddr() + " availablePermits="
                            + connManager.get().getAvailablePermits(method) + " connID=" + connRnd);
            }
        }
    }

    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ByteArrayInputStream bIs;
        DataInputStream dIs;
        LatherXCoder xCoder;
        LatherValue val;
        String[] method, args, argsClass;
        LatherContext ctx;
        byte[] decodedArgs;
        Class<?> valClass;

        ctx = new LatherContext();
        ctx.setCallerIP(req.getRemoteAddr());
        ctx.setRequestTime(System.currentTimeMillis());

        xCoder = new LatherXCoder();

        method = req.getParameterValues("method");
        args = req.getParameterValues("args");
        argsClass = req.getParameterValues("argsClass");

        if (method == null || args == null || argsClass == null || method.length != 1 || args.length != 1
                || argsClass.length != 1) {
            String msg = "Invalid Lather request made from " + req.getRemoteAddr();
            log.error(msg);
            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, msg);
            return;
        }

        if (log.isDebugEnabled()) {
            log.debug("Invoking method '" + method[0] + "' for connID=" + req.getAttribute(PROP_CONNID));
        }

        try {
            valClass = Class.forName(argsClass[0], true, xCoder.getClass().getClassLoader());
        } catch (ClassNotFoundException exc) {
            String msg = "Lather request from " + req.getRemoteAddr() + " required an argument object of class '"
                    + argsClass[0] + "' which could not be found";
            log.error(msg);
            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, msg);
            return;
        }

        decodedArgs = Base64.decode(args[0]);
        bIs = new ByteArrayInputStream(decodedArgs);
        dIs = new DataInputStream(bIs);

        try {
            val = xCoder.decode(dIs, valClass);
        } catch (LatherRemoteException exc) {
            LatherServlet.issueErrorResponse(resp, exc.toString());
            return;
        }

        this.doServiceCall(req, resp, method[0], val, xCoder, ctx);
    }

    private class ServiceCaller implements Runnable {

        private HttpServletResponse resp;

        private LatherXCoder xcoder;
        private LatherValue arg;
        private LatherContext ctx;
        private String method;
        private LatherDispatcher latherDispatcher;

        private Thread thread;
        private final AtomicBoolean finished = new AtomicBoolean(false);
        private AtomicLong startTime;

        private ServiceCaller(HttpServletResponse resp, LatherXCoder xcoder, LatherContext ctx, String method,
                LatherValue arg, LatherDispatcher latherDispatcher) {

            this.resp = resp;
            this.xcoder = xcoder;
            this.ctx = ctx;
            this.method = method;
            this.arg = arg;
            this.latherDispatcher = latherDispatcher;
            this.thread = Thread.currentThread();
        }

        private boolean hasStarted() {
            return startTime != null;
        }

        private boolean isFinished() {
            return finished.get();
        }

        private void markFinished() {
            this.thread = null;
            finished.set(true);
        }

        public void run() {

            try {
                startTime = new AtomicLong(System.currentTimeMillis());
                LatherValue res = latherDispatcher.dispatch(ctx, method, arg);
                if (thread.isInterrupted()) {
                    return;
                }

                if (CommandInfo.CMD_AI_SEND_REPORT.equals(method)) {
                    res = handleAutoApprovals(res);
                }

                issueSuccessResponse(this.resp, this.xcoder, res);

            } catch (Exception e) {
                Throwable cause = e.getCause();
                // no need to log a full stack trace for known exceptions
                if (e instanceof AgentUnauthorizedException
                        || (cause != null && cause instanceof AgentUnauthorizedException)) {
                    log.warn("Unauthorized agent from [" + ctx.getCallerIP() + "] denied", e);
                    log.debug(e, e);
                } else if (e instanceof DataInserterException
                        || (cause != null && cause instanceof DataInserterException)) {
                    log.warn(e);
                    log.debug(e, e);
                } else if (e.getMessage().equals("Server still initializing")) {
                    log.warn(e);
                    log.debug(e, e);
                } else {
                    log.error("error while invoking LatherDispatcher from ip=" + ctx.getCallerIP() + ", method="
                            + method + ": " + e, e);
                }
                try {
                    issueErrorResponse(resp, e.toString());
                } catch (IOException ioe) {
                    log.warn("IO error sending lather response method=" + method + ", ip=" + ctx.getCallerIP()
                            + ": " + ioe, ioe);
                }
            }
        }

        private LatherValue handleAutoApprovals(LatherValue arg) throws LatherRemoteException {

            if (arg == null || arg instanceof NullLatherValue) {
                return NullLatherValue.INSTANCE;
            }

            AiPlatformLatherValue aiPlatformLatherValue = (AiPlatformLatherValue) arg;
            AIPlatformValue aiPlatformValue = aiPlatformLatherValue.getAIPlatformValue();

            if (aiPlatformValue.isAutoApprove()) {
                this.latherDispatcher.invokeAutoApprove(aiPlatformValue);
            }

            return NullLatherValue.INSTANCE;
        }

        public void interrupt() {
            if (thread != null)
                thread.interrupt();
        }

        public boolean isExpired() {
            if (startTime == null) {
                return false;
            }
            final long now = System.currentTimeMillis();
            if ((startTime.get() + execTimeout) <= now) {
                return true;
            }
            return false;
        }

        public String toString() {
            return "method=" + method + ", ip=" + ctx.getCallerIP();
        }
    }

    private void doServiceCall(HttpServletRequest req, HttpServletResponse resp, String methName, LatherValue args,
            LatherXCoder xCoder, LatherContext ctx) throws IOException {

        final LatherDispatcher latherDispatcher = Bootstrap.getBean(LatherDispatcher.class);
        final ServiceCaller caller = new ServiceCaller(resp, xCoder, ctx, methName, args, latherDispatcher);
        final Thread currentThread = Thread.currentThread();
        final String threadName = currentThread.getName();

        try {
            currentThread.setName(methName + "-" + ids.getAndIncrement());
            LatherThreadMonitor.get().register(caller);
            caller.run();
            if (currentThread.isInterrupted()) {
                throw new InterruptedException();
            }
        } catch (InterruptedException exc) {
            log.warn("Interrupted while trying to execute lather method=" + methName + " from ip="
                    + ctx.getCallerIP());
        } finally {
            caller.markFinished();
            currentThread.setName(threadName);
        }
    }

    private static class LatherThreadMonitor extends Thread {

        private static final LatherThreadMonitor instance = new LatherThreadMonitor();

        static {
            log.info("Starting Lather Thread Monitor");
            instance.start();
        }

        private static final ConcurrentLinkedQueue<ServiceCaller> queue = new ConcurrentLinkedQueue<LatherServlet.ServiceCaller>();

        private LatherThreadMonitor() {
            super("LatherThreadMonitor");
            setDaemon(true);
        }

        private static LatherThreadMonitor get() {
            return instance;
        }

        public void register(ServiceCaller caller) {
            queue.add(caller);
        }

        public void run() {

            final boolean debug = log.isDebugEnabled();
            final long inspectionInterval = 1000;
            int callerIndex = 0;

            while (true) {
                try {

                    for (ServiceCaller caller : queue) {
                        if (caller.isFinished()) {
                            queue.remove(caller);
                        } else if (caller.isExpired()) {
                            log.warn("Expiring Lather thread " + caller);
                            caller.interrupt();
                            queue.remove(caller);
                        }
                        callerIndex++;
                    }
                    sleep(inspectionInterval);
                    if (debug)
                        log.debug("LatherThreadMonitor current queue size = " + queue.size() + ", " + callerIndex
                                + " threads were inspected");
                    callerIndex = 0;

                } catch (Throwable t) {
                    log.error(t, t);
                }
            }
        }
    }
}