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

Java tutorial

Introduction

Here is the source code for com.almende.eve.transport.http.HttpService.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.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;

import com.almende.eve.agent.AgentHost;
import com.almende.eve.agent.callback.AsyncCallback;
import com.almende.eve.agent.callback.AsyncCallbackQueue;
import com.almende.eve.transport.TransportService;
import com.almende.util.ClassUtil;
import com.almende.util.tokens.TokenStore;

/**
 * The Class HttpService.
 */
public class HttpService implements TransportService {
    private static final Logger LOG = Logger.getLogger(HttpService.class.getCanonicalName());
    private String servletUrl = null;
    private AgentHost host = null;
    private final List<String> protocols = Arrays.asList("http", "https", "web");

    /**
     * Instantiates a new http service.
     * 
     * @param agentHost
     *            the agent host
     */
    public HttpService(final AgentHost agentHost) {
        host = agentHost;
    }

    /**
     * Construct an HttpService This constructor is called when the
     * TransportService is constructed by the AgentHost.
     * 
     * @param agentHost
     *            the agent host
     * @param params
     *            Available parameters: {String} servlet_url
     */
    public HttpService(final AgentHost agentHost, final Map<String, Object> params) {
        host = agentHost;
        if (params != null) {
            setServletUrl((String) params.get("servlet_url"));
            if (params.get("servlet_launcher") != null) {
                String className = (String) params.get("servlet_launcher");
                if (className.equals("JettyLauncher")) {
                    className = "com.almende.eve.transport.http.embed.JettyLauncher";
                }
                try {
                    final Class<?> launcherClass = Class.forName(className);
                    if (!ClassUtil.hasInterface(launcherClass, ServletLauncher.class)) {
                        throw new IllegalArgumentException("ServletLauncher class " + launcherClass.getName()
                                + " must implement " + ServletLauncher.class.getName());
                    }

                    ServletLauncher launcher = (ServletLauncher) launcherClass.newInstance();
                    launcher.add(new AgentServlet(this), URI.create(getServletUrl()), agentHost.getConfig());

                } catch (Exception e) {
                    LOG.log(Level.WARNING, "Failed to load launcher!", e);
                }
            }
        }
    }

    /**
     * Construct an HttpService This constructor is called when the
     * TransportService is constructed by the AgentHost.
     * 
     * @param agentHost
     *            the agent host
     * @param servletUrl
     *            the servlet url
     */
    public HttpService(final AgentHost agentHost, final String servletUrl) {
        host = agentHost;
        setServletUrl(servletUrl);
    }

    /**
     * Set the servlet url for the transport service. This determines the
     * mapping between an agentId and agentUrl.
     * 
     * @param servletUrl
     *            the new servlet url
     */
    private void setServletUrl(final String servletUrl) {
        this.servletUrl = servletUrl;
        if (!this.servletUrl.endsWith("/")) {
            this.servletUrl += "/";
        }
        final int separator = this.servletUrl.indexOf(':');
        if (separator != -1) {
            final String protocol = this.servletUrl.substring(0, separator);
            if (!protocols.contains(protocol)) {
                protocols.add(protocol);
            }
        }
    }

    /**
     * Return the configured servlet url corresponding to this transport
     * service. The servlet url is loaded from the parameter servlet_url in the
     * configuration.
     * 
     * @return servletUrl
     */
    public String getServletUrl() {
        return servletUrl;
    }

    /**
     * Retrieve the protocols supported by the transport service. This can be
     * "http" or "https", depending on the configuration.
     * 
     * @return protocols
     */
    @Override
    public List<String> getProtocols() {
        return protocols;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.transport.TransportService#sendAsync(java.net.URI,
     * java.net.URI, byte[], java.lang.String)
     */
    @Override
    public void sendAsync(URI senderUri, URI receiverUri, byte[] message, String tag) throws IOException {
        sendAsync(senderUri, receiverUri, Base64.encodeBase64String(message), tag);
    }

