Package com.almende.eve.transport.xmpp

Source Code of com.almende.eve.transport.xmpp.XmppService

package com.almende.eve.transport.xmpp;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.jivesoftware.smack.SmackConfiguration;

import com.almende.eve.agent.AgentHost;
import com.almende.eve.rpc.annotation.Access;
import com.almende.eve.rpc.annotation.AccessType;
import com.almende.eve.rpc.jsonrpc.JSONRPCException;
import com.almende.eve.rpc.jsonrpc.JSONRequest;
import com.almende.eve.rpc.jsonrpc.JSONResponse;
import com.almende.eve.rpc.jsonrpc.jackson.JOM;
import com.almende.eve.state.State;
import com.almende.eve.transport.AsyncCallback;
import com.almende.eve.transport.SyncCallback;
import com.almende.eve.transport.TransportService;
import com.almende.util.EncryptionUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class XmppService implements TransportService {
  private static final String        CONNKEY        = "_XMPP_Connections";
  private AgentHost            agentHost      = null;
  private String              host        = null;
  private Integer              port        = null;
  private String              service        = null;
 
  // xmpp url as key "xmpp:username@host"
  private Map<String, AgentConnection>  connectionsByUrl  = new ConcurrentHashMap<String, AgentConnection>();
  private static List<String>        protocols      = Arrays.asList("xmpp");
 
  private static final Logger        LOG          = Logger.getLogger(XmppService.class
                                    .getSimpleName());
 
  protected XmppService() {
  }
 
  /**
   * Construct an XmppService
   * This constructor is called when the TransportService is constructed
   * by the AgentHost
   *
   * @param params
   *            Available parameters:
   *            {String} host
   *            {Integer} port
   *            {String} serviceName
   *            {String} id
   */
  public XmppService(AgentHost agentHost, Map<String, Object> params) {
    this.agentHost = agentHost;
   
    if (params != null) {
      host = (String) params.get("host");
      port = (Integer) params.get("port");
      service = (String) params.get("service");
    }
   
    init();
  }
 
  /**
   * initialize the settings for the xmpp service
   *
   * @param host
   * @param port
   * @param service
   *            service name
   */
  public XmppService(AgentHost agentHost, String host, Integer port,
      String service) {
    this.agentHost = agentHost;
    this.host = host;
    this.port = port;
    this.service = service;
   
    init();
  }
 
  private ArrayNode getConns(String agentId) throws IOException,
      JSONRPCException {
    State state = agentHost.getStateFactory().get(agentId);
   
    ArrayNode conns = null;
    if (state.containsKey(CONNKEY)) {
      conns = (ArrayNode) JOM.getInstance().readTree(state.get(CONNKEY,String.class));
    }
    return conns;
  }
 
  /**
   * Get the first XMPP url of an agent from its id.
   * If no agent with given id is connected via XMPP, null is returned.
   *
   * @param agentId
   *            The id of the agent
   * @return agentUrl
   */
  @Override
  public String getAgentUrl(String agentId) {
    try {
      ArrayNode conns = getConns(agentId);
      if (conns != null) {
        for (JsonNode conn : conns) {
          ObjectNode params = (ObjectNode) conn;
         
          String encryptedUsername = params.has("username") ? params
              .get("username").textValue() : null;
          String encryptedResource = params.has("resource") ? params
              .get("resource").textValue() : null;
          if (encryptedUsername != null) {
            String username = EncryptionUtil
                .decrypt(encryptedUsername);
            String resource = null;
            if (encryptedResource != null) {
              resource = EncryptionUtil
                  .decrypt(encryptedResource);
            }
            return generateUrl(username, host, resource);
          }
        }
      }
    } catch (Exception e) {
      LOG.log(Level.WARNING, "", e);
    }
    return null;
  }
 
  /**
   * Get the id of an agent from its url.
   * If no agent with given id is connected via XMPP, null is returned.
   *
   * @param agentUrl
   * @return agentId
   */
  @Override
  public String getAgentId(String agentUrl) {
    AgentConnection connection = connectionsByUrl.get(agentUrl);
    if (connection != null) {
      return connection.getAgentId();
    }
    return null;
  }
 
  /**
   * initialize the transport service
   */
  private void init() {
    SmackConfiguration.setPacketReplyTimeout(15000);
  }
 
  /**
   * Get the protocols supported by the XMPPService.
   * Will return an array with one value, "xmpp"
   *
   * @return protocols
   */
  @Override
  public List<String> getProtocols() {
    return protocols;
  }
 
  /**
   * Connect to the configured messaging service (such as XMPP). The service
   * must be configured in the Eve configuration
   *
   * @param agentUrl
   * @param username
   * @param password
   * @param resource
   * @throws IOException
   * @throws JSONRPCException
   * @throws BadPaddingException
   * @throws IllegalBlockSizeException
   * @throws NoSuchPaddingException
   * @throws InvalidKeySpecException
   * @throws NoSuchAlgorithmException
   * @throws InvalidAlgorithmParameterException
   * @throws InvalidKeyException
   */
  @Access(AccessType.UNAVAILABLE)
  public final void connect(String agentId, String username, String password)
      throws InvalidKeyException, InvalidAlgorithmParameterException,
      NoSuchAlgorithmException, InvalidKeySpecException,
      NoSuchPaddingException, IllegalBlockSizeException,
      BadPaddingException, JSONRPCException, IOException {
    String resource = null;
    connect(agentId, username, password, resource);
  }
 
  /**
   * Connect to the configured messaging service (such as XMPP). The service
   * must be configured in the Eve configuration
   *
   * @param agentUrl
   * @param username
   * @param password
   * @param resource
   *            (optional)
   * @throws IOException
   * @throws JSONRPCException
   * @throws BadPaddingException
   * @throws IllegalBlockSizeException
   * @throws NoSuchPaddingException
   * @throws InvalidKeySpecException
   * @throws NoSuchAlgorithmException
   * @throws InvalidAlgorithmParameterException
   * @throws JsonProcessingException
   * @throws InvalidKeyException
   * @throws Exception
   */
  @Access(AccessType.UNAVAILABLE)
  public final void connect(String agentId, String username, String password,
      String resource) throws InvalidKeyException,
      InvalidAlgorithmParameterException, NoSuchAlgorithmException,
      InvalidKeySpecException, NoSuchPaddingException,
      IllegalBlockSizeException, BadPaddingException, JSONRPCException,
      IOException {
    String agentUrl = generateUrl(username, host, resource);
    AgentConnection connection;
    if (connectionsByUrl.containsKey(agentUrl)) {
      LOG.warning("Warning, agent was already connected, reconnecting.");
      connection = connectionsByUrl.get(agentUrl);
    } else {
      // instantiate open the connection
      connection = new AgentConnection(agentHost);
    }
   
    if (username.indexOf('@') > 0) {
      LOG.warning("Warning: Username should not contain a domain! "
          + username);
    }
   
    connection.connect(agentId, host, port, service, username, password,
        resource);
   
    connectionsByUrl.put(agentUrl, connection);
    storeConnection(agentId, username, password, resource);
  }
 
  private void storeConnection(String agentId, String username,
      String password, String resource) throws JSONRPCException,
      IOException, InvalidKeyException,
      InvalidAlgorithmParameterException, NoSuchAlgorithmException,
      InvalidKeySpecException, NoSuchPaddingException,
      IllegalBlockSizeException, BadPaddingException {
   
    State state = agentHost.getStateFactory().get(agentId);
   
    String conns = state.get(CONNKEY,String.class);
    ArrayNode newConns;
    if (conns != null) {
      newConns = (ArrayNode) JOM.getInstance().readTree(conns);
    } else {
      newConns = JOM.createArrayNode();
    }
   
    ObjectNode params = JOM.createObjectNode();
    params.put("username", EncryptionUtil.encrypt(username));
    params.put("password", EncryptionUtil.encrypt(password));
    if (resource != null && !resource.isEmpty()) {
      params.put("resource", EncryptionUtil.encrypt(resource));
    }
    for (JsonNode item : newConns) {
      if (item.get("username").equals(params.get("username"))) {
        return;
      }
    }
    newConns.add(params);
    if (!state.putIfUnchanged(CONNKEY, JOM.getInstance()
        .writeValueAsString(newConns), conns)) {
      // recursive retry
      storeConnection(agentId, username, password, resource);
    }
  }
 
  private void delConnections(String agentId) throws JSONRPCException {
    State state = agentHost.getStateFactory().get(agentId);
    state.remove(CONNKEY);
  }
 
  /**
   * Disconnect the agent from the connected messaging service(s) (if any)
   *
   * @param agentId
   */
  @Access(AccessType.UNAVAILABLE)
  public final void disconnect(String agentId) {
   
    try {
      ArrayNode conns = getConns(agentId);
      if (conns != null) {
        for (JsonNode conn : conns) {
          ObjectNode params = (ObjectNode) conn;
         
          String encryptedUsername = params.has("username") ? params
              .get("username").textValue() : null;
          String encryptedResource = params.has("resource") ? params
              .get("resource").textValue() : null;
          if (encryptedUsername != null) {
            String username = EncryptionUtil
                .decrypt(encryptedUsername);
            String resource = null;
            if (encryptedResource != null) {
              resource = EncryptionUtil
                  .decrypt(encryptedResource);
            }
           
            String url = generateUrl(username, host, resource);
            AgentConnection connection = connectionsByUrl.get(url);
            if (connection != null) {
              connection.disconnect();
              connectionsByUrl.remove(url);
            }
          }
        }
      }
      delConnections(agentId);
    } catch (Exception e) {
      LOG.log(Level.WARNING, "", e);
    }
  }
 
  /**
   * Send a message to an other agent
   *
   * @param url
   * @param request
   * @param response
   */
  @Override
  public JSONResponse send(String senderUrl, String receiver,
      JSONRequest request) throws JSONRPCException {
    SyncCallback<JSONResponse> callback = new SyncCallback<JSONResponse>();
    sendAsync(senderUrl, receiver, request, callback);
    try {
      return callback.get();
    } catch (Exception e) {
      throw new JSONRPCException("Couldn't handle XMPP return.", e);
    }
  }
 
  /**
   * Asynchronously Send a message to an other agent
   *
   * @param url
   * @param request
   * @param callback
   *            with a JSONResponse
   */
  @Override
  public void sendAsync(String senderUrl, String receiver,
      JSONRequest request, AsyncCallback<JSONResponse> callback)
      throws JSONRPCException {
   
    AgentConnection connection = null;
   
    if (senderUrl != null) {
      connection = connectionsByUrl.get(senderUrl);
    }
    if (connection != null) {
      // remove the protocol from the receiver url
      String protocol = "xmpp:";
      if (!receiver.startsWith(protocol)) {
        throw new JSONRPCException("Receiver url must start with '"
            + protocol + "' (receiver='" + receiver + "')");
      }
      // username@domain
      String fullUsername = receiver.substring(protocol.length());
      connection.send(fullUsername, request, callback);
    } else {
      // TODO: use an anonymous xmpp connection when the sender agent has
      // no xmpp connection.
      throw new JSONRPCException("Cannot send an xmpp request, "
          + "agent has no xmpp connection.");
    }
  }
 
  /**
   * Get the url of an xmpp connection "xmpp:username@host"
   *
   * @param username
   * @param host
   * @param resource
   *            optional
   * @return url
   */
  private static String generateUrl(String username, String host,
      String resource) {
    String url = "xmpp:" + username + "@" + host;
    if (resource != null && !resource.isEmpty()) {
      url += "/" + resource;
    }
    return url;
  }
 
  @Override
  public String toString() {
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("class", this.getClass().getName());
    data.put("host", host);
    data.put("port", port);
    data.put("service", service);
    data.put("protocols", protocols);
   
    return data.toString();
  }
 
  @Override
  public void reconnect(String agentId) throws JSONRPCException, IOException {
    ArrayNode conns = getConns(agentId);
    if (conns != null) {
      for (JsonNode conn : conns) {
        ObjectNode params = (ObjectNode) conn;
        LOG.info("Initializing connection:" + agentId + " --> "
            + params);
        try {
          String encryptedUsername = params.has("username") ? params
              .get("username").textValue() : null;
          String encryptedPassword = params.has("password") ? params
              .get("password").textValue() : null;
          String encryptedResource = params.has("resource") ? params
              .get("resource").textValue() : null;
          if (encryptedUsername != null && encryptedPassword != null) {
            String username = EncryptionUtil
                .decrypt(encryptedUsername);
            String password = EncryptionUtil
                .decrypt(encryptedPassword);
            String resource = null;
            if (encryptedResource != null) {
              resource = EncryptionUtil
                  .decrypt(encryptedResource);
            }
            connect(agentId, username, password, resource);
          }
        } catch (Exception e) {
          throw new JSONRPCException("Failed to connect XMPP.", e);
        }
      }
    }
  }
 
  @Override
  public String getKey() {
    return "xmpp://" + host + ":" + port + "/" + service;
  }
}
TOP

Related Classes of com.almende.eve.transport.xmpp.XmppService

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.