com.almende.eve.transport.http.AgentServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.almende.eve.transport.http.AgentServlet.java

Source

/*
 * Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
 * License: The Apache Software License, Version 2.0
 */
package com.almende.eve.transport.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import com.almende.eve.agent.Agent;
import com.almende.eve.agent.AgentHost;
import com.almende.eve.agent.AgentSignal;
import com.almende.eve.agent.callback.AsyncCallbackQueue;
import com.almende.eve.agent.callback.SyncCallback;
import com.almende.eve.config.Config;
import com.almende.eve.rpc.jsonrpc.jackson.JOM;
import com.almende.util.StreamingUtil;
import com.almende.util.StringUtil;
import com.almende.util.tokens.TokenStore;
import com.almende.util.uuid.UUID;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * The Class AgentServlet.
 */
@SuppressWarnings("serial")
public class AgentServlet extends HttpServlet {

    private static final Logger LOG = Logger.getLogger(AgentServlet.class.getSimpleName());
    private static final String RESOURCES = "/com/almende/eve/resources/";
    private static final String AGENTURLWARNING = "AgentServlet has a strange URL, can't find agentId!";
    private AgentHost host;
    private HttpService httpTransport = null;

    /**
     * Instantiates a new agent servlet.
     */
    public AgentServlet() {
    };

    /**
     * Instantiates a new agent servlet.
     * 
     * @param transport
     *            the transport
     */
    public AgentServlet(final HttpService transport) {
        httpTransport = transport;
    };

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.GenericServlet#init()
     */
    @Override
    public void init() {
        host = AgentHost.getInstance();
        if (httpTransport == null) {
            if (AgentHost.getInstance().getStateFactory() == null) {
                LOG.severe(
                        "DEPRECATED SETUP: Please add com.almende.eve.transport.http.AgentListener as a Listener to your web.xml!");
                AgentListener.init(getServletContext());
            }
            final String environment = Config.getEnvironment();
            final String envParam = "environment." + environment + ".servlet_url";
            final String globalParam = "servlet_url";
            String servletUrl = getInitParameter(envParam);
            if (servletUrl == null) {
                // if no environment specific servlet_url is defined, read
                // the global servlet_url
                servletUrl = getInitParameter(globalParam);
            }
            if (servletUrl == null) {
                LOG.severe("Cannot initialize HttpTransport: " + "Init Parameter '" + globalParam + "' or '"
                        + envParam + "' " + "missing in context configuration web.xml.");
            } else {
                httpTransport = new HttpService(host, servletUrl);
                host.addTransportService(httpTransport);
            }
        }
    }

    /**
     * The Enum Handshake.
     */
    enum Handshake {

        /** The ok. */
        OK,
        /** The nak. */
        NAK,
        /** The invalid. */
        INVALID
    }

    /**
     * Handle hand shake.
     * 
     * @param req
     *            the req
     * @param res
     *            the res
     * @return true, if successful
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private boolean handleHandShake(final HttpServletRequest req, final HttpServletResponse res)
            throws IOException {
        final String time = req.getHeader("X-Eve-requestToken");

        if (time == null) {
            return false;
        }

        final String token = TokenStore.get(time);
        if (token == null) {
            res.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
        } else {
            res.setHeader("X-Eve-replyToken", token);
            res.setStatus(HttpServletResponse.SC_OK);
            res.flushBuffer();
        }
        return true;
    }

    /**
     * Do hand shake.
     * 
     * @param req
     *            the req
     * @return the handshake
     */
    private Handshake doHandShake(final HttpServletRequest req) {
        final String tokenTupple = req.getHeader("X-Eve-Token");
        if (tokenTupple == null) {
            return Handshake.NAK;
        }

        try {
            final String senderUrl = req.getHeader("X-Eve-SenderUrl");
            if (senderUrl != null && !senderUrl.equals("")) {
                final ObjectNode tokenObj = (ObjectNode) JOM.getInstance().readTree(tokenTupple);
                final HttpGet httpGet = new HttpGet(senderUrl);
                httpGet.setHeader("X-Eve-requestToken", tokenObj.get("time").textValue());
                final HttpResponse response = ApacheHttpClient.get().execute(httpGet);
                if (response.getStatusLine().getStatusCode() == HttpServletResponse.SC_OK) {
                    if (tokenObj.get("token").textValue()
                            .equals(response.getLastHeader("X-Eve-replyToken").getValue())) {
                        return Handshake.OK;
                    }
                } else {
                    LOG.log(Level.WARNING, "Failed to receive valid handshake:" + response);
                }
            }
        } catch (final Exception e) {
            LOG.log(Level.WARNING, "", e);
        }

        return Handshake.INVALID;
    }

