com.almende.demo.tuneswarm.ConductorAgent.java Source code

Java tutorial

Introduction

Here is the source code for com.almende.demo.tuneswarm.ConductorAgent.java

Source

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

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import org.joda.time.DateTime;

import com.almende.eve.agent.Agent;
import com.almende.eve.agent.AgentConfig;
import com.almende.eve.scheduling.SyncScheduler;
import com.almende.eve.scheduling.SyncSchedulerConfig;
import com.almende.eve.transform.rpc.annotation.Access;
import com.almende.eve.transform.rpc.annotation.AccessType;
import com.almende.eve.transform.rpc.annotation.Name;
import com.almende.eve.transform.rpc.annotation.Namespace;
import com.almende.eve.transform.rpc.annotation.Optional;
import com.almende.eve.transform.rpc.annotation.Sender;
import com.almende.eve.transport.http.HttpTransportConfig;
import com.almende.eve.transport.ws.WebsocketTransportConfig;
import com.almende.util.callback.AsyncCallback;
import com.almende.util.jackson.JOM;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * The Class ConductorAgent.
 */
@Access(AccessType.PUBLIC)
public class ConductorAgent extends Agent {
    private static final Logger LOG = Logger.getLogger(ConductorAgent.class.getName());
    private static final ConductorAgent SINGLETON = new ConductorAgent();
    private static final Map<Tone, List<ToneAgent>> agents = new HashMap<Tone, List<ToneAgent>>();
    private static final Map<URI, ToneAgent> agents2 = new HashMap<URI, ToneAgent>();
    private static final URI monitor = URI.create("wsclient:monitor");

    /**
     * Gets the agents.
     *
     * @return the agents
     */
    public ArrayList<ToneAgent> getAgents() {
        return new ArrayList<ToneAgent>(agents2.values());
    }

    /**
     * On agents change.
     */
    public void onAgentsChange() {
        try {
            caller.call(monitor, "onAgentsChange", JOM.createObjectNode());
        } catch (IOException e) {
            LOG.warning("Monitor not available?");
        }
    }

    /**
     * On note.
     *
     * @param event
     *            the event
     * @param senderUrl
     *            the sender url
     */
    public void onNote(@Name("toneEvent") ToneEvent event, @Sender String senderUrl) {
        final ObjectNode params = JOM.createObjectNode();
        params.put("start", new DateTime(event.getTimestamp()).toString());
        params.put("duration", event.getDuration());
        String tone = event.getTone();
        if (tone != null) {
            params.put("note", tone);
        } else {
            params.put("note", agents2.get(senderUrl).tone.toString());
        }
        try {
            caller.call(monitor, "onNote", params);
        } catch (IOException e) {
            LOG.warning("Monitor not available?");
        }
    }

    /**
     * The Class ToneAgent.
     */
    class ToneAgent {
        URI address;
        Tone tone;
        String name;
        boolean offline;

        public ToneAgent() {
        }

        public URI getAddress() {
            return address;
        }

        public void setAddress(URI address) {
            this.address = address;
        }

        public Tone getTone() {
            return tone;
        }

        public void setTone(Tone tone) {
            this.tone = tone;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public boolean isOffline() {
            return offline;
        }

        public void setOffline(boolean offline) {
            this.offline = offline;
        }

    }

    private Tone getTone() {
        // Add to existing but empty lists first;
        Entry<Tone, List<ToneAgent>> leastMembers = null;
        for (Entry<Tone, List<ToneAgent>> tone : agents.entrySet()) {
            List<ToneAgent> value = tone.getValue();
            if (value.size() == 0) {
                return tone.getKey();
            }
            if (leastMembers == null || value.size() < leastMembers.getValue().size()) {
                leastMembers = tone;
            }
        }
        // Add missing tones:
        for (Tone tone : Tone.values()) {
            if (!agents.containsKey(tone)) {
                agents.put(tone, new ArrayList<ToneAgent>(3));
                return tone;
            }
        }
        // Double to the leastMembers tone:
        return leastMembers.getKey();
    }

