/*
 * RHQ Management Platform
 * Copyright (C) 2005-2013 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.enterprise.communications.servlet;

import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jboss.logging.Logger;
import org.jboss.remoting.transport.servlet.ServletServerInvokerMBean;

/**
 * Extends JBoss/Remoting 2's servlet that receives the inital http request for the ServletServerInvoker.
 * This extension finds remoting ServletServerInvoker using our own mechanism.  We need to do this since
 * Remoting 2.4+ changed the ObjectName such that we can't statically define it in the web.xml.
 * The new ObjectName that Remoting uses is:
 * jboss.remoting:service=invoker,transport=servlet,host=myhost,port=7080,rhq.communications.connector.rhqtype=server
 * but we can't assume users won't configure RHQ with a different port and we don't know at build time
 * host name.
 */
public class ServerInvokerServlet extends org.jboss.remoting.transport.servlet.web.ServerInvokerServlet {
    private static final long serialVersionUID = 1L;

    private static Logger log = Logger.getLogger(ServerInvokerServlet.class);

    private ServletConfig servletConfig;
    private AtomicInteger evilLogMessageCount = new AtomicInteger(0);
    private final AtomicBoolean alreadyInitialized = new AtomicBoolean(false);

    @Override
    public void init(ServletConfig config) throws ServletException {
        // purposefully do not call init(config) - we can't have the superclass try to get
        // the servletInvoker yet and risk getting that dreaded and evil log message!
        super.init();
        this.servletConfig = config;
    }

    @Override
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException,
        java.io.IOException {

        // find the servlet invoker now - catch exceptions and avoid logging so we don't scare people
        if (initServletInvokerNow()) {
            super.processRequest(request, response);
        } else {
            // tell the client we can't process the request
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Server is not ready yet");
        }
    }

    /**
     * Performs the "real" init so the JBoss/Remoting superclass loads the servlet invoker.
     * This returns true if we can keep going; false if an error occurred and we can't process the current request.
     */
    private boolean initServletInvokerNow() {
        try {
            synchronized (alreadyInitialized) {
                if (!alreadyInitialized.get()) {
                    super.init(this.servletConfig);
                    alreadyInitialized.set(true);
                }
            }
            return true;
        } catch (Exception e) {
            // only log a message (at debug level) if we see this lots of times, but only every 10th time
            int msgCount = evilLogMessageCount.incrementAndGet();
            if ((msgCount % 10 == 0) && (msgCount >= 100)) {
                log.debug(e);
            }
            return false;
        }
    }

    /**
     * Returns the servlet server invoker. In our implementation, this always returns
     * the MBean.
     *
     * @param config the servlet configuration
     * @return the servlet server invoker using the "invokerName" to query to find the MBean
     * @throws ServletException
     */
    protected ServletServerInvokerMBean getInvokerFromInvokerName(ServletConfig config) throws ServletException {
        // get the invokerName initial parameter as defined in web.xml
        String invokerNameString = config.getInitParameter("invokerName");
        if (invokerNameString == null) {
            throw new ServletException("RHQ's use of this Servlet requires invokerName init parameter");
        }

        // the invoker name should be a query ObjectName that should find the 1 MBean we are looking for
        ObjectName invokerObjectNameQuery = null;
        try {
            invokerObjectNameQuery = new ObjectName(invokerNameString);
            log("invokerObjectNameQuery=" + invokerObjectNameQuery);
        } catch (MalformedObjectNameException e) {
            throw new ServletException("Failed to build invokerObjectNameQuery", e);
        }

        // Lookup the MBeanServer
        String mbeanServerId = config.getInitParameter("mbeanServer");
        MBeanServer mbeanServer = getMBeanServer(mbeanServerId);
        if (mbeanServer == null) {
            throw new ServletException("Failed to locate the MBeanServer");
        }

        Set<ObjectName> mbeans = mbeanServer.queryNames(invokerObjectNameQuery, null);
        if (mbeans.isEmpty()) {
            throw new ServletException(
                "Could not find the remoting servlet invoker ["
                    + invokerObjectNameQuery
                    + "].  DURING SERVER STARTUP AND INITIALIZATION THIS IS NOT AN ERROR AND CAN BE IGNORED.  This may be a problem if occurring during normal server runtime.");
        }
        if (mbeans.size() > 1) {
            throw new ServletException("Found more than one remoting servlet invoker at [" + invokerObjectNameQuery
                + "]=" + mbeans);
        }

        ObjectName theInvokerObjectName = mbeans.iterator().next();
        log("Found RHQ remoting servlet: " + theInvokerObjectName);

        return MBeanServerInvocationHandler.newProxyInstance(mbeanServer,
            theInvokerObjectName, ServletServerInvokerMBean.class, false);
    }
}