    /**
     * Send a JSON-RPC request to an agent via HTTP.
     * 
     * @param senderUrl
     *            the sender url
     * @param receiverUrl
     *            the receiver url
     * @param message
     *            the message
     * @param tag
     *            the tag
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    @Override
    public void sendAsync(final URI senderUrl, final URI receiverUrl, final String message, final String tag)
            throws IOException {

        host.getPool().execute(new Runnable() {

            @Override
            public void run() {
                HttpPost httpPost = null;
                try {

                    if (tag != null) {
                        // This is a reply to a synchronous inbound call, get
                        // callback
                        // and use it to send the message
                        final AsyncCallbackQueue<String> callbacks = host.getCallbackQueue("HttpTransport",
                                String.class);
                        if (callbacks != null) {
                            final AsyncCallback<String> callback = callbacks.pull(tag);
                            if (callback != null) {
                                callback.onSuccess(message);
                                return;
                            } else {
                                LOG.warning("Tag set, but no callback found! " + callback);
                            }
                        } else {
                            LOG.warning("Tag set, but no callbacks found!");
                        }
                        // Chicken out
                        return;
                    }
                    httpPost = new HttpPost(receiverUrl);
                    // invoke via Apache HttpClient request:
                    httpPost.setEntity(new StringEntity(message));

                    // Add token for HTTP handshake
                    httpPost.addHeader("X-Eve-Token", TokenStore.create().toString());
                    httpPost.addHeader("X-Eve-SenderUrl", senderUrl.toString());
                    final HttpResponse webResp = ApacheHttpClient.get().execute(httpPost);
                    final String result = EntityUtils.toString(webResp.getEntity());
                    if (webResp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                        LOG.warning("Received HTTP Error Status:" + webResp.getStatusLine().getStatusCode() + ":"
                                + webResp.getStatusLine().getReasonPhrase());
                        LOG.warning(result);
                    } else {
                        host.receive(getAgentId(senderUrl), result, receiverUrl, null);
                    }
                } catch (final Exception e) {
                    LOG.log(Level.WARNING, "HTTP roundtrip resulted in exception!", e);
                } finally {
                    if (httpPost != null) {
                        httpPost.reset();
                    }
                }
            }
        });
    }

    /**
     * Get the url of an agent from its id.
     * 
     * @param agentId
     *            the agent id
     * @return agentUrl
     */
    @Override
    public URI getAgentUrl(final String agentId) {
        if (servletUrl != null) {
            try {
                try {
                    return new URI(servletUrl + URLEncoder.encode(agentId, "UTF-8") + "/");
                } catch (final UnsupportedEncodingException e) {
                    return new URI(servletUrl + agentId + "/");
                }
            } catch (URISyntaxException e) {
                LOG.log(Level.WARNING, "Strange, couldn't generate agentUrl:" + agentId, e);
            }
        }
        return null;
    }

    /**
     * Get the id of an agent from its url. If the id cannot be extracted, null
     * is returned. A typical url is "http://myserver.com/agents/agentid/"
     * 
     * @param agentUri
     *            the agent url
     * @return agentId
     */
    @Override
    public String getAgentId(URI agentUri) {
        if (servletUrl != null) {
            String agentUrl = agentUri.toString();
            // add domain when missing
            final String domain = getDomain(agentUrl);
            if (domain.equals("")) {
                // provided url is only containing the path (not the domain)
                agentUrl = getDomain(servletUrl) + agentUrl;
            }

            if (agentUrl.startsWith(servletUrl)) {
                final int separator = agentUrl.indexOf('/', servletUrl.length());
                try {
                    if (separator != -1) {
                        return URLDecoder.decode(agentUrl.substring(servletUrl.length(), separator), "UTF-8");
                    } else {
                        return URLDecoder.decode(agentUrl.substring(servletUrl.length()), "UTF-8");
                    }
                } catch (final UnsupportedEncodingException e) {
                    LOG.log(Level.WARNING, "", e);
                }
            }
        }

        return null;
    }

    /**
     * Get the resource from the end of an agentUrl, for example
     * "http://myserver.com/agents/agentid/index.html" will return "index.html"
     * The method will return null when the provided url does not match the
     * configured url
     * 
     * @param agentUrl
     *            the agent url
     * @return the agent resource
     */
    public String getAgentResource(String agentUrl) {
        if (servletUrl != null) {
            // add domain when missing
            final String domain = getDomain(agentUrl);
            if (domain.equals("")) {
                // provided url is only containing the path (not the domain)
                agentUrl = getDomain(servletUrl) + agentUrl;
            }

            if (agentUrl.startsWith(servletUrl)) {
                final int separator = agentUrl.indexOf('/', servletUrl.length());
                if (separator != -1) {
                    return agentUrl.substring(separator + 1);
                } else {
                    return "";
                }
            }
        }

        return null;
    }

    /**
     * Get the domain part of given url. For example
     * "http://localhost:8080/EveCore/agents/testagent/1/" will return
     * "http://localhost:8080", and "/EveCore/agents/testagent/1/" will return
     * "".
     * 
     * @param url
     *            the url
     * @return domain
     */
    public String getDomain(final String url) {
        final int protocolSeparator = url.indexOf("://");
        if (protocolSeparator != -1) {
            final int fromIndex = (protocolSeparator != -1) ? protocolSeparator + 3 : 0;
            final int pathSeparator = url.indexOf('/', fromIndex);
            if (pathSeparator != -1) {
                return url.substring(0, pathSeparator);
            }
        }
        return "";
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        final Map<String, Object> data = new HashMap<String, Object>();
        data.put("class", this.getClass().getName());
        data.put("servlet_url", servletUrl);
        data.put("protocols", protocols);
        return data.toString();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.almende.eve.transport.TransportService#reconnect(java.lang.String)
     */
    @Override
    public void reconnect(final String agentId) {
        // Nothing todo at this point
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.transport.TransportService#getKey()
     */
    @Override
    public String getKey() {
        return "http://" + (getServletUrl() == null ? "outbound" : getServletUrl());
    }

}