    private String getHostAddress() throws SocketException {
        Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
        while (e.hasMoreElements()) {
            NetworkInterface n = (NetworkInterface) e.nextElement();
            if (!n.isLoopback() && n.isUp() && !n.isVirtual()) {

                Enumeration<InetAddress> ee = n.getInetAddresses();
                while (ee.hasMoreElements()) {
                    InetAddress i = (InetAddress) ee.nextElement();
                    if (i instanceof Inet4Address && !i.isLinkLocalAddress() && !i.isMulticastAddress()) {
                        return i.getHostAddress().trim();
                    }
                }
            }
        }
        return null;
    }

    /**
     * Inits the Conference Cloud Agent.
     */
    public void init() {
        String host;
        try {
            host = getHostAddress() + ":8082";
        } catch (SocketException e) {
            LOG.warning("Couldn't determine ipaddress, defaulting to 10.10.1.105");
            host = "10.10.1.105:8082";
        }
        final String id = "conductor";
        final AgentConfig config = new AgentConfig(id);

        final ArrayNode transports = JOM.createArrayNode();
        final WebsocketTransportConfig serverConfig = new WebsocketTransportConfig();
        serverConfig.setId("conductor");
        serverConfig.setServer(true);
        serverConfig.setAddress("ws://" + host + "/ws/" + id);
        serverConfig.setServletLauncher("JettyLauncher");
        final ObjectNode jettyParms = JOM.createObjectNode();
        jettyParms.put("port", 8082);
        serverConfig.set("jetty", jettyParms);
        transports.add(serverConfig);

        final HttpTransportConfig debugConfig = new HttpTransportConfig();
        debugConfig.setId("conductor");
        debugConfig.setDoAuthentication(false);
        debugConfig.setServletUrl("http://" + host + "/www/");
        debugConfig.setServletClass("com.almende.eve.transport.http.DebugServlet");
        debugConfig.setServletLauncher("JettyLauncher");
        debugConfig.set("jetty", jettyParms);
        transports.add(debugConfig);

        config.setTransport(transports);

        final SyncSchedulerConfig schedulerConfig = new SyncSchedulerConfig();
        config.setScheduler(schedulerConfig);

        setConfig(config);

        final SyncScheduler scheduler = (SyncScheduler) getScheduler();
        scheduler.setCaller(caller);
        LOG.warning("Started Conductor at:" + host);

        schedule("pingAgents", JOM.createObjectNode(), DateTime.now().plus(10000));
    }

    /**
     * Gets the sync scheduler.
     *
     * @return the sync scheduler
     */
    @Namespace("*")
    public SyncScheduler getSyncScheduler() {
        return (SyncScheduler) getScheduler();
    }

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(final String[] args) {
        SINGLETON.init();
    }

    /**
     * Register agent.
     *
     * @param senderUrl
     *            the sender url
     * @param name
     *            the name
     * @return the double
     */
    public double registerAgent(final @Sender String senderUrl, final @Optional @Name("name") String name) {
        final URI address = URI.create(senderUrl);
        if (agents2.containsKey(address)) {
            final ToneAgent agent = agents2.get(address);
            final Tone tone = agent.tone;
            agent.offline = false;
            LOG.warning("Re-registering:" + senderUrl + "(" + name + ")" + " was already tone:" + tone);
            onAgentsChange();
            return tone.getFrequency();
        }
        // New agent
        ToneAgent agent = new ToneAgent();
        agent.address = URI.create(senderUrl);
        agent.tone = getTone();
        agent.name = name;
        agent.offline = false;
        synchronized (agents) {
            List<ToneAgent> value = agents.get(agent.tone);
            value.add(agent);
            agents.put(agent.tone, value);
        }
        agents2.put(address, agent);
        LOG.warning("Registering:" + senderUrl + "(" + name + ")" + " will be tone:" + agent.tone);
        onAgentsChange();
        return agent.tone.getFrequency();
    }

