ie.deri.wsmx.core.management.httpadapter.HttpAdapter.java Source code

Java tutorial

Introduction

Here is the source code for ie.deri.wsmx.core.management.httpadapter.HttpAdapter.java

Source

/*
 * Copyright (c) 2005 National University of Ireland, Galway
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 ie.deri.wsmx.core.management.httpadapter;

import ie.deri.wsmx.core.management.AdapterServerSocketFactory;
import ie.deri.wsmx.core.management.PlainAdapterServerSocketFactory;
import ie.deri.wsmx.core.management.webfrontend.DefaultProcessor;
import ie.deri.wsmx.core.management.webfrontend.ProcessorMBean;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;

import javax.management.JMException;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;

/**
 * HttpAdapter sets the basic adapter listening for HTTP requests
 *
 * <pre>
 * Created on Feb 11, 2005
 * Committed by $$Author: haselwanter $$
 * $$Source: /cygdrive/e/progs/cygwin/usr/maczar/cvsbackup/core/src/main/ie/deri/wsmx/core/management/httpadapter/HttpAdapter.java,v $$
 * </pre>
 * 
 * @author Thomas Haselwanter
 * @author Michal Zaremba
 *
 * @version $Revision: 1.9 $ $Date: 2005-11-25 14:38:58 $
 */
public class HttpAdapter implements HttpAdapterMBean, MBeanRegistration {
    static Logger logger = Logger.getLogger(HttpAdapter.class);

    /**
     * Port to listen for connections
     */
    int port = 8080;

    /**
     * Host where to set the server socket
     */
    private String host = "localhost";

    /**
     * Target server
     */
    private MBeanServer server;

    /**
     * Server socket
     */
    ServerSocket serverSocket;

    /**
     * Indicates whether the server is running
     */
    boolean alive;

    /**
     * Map of commands indexed by the request path
     */
    private Map<String, HttpCommandProcessor> commands = new HashMap<String, HttpCommandProcessor>();

    /**
     * Target processor
     */
    private ProcessorMBean processor = null;

    /**
     * Target processor name
     */
    private ObjectName processorName = null;

    /**
     * Default processor
     */
    private ProcessorMBean defaultProcessor = new DefaultProcessor();

    String authenticationMethod = "none";

    String realm = "WSMX";

    private Map<String, String> authorizations = new HashMap<String, String>();

    private AdapterServerSocketFactory socketFactory = null;

    private ObjectName factoryName;

    private String processorClass;

    Date startDate;

    long requestsCount;

    private String[][] defaultCommandProcessors = {
            { "main", "ie.deri.wsmx.core.management.httpadapter.MainHttpCommandProcessor" },
            { "serverbydomain", "ie.deri.wsmx.core.management.httpadapter.ServerByDomainHttpCommandProcessor" },
            { "mbean", "ie.deri.wsmx.core.management.httpadapter.MBeanHttpCommandProcessor" },
            { "setattributes", "ie.deri.wsmx.core.management.httpadapter.SetAttributesHttpCommandProcessor" },
            { "setattribute", "ie.deri.wsmx.core.management.httpadapter.SetAttributeHttpCommandProcessor" },
            { "getattribute", "ie.deri.wsmx.core.management.httpadapter.GetAttributeHttpCommandProcessor" },
            { "delete", "ie.deri.wsmx.core.management.httpadapter.DeleteMBeanHttpCommandProcessor" },
            { "invoke", "ie.deri.wsmx.core.management.httpadapter.InvokeOperationHttpCommandProcessor" },
            { "create", "ie.deri.wsmx.core.management.httpadapter.CreateMBeanHttpCommandProcessor" },
            { "constructors", "ie.deri.wsmx.core.management.httpadapter.ConstructorsHttpCommandProcessor" },
            { "empty", "ie.deri.wsmx.core.management.httpadapter.EmptyHttpCommandProcessor" } };

    private DocumentBuilder builder;

    /**
     * Default Constructor added so that we can have some additional
     * constructors as well.
     */
    public HttpAdapter() {
    }

    /**
     * Overloaded constructor to allow the port to be set. The reason this was
     * added was to allow the loading of this adaptor by the dynamic loading
     * service of the MBean server and have the port set from a param in the
     * mlet file. 
     * <p>
     * This constructor uses the default host or the host must be set later.
     * 
     * @param port
     *            The port on which the HttpAdapter should listen
     */
    public HttpAdapter(int port) {
        this.port = port;
    }