    /**
     * Handle session.
     * 
     * @param req
     *            the req
     * @param res
     *            the res
     * @return true, if successful
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private boolean handleSession(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
        try {

            if (req.getSession(false) != null) {
                return true;
            }

            final Handshake hs = doHandShake(req);
            if (hs.equals(Handshake.INVALID)) {
                return false;
            }

            String doAuthenticationStr = AgentListener.getParam("eve_authentication");
            if (doAuthenticationStr == null) {
                // TODO: authentication param is deprecated since v2.0. Cleanup
                // some day
                doAuthenticationStr = AgentListener.getParam("authentication");
                if (doAuthenticationStr == null) {
                    doAuthenticationStr = "true";
                    LOG.warning("context-param \"eve_authentication\" not found. Using default value "
                            + doAuthenticationStr);
                } else {
                    LOG.warning(
                            "context-param \"authentication\" is deprecated. Use \"eve_authentication\" instead.");
                }
            }
            final Boolean doAuthentication = Boolean.parseBoolean(doAuthenticationStr);

            if (hs.equals(Handshake.NAK) && doAuthentication) {
                if (!req.isSecure()) {
                    res.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "Request needs to be secured with SSL for session management!");
                    return false;
                }
                if (!req.authenticate(res)) {
                    return false;
                }
            }
            // generate new session:
            req.getSession(true);
        } catch (final Exception e) {
            res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Exception running HandleSession:" + e.getMessage());
            LOG.log(Level.WARNING, "", e);
            return false;
        }
        return true;
    }

    /**
     * Get an agents web interface Usage: GET /servlet/{agentId}.
     * 
     * @param req
     *            the req
     * @param resp
     *            the resp
     * @throws ServletException
     *             the servlet exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @Override
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        final String uri = req.getRequestURI();
        String agentId;
        try {
            agentId = httpTransport.getAgentId(new URI(uri));
        } catch (URISyntaxException e) {
            throw new ServletException(AGENTURLWARNING, e);
        }
        String resource = httpTransport.getAgentResource(uri);

        // if no agentId is found, return generic information on servlet usage
        if (agentId == null || agentId.equals("")) {
            resp.getWriter().write(getServletDocs());
            resp.setContentType("text/plain");
            return;
        }

        // check if the agent exists
        try {
            if (!host.hasAgent(agentId)) {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Agent with id '" + agentId + "' not found.");
                return;
            }
        } catch (final Exception e) {
            throw new ServletException(e);
        }

        // If this is a handshake request, handle it.
        if (handleHandShake(req, resp)) {
            return;
        }

        try {
            if (host.getAgent(agentId).hasPrivate() && !handleSession(req, resp)) {
                if (!resp.isCommitted()) {
                    resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                }
                return;
            }
        } catch (final Exception e1) {
            LOG.log(Level.WARNING, "", e1);
        }
        // get the resource name from the end of the url
        if (resource == null || resource.equals("")) {
            if (!uri.endsWith("/") && !resp.isCommitted()) {
                final String redirect = uri + "/";
                resp.sendRedirect(redirect);
                return;
            }
            resource = "index.html";
        }
        final String extension = resource.substring(resource.lastIndexOf('.') + 1);

        if (resource.equals("events")) {
            //TODO: fix this again.
        } else {
            // load the resource
            final String mimetype = StreamingUtil.getMimeType(extension);
            final String filename = RESOURCES + resource;
            final InputStream is = this.getClass().getResourceAsStream(filename);
            if (is != null) {
                StreamingUtil.streamBinaryData(is, mimetype, resp);
            } else {
                throw new ServletException("Resource '" + resource + "' not found");
            }
        }
    }

    /**
     * Send a JSON-RPC message to an agent Usage: POST /servlet/{agentId} With a
     * JSON-RPC request as body. Response will be a JSON-RPC response.
     * 
     * @param req
     *            the req
     * @param resp
     *            the resp
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws ServletException
     *             the servlet exception
     */
    @Override
    public void doPost(final HttpServletRequest req, final HttpServletResponse resp)
            throws IOException, ServletException {

        // retrieve the agent url and the request body
        final String body = StringUtil.streamToString(req.getInputStream());

        final String agentUrl = req.getRequestURI();
        String agentId;
        try {
            agentId = httpTransport.getAgentId(new URI(agentUrl));
        } catch (URISyntaxException e) {
            throw new ServletException(AGENTURLWARNING, e);
        }
        if (agentId == null || agentId.equals("")) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "No agentId found in url.");
            resp.flushBuffer();
            return;
        }

        if (host.hasPrivate(agentId) && !handleSession(req, resp)) {
            if (!resp.isCommitted()) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
            resp.flushBuffer();
            return;
        }

        // Attach the claimed senderId, or null if not given.
        String senderUrl = req.getHeader("X-Eve-SenderUrl");
        if (senderUrl == null || senderUrl.equals("")) {
            senderUrl = "web://" + req.getRemoteUser() + "@" + req.getRemoteAddr();
        }
        final String tag = new UUID().toString();

        final SyncCallback<String> callback = new SyncCallback<String>();

        final AsyncCallbackQueue<String> callbacks = host.getCallbackQueue("HttpTransport", String.class);
        callbacks.push(tag, "", callback);
        //TODO: check if it's base64 encoded data, decode to byte[] and call receive byte[].
        host.receive(agentId, body, URI.create(senderUrl), tag);

        try {
            final Object message = callback.get();
            // return response
            resp.addHeader("Content-Type", "application/json");
            resp.getWriter().println(message.toString());
            resp.getWriter().close();
        } catch (final Exception e) {
            LOG.log(Level.WARNING, "Http Sync receive raised exception.", e);
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Receiver raised exception:" + e.getMessage());
        }
        resp.flushBuffer();
    }

    /**
     * Create a new agent Usage: PUT /servlet/{agentId}?type={agentType} Where
     * agentType is the full class path of the agent. Returns a list with the
     * urls of the created agent.
     * 
     * @param req
     *            the req
     * @param resp
     *            the resp
     * @throws ServletException
     *             the servlet exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @Override
    protected void doPut(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        final String agentUrl = req.getRequestURI();
        String agentId;
        try {
            agentId = httpTransport.getAgentId(new URI(agentUrl));
        } catch (URISyntaxException e) {
            throw new ServletException(AGENTURLWARNING, e);
        }
        String agentType = req.getParameter("type");

        if (!handleSession(req, resp)) {
            if (!resp.isCommitted()) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
            return;
        }
        if (agentType == null) {
            // TODO: class is deprecated since 2013-02-19. Remove this some day
            agentType = req.getParameter("class");
            LOG.warning("Query parameter 'class' is deprecated. Use 'type' instead.");
        }

        if (agentId == null) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "No agentId found in url.");
            return;
        }
        if (agentType == null || agentType.equals("")) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Query parameter 'type' missing in url.");
            return;
        }

        try {
            final Agent agent = host.createAgent(agentType, agentId);
            for (final String url : agent.getUrls()) {
                resp.getWriter().println(url);
            }
            agent.signalAgent(new AgentSignal<Void>(AgentSignal.DESTROY, null));
        } catch (final Exception e) {
            throw new ServletException(e);
        }
    }

    /**
     * Delete an agent usage: DELETE /servlet/agentId.
     * 
     * @param req
     *            the req
     * @param resp
     *            the resp
     * @throws ServletException
     *             the servlet exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @Override
    protected void doDelete(final HttpServletRequest req, final HttpServletResponse resp)
            throws ServletException, IOException {
        final String agentUrl = req.getRequestURI();
        String agentId;
        try {
            agentId = httpTransport.getAgentId(new URI(agentUrl));
        } catch (URISyntaxException e) {
            throw new ServletException(AGENTURLWARNING, e);
        }

        if (!handleSession(req, resp)) {
            if (!resp.isCommitted()) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
            return;
        }
        if (agentId == null) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "No agentId found in url.");
            return;
        }

        try {
            host.deleteAgent(agentId);
            resp.getWriter().write("Agent " + agentId + " deleted");
        } catch (final Exception e) {
            throw new ServletException(e);
        }
    }

    /**
     * Get a description on how to use this servlet.
     * 
     * @return info
     */
    protected String getServletDocs() {
        final String servletUrl = httpTransport.getServletUrl();
        final String info = "EVE AGENTS SERVLET\n" + "\n" + "Usage:\n" + "\n" +

                "GET " + servletUrl + "\n" + "\n" + "    Returns information on how to use this servlet.\n" + "\n" +

                "GET " + servletUrl + "{agentId}\n" + "\n"
                + "    Returns an agents web interface, allowing for easy interaction\n" + "    with the agent.\n"
                + "    A 404 error will be returned when the agent does not exist.\n" + "\n" +

                "POST " + servletUrl + "{agentId}\n" + "\n" + "    Send an RPC call to an agent.\n"
                + "    The body of the request must contain a JSON-RPC request.\n"
                + "    The addressed agent will execute the request and return a\n"
                + "    JSON-RPC response. This response can contain the result or\n" + "    an exception.\n"
                + "    A 404 error will be returned when the agent does not exist.\n" + "\n" +

                "PUT " + servletUrl + "{agentId}?type={agentType}\n" + "\n"
                + "    Create an agent. agentId can be any string. agentType must\n"
                + "    be a full java class path of an Agent. A 500 error will be\n"
                + "    thrown when an agent with this id already exists.\n" + "\n" +

                "DELETE " + servletUrl + "{agentId}\n" + "\n" + "    Delete an agent by its id.";

        return info;
    }
}