    /**
     * Send tune to agents.
     *
     * @param tune
     *            the tune
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void sendTuneToAgents(@Name("tune") TuneDescription tune) throws IOException {
        final Map<Tone, TuneDescription> tunes = tune.splitToTones();
        final Map<Tone, TuneDescription> plan = new HashMap<Tone, TuneDescription>();
        for (Tone tone : agents.keySet()) {
            final TuneDescription description = tunes.remove(tone);
            if (description != null) {
                LOG.warning("Adding tone:" + tone + " to plan");
                plan.put(tone, description);
            }
        }
        Iterator<TuneDescription> iter = tunes.values().iterator();
        TuneDescription td = null;
        while (iter.hasNext()) {
            boolean progress = false;
            for (Entry<Tone, TuneDescription> item : plan.entrySet()) {
                if (iter.hasNext()) {
                    td = iter.next();
                } else {
                    break;
                }
                LOG.warning("Merging tones:" + JOM.getInstance().valueToTree(td.getTones()) + " to plan:"
                        + JOM.getInstance().valueToTree(item));

                if (item.getValue().tryMerge(td)) {
                    td = null;
                    progress = true;
                }
            }
            if (!progress) {
                break;
            }
        }
        for (Entry<Tone, TuneDescription> item : plan.entrySet()) {
            for (final ToneAgent agent : agents.get(item.getKey())) {
                if (agent.offline) {
                    continue;
                }
                final ObjectNode params = JOM.createObjectNode();
                params.set("description", JOM.getInstance().valueToTree(item.getValue()));
                LOG.warning("Sending:" + agent.address + " storeTune:" + params.toString());
                caller.call(agent.address, "storeTune", params);
            }
        }
    }

    /**
     * Start tune at agents.
     *
     * @param id
     *            the id
     * @param delay
     *            the delay
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void startTuneAtAgents(@Name("tuneId") String id, @Name("delay") Integer delay) throws IOException {
        final Long startTime = getScheduler().now() + delay;
        for (final List<ToneAgent> list : agents.values()) {
            for (final ToneAgent agent : list) {
                if (agent.offline) {
                    continue;
                }
                final ObjectNode params = JOM.createObjectNode();
                params.put("id", id);
                params.put("timestamp", startTime);
                LOG.warning("Sending:" + agent.address + " startTune:" + params.toString());
                caller.call(agent.address, "startTune", params);
            }
        }
    }

    /**
     * Configure agents.
     *
     * @param config
     *            the config
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public void configureAgents(@Name("config") ObjectNode config) throws IOException {
        final ObjectNode params = JOM.createObjectNode();
        params.set("config", config);
        for (final List<ToneAgent> list : agents.values()) {
            for (final ToneAgent agent : list) {
                if (agent.offline) {
                    continue;
                }
                caller.call(agent.address, "configure", params);
            }
        }
    }

    /**
     * Ping agents.
     */
    public void pingAgents() {
        for (final ToneAgent agent : agents2.values()) {
            try {
                caller.call(agent.address, "ping", null, new AsyncCallback<Long>() {

                    @Override
                    public void onSuccess(Long result) {
                        // GOOD, nothing to do!
                        agent.offline = false;
                    }

                    @Override
                    public void onFailure(Exception exception) {
                        // Oops, disable agent in list
                        agent.offline = true;
                    }
                });
            } catch (IOException e) {
                agent.offline = true;
            }
        }
        onAgentsChange();
        schedule("pingAgents", JOM.createObjectNode(), DateTime.now().plus(10000));
    }

    /**
     * Call other agent.
     *
     * @param url
     *            the url
     * @param method
     *            the method
     * @param params
     *            the params
     * @return the object node
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public String callOtherAgent(@Name("url") String url, @Name("method") String method,
            @Name("params") ObjectNode params) throws IOException {
        LOG.warning("Call other agent called:" + url + " " + method + " params:" + params.toString());
        return caller.callSync(URI.create(url), method, params);
    }
}