    /**
     * Overloaded constructor to allow the host to be set. The reason this was
     * added was to allow the loading of this adaptor by the dynamic loading
     * service of the MBean server and have the host set from a param in the
     * mlet file. 
     * <p>
     * This constructor uses the default port or the port must be set later.
     * 
     * @param host
     *            The host on which the HttpAdapter should listen
     */
    public HttpAdapter(String host) {
        this.host = host;
    }

    /**
     * Overloaded constructor to allow the port to be set. The reason this was
     * added was to allow the loading of this adaptor by the dynamic loading
     * service of the MBean server and have the port set from a param in the
     * mlet file.
     * 
     * @param port
     *            The port on which the HttpAdapter should listen
     * @param host
     *            The host on which the HttpAdapter should listen
     */
    public HttpAdapter(int port, String host) {
        this.port = port;
        this.host = host;
    }

    /**
     * Sets the value of the server's port
     * 
     * @param port
     *            the new port's value
     */
    public void setPort(int port) {
        if (alive) {
            throw new IllegalArgumentException("Not possible to change port with the server running");
        }
        this.port = port;
    }

    /**
     * Returns the port where the server is running on. Default is 8080
     * 
     * @return HTTPServer's port
     */
    public int getPort() {
        return port;
    }

    /**
     * Sets the host name where the server will be listening
     * 
     * @param host
     *            Server's host
     */
    public void setHost(String host) {
        if (alive) {
            throw new IllegalArgumentException("Not possible to change port with the server running");
        }
        this.host = host;
    }

    /**
     * Return the host name the server will be listening to. If null the server
     * listen at the localhost
     * 
     * @return the current hostname
     */
    public String getHost() {
        return host;
    }

    /**
     * Sets the Authentication Method.
     * 
     * @param method
     *            none/basic/digest
     */
    public void setAuthenticationMethod(String method) {
        if (alive) {
            throw new IllegalArgumentException(
                    "Not possible to change authentication method with the server running");
        }
        if (method == null || !(method.equals("none") || method.equals("basic") || method.equals("digest"))) {
            throw new IllegalArgumentException("Only accept methods none/basic/digest");
        }
        this.authenticationMethod = method;
    }

    /**
     * Authentication Method
     * 
     * @return authentication method
     */
    public String getAuthenticationMethod() {
        return authenticationMethod;
    }

    /**
     * Sets the object which will post process the XML results. The last value
     * set between the setPostProcessor and setPostProcessorName will be the
     * valid one
     * 
     * @param processor
     *            a Post processor object
     */
    public void setProcessor(ProcessorMBean processor) {
        this.processor = processor;
        this.processorName = null;
    }

    /**
     * Sets the classname of the object which will post process the XML results.
     * The adaptor will try to build the object and use the processor name
     * ObjectName to register it The class name has to implements
     * ProcessorMBean and be MBean compliant
     * 
     * @param processorClass
     *            a Post processor object
     */
    public void setProcessorClass(String processorClass) {
        this.processorClass = processorClass;
    }

    /**
     * Sets the object name of the PostProcessor MBean. If ProcessorClass is set
     * the processor will be created
     * 
     * @param processorName
     *            a Post processor object
     */
    public void setProcessorNameString(String processorName) throws MalformedObjectNameException {
        this.processorName = new ObjectName(processorName);
    }

    /**
     * Sets the object name which will post process the XML result. The last
     * value set between the setPostProcessor and setPostProcessorName will be
     * the valid one. The MBean will be verified to be of instance
     * HttpPostProcessor
     * 
     * @param processorName
     *            The new processorName value
     */
    public void setProcessorName(ObjectName processorName) {
        this.processor = null;
        this.processorName = processorName;
    }

    public ProcessorMBean getProcessor() {
        return this.processor;
    }

    public ObjectName getProcessorName() {
        return this.processorName;
    }

    /**
     * Sets the object which create the server sockets
     * 
     * @param factory
     *            the socket factory
     */
    public void setSocketFactory(AdapterServerSocketFactory factory) {
        this.factoryName = null;
        this.socketFactory = factory;
    }

    /**
     * Sets the factory's object name which will create the server sockets
     * 
     * @param factoryName
     *            the socket factory
     */
    public void setSocketFactoryName(ObjectName factoryName) {
        this.socketFactory = null;
        this.factoryName = factoryName;
    }

