Java tutorial
/* * 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; } }