org.inaetics.pubsub.impl.discovery.etcd.EtcdDiscoveryManager.java Source code

Java tutorial

Introduction

Here is the source code for org.inaetics.pubsub.impl.discovery.etcd.EtcdDiscoveryManager.java

Source

/*******************************************************************************
 * Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 *******************************************************************************/
package org.inaetics.pubsub.impl.discovery.etcd;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.inaetics.pubsub.api.discovery.DiscoveryManager;
import org.inaetics.pubsub.api.pubsub.Publisher;
import org.inaetics.pubsub.api.pubsub.Subscriber;
import org.inaetics.pubsub.impl.utils.Constants;
import org.inaetics.pubsub.impl.utils.Utils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.log.LogService;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class EtcdDiscoveryManager implements DiscoveryManager, ManagedService {
    public final static String SERVICE_PID = EtcdDiscoveryManager.class.getName();
    public final static String PUBSUB_ROOT_PATH = "/pubsub/";
    public final static String PUBSUB_PUBLISHER_PATH = PUBSUB_ROOT_PATH + "publisher/";
    public final static String PUBSUB_SUBSCRIBER_PATH = PUBSUB_ROOT_PATH + "subscriber/";

    private final Set<Map<String, String>> publishers = Collections.synchronizedSet(new HashSet<>());
    private final Set<Map<String, String>> subscribers = Collections.synchronizedSet(new HashSet<>());

    private volatile int TTL = 15;
    private volatile int TTLRefresh = 10;
    private volatile boolean stopped = false;
    private String etcdUrl = "http://localhost:2379/v2/keys";
    private EtcdWrapper etcdWrapper;
    private ExecutorService executor;
    private ResponseListener responseListener;
    private TTLRefresher refresher;;
    private BundleContext myBundleContext;
    private String localFrameworkUUID;
    private volatile LogService m_LogService;

    @Override
    public void announcePublisher(Map<String, String> properties) {
        publishers.add(properties);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    etcdWrapper.put(getPublisherEtcdPath(properties), mapToJSON(properties), TTL);
                } catch (IOException e) {
                    m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
                }
            }
        });

    }

    @Override
    public void removePublisher(Map<String, String> properties) {
        publishers.remove(properties);

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    etcdWrapper.delete(getPublisherEtcdPath(properties));
                } catch (IOException e) {
                    m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
                }
            }
        });
    }

    @Override
    public void announceSubscriber(Map<String, String> properties) {
        subscribers.add(properties);

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    etcdWrapper.put(getSubscriberEtcdPath(properties), mapToJSON(properties), TTL);
                } catch (IOException e) {
                    m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
                }
            }
        });
    }

    @Override
    public void removeSubscriber(Map<String, String> properties) {
        subscribers.remove(properties);

        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    etcdWrapper.delete(getSubscriberEtcdPath(properties));
                } catch (IOException e) {
                    m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
                }
            }
        });
    }

    @Override
    public List<Map<String, String>> getCurrentSubscriberEndPoints() {
        List<Map<String, String>> subscribers = new ArrayList<>();
        try {
            JsonNode response = etcdWrapper.get(PUBSUB_SUBSCRIBER_PATH, false, true, -1);
            List<JsonNode> nodes = getRecursivelyFromDirectory(response.get("node"));
            for (JsonNode node : nodes) {
                subscribers.add(getValueMap(node));
            }
        } catch (IOException e) {
            m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
        }
        return subscribers;
    }

    @Override
    public List<Map<String, String>> getCurrentPublisherEndPoints() {
        List<Map<String, String>> publishers = new ArrayList<>();
        try {
            JsonNode response = etcdWrapper.get(PUBSUB_PUBLISHER_PATH, false, true, -1);
            List<JsonNode> nodes = getRecursivelyFromDirectory(response.get("node"));
            for (JsonNode node : nodes) {
                publishers.add(getValueMap(node));
            }
        } catch (IOException e) {
            m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
        }
        return publishers;
    }

    private class TTLRefresher extends Thread {
        private EtcdWrapper wrapper;

        public TTLRefresher(String etcdUrl) {
            wrapper = new EtcdWrapper(etcdUrl);
        }

        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    synchronized (EtcdDiscoveryManager.class) {
                        for (Map<String, String> subscriber : subscribers) {
                            wrapper.refreshTTL(getSubscriberEtcdPath(subscriber), TTL);
                        }
                        for (Map<String, String> publisher : publishers) {
                            wrapper.refreshTTL(getPublisherEtcdPath(publisher), TTL);
                        }
                    }
                    Thread.sleep(TTLRefresh * 1000);
                } catch (IOException | InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    return;
                }
            }
        }

    }

    public String getSubscriberEtcdPath(Map<String, String> info) {
        String serviceID = info.get(SERVICE_ID);
        String topic = info.get(Subscriber.PUBSUB_TOPIC);
        return PUBSUB_SUBSCRIBER_PATH + topic + "/" + localFrameworkUUID + "-" + serviceID;
    }

    public String getPublisherEtcdPath(Map<String, String> info) {
        String serviceID = info.get(SERVICE_ID);
        String topic = info.get(Publisher.PUBSUB_TOPIC);
        return PUBSUB_PUBLISHER_PATH + topic + "/" + localFrameworkUUID + "-" + serviceID;
    }

    protected final void start() {
        myBundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
        localFrameworkUUID = Utils.getFrameworkUUID(myBundleContext);

        System.out.println("STARTED " + this.getClass().getName());

        etcdWrapper = new EtcdWrapper(etcdUrl);
        executor = Executors.newCachedThreadPool();
        refresher = new TTLRefresher(etcdUrl);
        refresher.start();
        responseListener = new ResponseListener();
        stopped = false;

        executor.execute(new Runnable() {
            @Override
            public void run() {
                initDiscovery();
            }
        });
    }

    public void stopDiscovery() {
        stopped = true;
        executor.shutdownNow();
        etcdWrapper.stop();
        refresher.interrupt();
    }

    private class ResponseListener implements EtcdCallback {

        @Override
        public void onResult(JsonNode result) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    pubSubChanged(result);
                }
            }).start();
            ;

        }

        @Override
        public void onException(Exception exception) {
            if (stopped) {
                System.out.println("Stopped " + this.getClass().getSimpleName());
                return;
            } else {
                System.out.println("Restarting " + this.getClass().getSimpleName());
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        initDiscovery();
                    }
                }).start();
            }
        }
    }

    private void pubSubChanged(JsonNode response) {
        try {
            long index = response.get("node").get("modifiedIndex").asLong();
            if (!isEtcdDirectory(response)) {
                if (response.get("action").asText().equals("set")) {
                    if (response.has("prevNode")) {
                        // remove old
                        JsonNode prevNode = response.get("prevNode");
                        informTopologyManagerOfDelete(prevNode);
                    }
                    JsonNode newNode = response.get("node");
                    informTopologyManagerOfDiscover(newNode);

                } else if (response.get("action").asText().equals("delete")
                        || response.get("action").asText().equals("expire")) {
                    // remove
                    JsonNode prevNode = response.get("prevNode");
                    informTopologyManagerOfDelete(prevNode);
                }
            }

            setDirectoryWatch(index + 1);
        } catch (Throwable e) {
            m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
        }

    }

    private boolean isEtcdDirectory(JsonNode node) {
        if (node.has("dir") && node.get("dir").asBoolean()) {
            return true;
        } else {
            return false;
        }

    }

    private void initDiscovery() {
        try {
            try {
                etcdWrapper.get(PUBSUB_ROOT_PATH, false, true, -1);
            } catch (FileNotFoundException e) {
                etcdWrapper.createDirectory(PUBSUB_ROOT_PATH);
            }

            JsonNode response = etcdWrapper.get(PUBSUB_ROOT_PATH, false, true, -1);
            long index = response.get("EtcdIndex").asLong();
            List<JsonNode> nodes = getRecursivelyFromDirectory(response.get("node"));
            for (JsonNode node : nodes) {
                informTopologyManagerOfDiscover(node);
            }
            setDirectoryWatch(index + 1);
        } catch (Throwable e) {
            e.printStackTrace();
            m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
        }
    }

    private List<JsonNode> getRecursivelyFromDirectory(JsonNode node) {
        JsonNode nodes = node.get("nodes");

        List<JsonNode> resultNodes = new ArrayList<>();

        if (nodes != null) {
            for (int i = 0;; i++) {
                if (nodes.has(i)) {
                    JsonNode foundNode = nodes.get(i);
                    if (isEtcdDirectory(foundNode)) {
                        resultNodes.addAll(getRecursivelyFromDirectory(foundNode));
                    } else {
                        resultNodes.add(foundNode);
                    }
                } else {
                    break;
                }
            }
        }
        return resultNodes;
    }

    /**
     * Wait for a change in etcd with the specified index
     * 
     * @param index
     */
    private void setDirectoryWatch(long index) {
        etcdWrapper.waitForChange(PUBSUB_ROOT_PATH, true, index, responseListener);
    }

    /**
     * Get the Map from the JsonNode.
     * 
     * @param node
     * @return
     */
    private Map<String, String> getValueMap(JsonNode node) {
        if (!isEtcdDirectory(node)) {
            try {
                String json = node.get("value").asText();
                ObjectMapper mapper = new ObjectMapper();
                Map<String, String> map = mapper.readValue(json, new TypeReference<Map<String, String>>() {
                });
                return map;
            } catch (Exception e) {
                m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
            }
        }
        return null;
    }

    private void informTopologyManagerOfDiscover(JsonNode node) {
        Map<String, String> info = getValueMap(node);
        if (info != null) {
            sendEvent(info, Constants.DISCOVERY_EVENT_TYPE_CREATED);
        }
    }

    private void informTopologyManagerOfDelete(JsonNode node) {
        Map<String, String> info = getValueMap(node);
        if (info != null) {
            sendEvent(info, Constants.DISCOVERY_EVENT_TYPE_DELETED);
        }
    }

    /**
     * Send an event to the TopologyManager.
     * 
     * @param info
     * @param eventType
     */
    private void sendEvent(Map<String, String> info, String eventType) {
        ServiceReference ref = myBundleContext.getServiceReference(EventAdmin.class.getName());
        if (ref != null) {
            EventAdmin eventAdmin = (EventAdmin) myBundleContext.getService(ref);

            Dictionary<String, Object> properties = new Hashtable<>();
            properties.put(Constants.DISCOVERY_EVENT_TYPE, eventType);
            properties.put(Constants.DISCOVERY_INFO, info);

            Event reportGeneratedEvent = new Event(Constants.DISCOVERY_EVENT, properties);

            eventAdmin.sendEvent(reportGeneratedEvent);

        }

    }

    protected final void stop() throws Exception {
        stopDiscovery();
        System.out.println("STOPPED " + this.getClass().getName());
    }

    void destroy() {
        System.out.println("DESTROYED " + this.getClass().getName());
    }

    /**
     * Implementation of ManagedService
     */
    @Override
    public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
        if (properties != null) {
            if (properties.get("url") != null) {
                this.etcdUrl = (String) properties.get("url");
            }
            if (properties.get("TTL") != null) {
                this.TTL = (Integer) properties.get("TTL");
            }
            if (properties.get("TTLRefresh") != null) {
                this.TTLRefresh = (Integer) properties.get("TTLRefresh");
            }
        }

    }

    private String mapToJSON(Map<String, String> map) {
        try {
            return new ObjectMapper().writeValueAsString(map);
        } catch (JsonProcessingException e) {
            m_LogService.log(LogService.LOG_ERROR, e.getMessage(), e);
        }
        return "";
    }
}