Package com.almende.eve.transport.http

Source Code of com.almende.eve.transport.http.AgentServlet

/*
* 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.List;
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.agent.log.Log;
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 static AgentHost  host;
  private static HttpService  httpTransport;
 
  /*
   * (non-Javadoc)
   *
   * @see javax.servlet.GenericServlet#init()
   */
  @Override
  public void init() {
    if (AgentHost.getInstance().getStateFactory() == null) {
      LOG.severe("DEPRECIATED SETUP: Please add com.almende.eve.transport.http.AgentListener as a Listener to your web.xml!");
      AgentListener.init(getServletContext());
    }
    host = AgentHost.getInstance();
   
    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.");
    }
    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")) {
      // retrieve the agents logs
      final String sinceStr = req.getParameter("since");
      Long since = null;
      if (sinceStr != null) {
        try {
          since = Long.valueOf(sinceStr);
        } catch (final java.lang.NumberFormatException e) {
          LOG.warning("Couldn't parse 'since' parameter:'" + since
              + "'");
        }
      }
     
      try {
        final List<Log> logs = host.getEventLogger().getLogs(agentId,
            since);
        resp.addHeader("Content-type", "application/json");
        JOM.getInstance().writer().writeValue(resp.getWriter(), logs);
      } catch (final Exception e) {
        LOG.log(Level.WARNING, "", e);
        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
            e.getMessage());
      }
    } 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
   */
  @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;
    }
    Agent agent = null;
    try {
      agent = host.getAgent(agentId);
    } catch (final Exception e) {
      LOG.log(Level.WARNING, "Couldn't get agent:" + agentId, e);
    }
    if (agent == null) {
      resp.sendError(HttpServletResponse.SC_NOT_FOUND,
          "Agent not found at this host.");
      resp.flushBuffer();
      return;
    }
   
    if (agent.hasPrivate() && !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);
    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;
  }
}
TOP

Related Classes of com.almende.eve.transport.http.AgentServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.