    /**
     * Sets the factory's object name which will create the server sockets
     * 
     * @param factoryName
     *            the socket factory
     */
    public void setSocketFactoryNameString(String factoryName) throws MalformedObjectNameException {
        this.socketFactory = null;
        this.factoryName = new ObjectName(factoryName);
    }

    /**
     * Indicates whether the server's running
     * 
     * @return The active value
     */
    public boolean isActive() {
        return alive;
    }

    /**
     * Starting date
     * 
     * @return The date when the server was started
     */
    public Date getStartDate() {
        return startDate;
    }

    /**
     * Requests count
     * 
     * @return The total of requests served so far
     */
    public long getRequestsCount() {
        return requestsCount;
    }

    /**
     * Adds a command processor object
     */
    public void addCommandProcessor(String path, HttpCommandProcessor processor) {
        commands.put(path, processor);
        if (alive) {
            processor.setMBeanServer(server);
            processor.setDocumentBuilder(builder);
        }
    }

    /**
     * Adds a command processor object by class
     */
    public void addCommandProcessor(String path, String processorClass) {
        try {
            HttpCommandProcessor processor = (HttpCommandProcessor) Class.forName(processorClass).newInstance();
            addCommandProcessor(path, processor);
        } catch (Exception e) {
            logger.error("Exception creating Command Processor of class " + processorClass, e);
        }
    }

    /**
     * Removes a command processor object by class
     */
    public void removeCommandProcessor(String path) {
        if (commands.containsKey(path)) {
            commands.remove(path);
        }
    }

    /**
     * Starts the server
     */
    public void start() throws IOException {
        if (server != null) {
            serverSocket = createServerSocket();

            if (serverSocket == null) {
                logger.error("Server socket is null");
                return;
            }

            if (processorClass != null && processorName != null) {
                logger.debug("Building processor class of type " + processorClass + " and name " + processorName);
                try {
                    server.createMBean(processorClass, processorName, null);
                } catch (JMException e) {
                    logger.error("Exception creating processor class", e);
                }
            }

            Iterator i = commands.values().iterator();
            while (i.hasNext()) {
                HttpCommandProcessor processor = (HttpCommandProcessor) i.next();
                processor.setMBeanServer(server);
                processor.setDocumentBuilder(builder);
            }

            logger.debug("HttpAdapter server listening on port " + port);
            alive = true;
            Thread serverThread = new Thread(new Runnable() {
                public void run() {
                    logger.info("HttpAdapter started on port " + port);

                    startDate = new Date();
                    requestsCount = 0;

                    while (alive) {
                        try {
                            Socket client = null;
                            client = serverSocket.accept();
                            if (!alive) {
                                client.close();
                                break;
                            }
                            requestsCount++;
                            new HttpClient(client).start();
                        } catch (InterruptedIOException e) {
                            continue;
                        } catch (IOException e) {
                            continue;
                        } catch (Exception e) {
                            logger.warn("Exception during request processing", e);
                            continue;
                        } catch (Error e) {
                            logger.error("Error during request processing", e);
                            continue;
                        }
                    }
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        logger.warn("Exception closing the server", e);
                    }
                    serverSocket = null;
                    alive = false;
                    logger.info("HttpAdapter stopped");
                }
            }, "HttpDaemon");
            serverThread.start();
        } else {
            logger.warn("Start failed, no server target server has been set");
        }
    }

    /**
     * Restarts the server. Useful when changing the Server parameters
     * 
     * @deprecated
     */
    @Deprecated
    public void restart() throws IOException {
        stop();
        start();
    }

    /**
     * Stops the HTTP daemon
     */
    public void stop() {
        try {
            if (alive) {
                alive = false;
                // force the close with a socket call
                new Socket(host, port);
            }
        } catch (IOException e) {
            logger.warn(e.getMessage());
        }
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (IOException e) {
            logger.warn(e.getMessage());
        }
    }

    /**
     * Adds an authorization pair as username/password
     */
    public void addAuthorization(String username, String password) {
        if (username == null || password == null) {
            throw new IllegalArgumentException("username and passwords cannot be null");
        }
        authorizations.put(username, password);
    }

    /**
     * Gathers some basic data
     */
    public ObjectName preRegister(MBeanServer server, ObjectName name) throws java.lang.Exception {
        this.server = server;
        buildCommands();
        return name;
    }

    public void postRegister(Boolean registrationDone) {
    }

    public void preDeregister() throws java.lang.Exception {
        // stop the server
        stop();
    }

    public void postDeregister() {
    }

    private ServerSocket createServerSocket() throws IOException {
        if (socketFactory == null) {
            if (factoryName == null) {
                socketFactory = new PlainAdapterServerSocketFactory();
                return socketFactory.createServerSocket(port, 50, host);
            }
            try {
                return (ServerSocket) server.invoke(factoryName, "createServerSocket",
                        new Object[] { new Integer(port), new Integer(50), host },
                        new String[] { "int", "int", "java.lang.String" });
            } catch (Exception x) {
                logger.error("Exception invoking AdapterServerSocketFactory via MBeanServer", x);
            }
        } else {
            return socketFactory.createServerSocket(port, 50, host);
        }

        return null;
    }

    boolean isUsernameValid(String username, String password) {
        if (authorizations.containsKey(username)) {
            return password.equals(authorizations.get(username));
        }
        return false;
    }

    protected HttpCommandProcessor getProcessor(String path) {
        return commands.get(path);
    }

    /**
     * Build the commands
     */
    protected void buildCommands() {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            builder = factory.newDocumentBuilder();
            for (int i = 0; i < defaultCommandProcessors.length; i++) {
                try {
                    HttpCommandProcessor processor = (HttpCommandProcessor) Class
                            .forName(defaultCommandProcessors[i][1]).newInstance();
                    commands.put(defaultCommandProcessors[i][0], processor);
                } catch (Exception e) {
                    logger.warn("Exception building command procesor", e);
                }
            }
        } catch (ParserConfigurationException e) {
            logger.error("Exception building the Document Factories", e);
        }
    }

    protected void postProcess(HttpOutputStream out, HttpInputStream in, Document document)
            throws IOException, JMException {
        boolean processed = false;
        // inefficient but handles modifications at runtime
        if (processorName != null) {
            if (server.isRegistered(processorName) && server.isInstanceOf(processorName,
                    "ie.deri.wsmx.core.management.httpadapter.ProcessorMBean")) {
                server.invoke(processorName, "writeResponse", new Object[] { out, in, document },
                        new String[] { "ie.deri.wsmx.core.management.httpadapter.HttpOutputStream",
                                "ie.deri.wsmx.core.management.httpadapter.HttpInputStream",
                                "org.w3c.dom.Document" });
                processed = true;
            } else {
                logger.fatal(processorName + " not found");
            }
        }
        if (!processed && processor != null) {
            processor.writeResponse(out, in, document);
            processed = true;
        }
        if (!processed) {
            defaultProcessor.writeResponse(out, in, document);
        }
    }

    protected void findUnknownElement(String path, HttpOutputStream out, HttpInputStream in)
            throws IOException, JMException {
        boolean processed = false;
        // inefficient but handles modifications at runtime
        if (processorName != null) {
            if (server.isRegistered(processorName)
                    && server.isInstanceOf(processorName, "ie.deri.wsmx.core.management.ProcessorMBean")) {
                server.invoke(processorName, "notFoundElement", new Object[] { path, out, in },
                        new String[] { "java.lang.String",
                                "ie.deri.wsmx.core.management.httpadapter.HttpOutputStream",
                                "ie.deri.wsmx.core.management.httpadapter.HttpInputStream" });
                processed = true;
            } else {
                logger.fatal(processorName + " not found");
            }
        }
        if (!processed && processor != null) {
            processor.notFoundElement(path, out, in);
            processed = true;
        }
        if (!processed) {
            defaultProcessor.notFoundElement(path, out, in);
        }
    }

    protected String preProcess(String path) throws JMException {
        boolean processed = false;
        // inefficient but handles modifications at runtime
        if (processorName != null) {
            logger.debug("Preprocess using " + processorName);
            if (server.isRegistered(processorName) && server.isInstanceOf(processorName,
                    "ie.deri.wsmx.core.management.httpadapter.ProcessorMBean")) {
                logger.debug("Preprocessing");
                path = (String) server.invoke(processorName, "preProcess", new Object[] { path },
                        new String[] { "java.lang.String" });
                processed = true;
            } else {
                logger.debug(processorName + " not found");
            }
        }
        if (!processed && processor != null) {
            path = processor.preProcess(path);
            processed = true;
        }
        if (!processed) {
            path = defaultProcessor.preProcess(path);
        }
        return path;
    }

    protected void postProcess(HttpOutputStream out, HttpInputStream in, Exception e)
            throws IOException, JMException {
        boolean processed = false;
        // inefficient but handles modifications at runtime
        if (processorName != null) {
            if (server.isRegistered(processorName) && server.isInstanceOf(processorName,
                    "ie.deri.wsmx.core.management.httpadapter.ProcessorMBean")) {
                server.invoke(processorName, "writeError", new Object[] { out, in, e },
                        new String[] { "ie.deri.wsmx.core.management.httpadapter.HttpOutputStream",
                                "ie.deri.wsmx.core.management.httpadapter.HttpInputStream",
                                "java.lang.Exception" });
                processed = true;
            } else {
                logger.debug(processorName + " not found");
            }
        }
        if (!processed && processor != null) {
            processor.writeError(out, in, e);
            processed = true;
        }
        if (!processed) {
            defaultProcessor.writeError(out, in, e);
        }
    }

    private class HttpClient extends Thread {
        private Socket client;

        HttpClient(Socket client) {
            this.client = client;
        }

        public boolean isValid(String authorizationString) {
            if (authenticationMethod.startsWith("basic")) {
                authorizationString = authorizationString.substring(5, authorizationString.length());
                String decodeString = new String(Base64.decodeBase64(authorizationString.getBytes()));
                if (decodeString.indexOf(":") > 0) {
                    try {
                        StringTokenizer tokens = new StringTokenizer(decodeString, ":");
                        String username = tokens.nextToken();
                        String password = tokens.nextToken();
                        return isUsernameValid(username, password);
                    } catch (Exception e) {
                        return false;
                    }
                }
            }
            return false;
        }

        private boolean handleAuthentication(HttpInputStream in, HttpOutputStream out) throws IOException {
            if (authenticationMethod.equals("basic")) {
                String result = in.getHeader("authorization");
                if (result != null) {
                    if (isValid(result)) {
                        return true;
                    }
                    throw new HttpException(HttpConstants.STATUS_FORBIDDEN, "Authentication failed");
                }

                out.setCode(HttpConstants.STATUS_AUTHENTICATE);
                out.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
                out.sendHeaders();
                out.flush();
                return false;
            }
            if (authenticationMethod.equals("digest")) {
                // not implemented
            }
            return true;
        }

        @Override
        public void run() {

            HttpInputStream httpIn = null;
            HttpOutputStream httpOut = null;
            try {
                // get input streams
                InputStream in = client.getInputStream();
                httpIn = new HttpInputStream(in);
                httpIn.readRequest();

                // Find a suitable command processor
                String path = httpIn.getPath();
                String queryString = httpIn.getQueryString();
                logger.debug("Request " + path + ((queryString == null) ? "" : ("?" + queryString)));
                String postPath = preProcess(path);
                if (!postPath.equals(path)) {
                    logger.debug("Processor replaced path " + path + " with the path " + postPath);
                    path = postPath;
                }
                OutputStream out = client.getOutputStream();
                httpOut = new HttpOutputStream(out, httpIn);
                if (!handleAuthentication(httpIn, httpOut)) {
                    return;
                }
                HttpCommandProcessor processor = getProcessor(path.substring(1, path.length()));
                if (processor == null) {
                    logger.debug("No suitable command processor found, requesting from processor path " + path);
                    findUnknownElement(path, httpOut, httpIn);
                } else {
                    Document document = processor.executeRequest(httpIn);
                    postProcess(httpOut, httpIn, document);
                }
            } catch (Exception ex) {
                logger.warn("Exception during http request", ex);
                if (httpOut != null) {
                    try {
                        postProcess(httpOut, httpIn, ex);
                    } catch (IOException e) {
                        logger.warn("IOException during http request", e);
                    } catch (JMException e) {
                        logger.warn("JMException during http request", e);
                    } catch (RuntimeException rte) {
                        logger.error("RuntimeException during http request", rte);
                    } catch (Error er) {
                        logger.error("Error during http request ", er);
                    } catch (Throwable t) {
                        logger.fatal("Throwable during http request ", t);
                    }
                }
            } catch (Error ex) {
                logger.error("Error during http request ", ex);
            } finally {
                try {
                    if (httpOut != null) {
                        httpOut.flush();
                    }
                } catch (IOException e) {
                    logger.warn("Exception during request processing", e);
                } finally {
                    try {
                        // always close the socket
                        client.close();
                    } catch (IOException e) {
                        logger.info("Exception during socket close", e);
                    }
                }
            }
        }
    }
}