org.jmxtrans.embedded.config.EtcdKVStore.java Source code

Java tutorial

Introduction

Here is the source code for org.jmxtrans.embedded.config.EtcdKVStore.java

Source

/*
 * Copyright (c) 2016 the original author or authors
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package org.jmxtrans.embedded.config;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.URL;
import java.util.StringTokenizer;

import org.apache.commons.io.IOUtils;
import org.jmxtrans.embedded.EmbeddedJmxTransException;
import org.jmxtrans.embedded.util.json.PlaceholderEnabledJsonNodeFactory;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * This is an etcd based KVStore implementation. The connection to etcd is estabilished and closed
 * every time a key is read. This is by design since we don't need to read a lot of keys and we do
 * it at relativly long interval times
 * 
 * @author Simone Zorzetti
 */
public class EtcdKVStore implements KVStore {

    private static final String HTTP_ERR = "ERR";

    private final ObjectMapper mapper;
    {
        mapper = new ObjectMapper();
        mapper.setNodeFactory(new PlaceholderEnabledJsonNodeFactory());
    }

    /**
     * Bean for a simplified etcd node
     */
    public static class EtcdNode {
        private String key;
        private long createdIndex;
        private long modifiedIndex;
        private String value;

        // For TTL keys
        private String expiration;
        private Integer ttl;

        public EtcdNode() {
            super();
        }

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public long getCreatedIndex() {
            return createdIndex;
        }

        public void setCreatedIndex(long createdIndex) {
            this.createdIndex = createdIndex;
        }

        public long getModifiedIndex() {
            return modifiedIndex;
        }

        public void setModifiedIndex(long modifiedIndex) {
            this.modifiedIndex = modifiedIndex;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public String getExpiration() {
            return expiration;
        }

        public void setExpiration(String expiration) {
            this.expiration = expiration;
        }

        public Integer getTtl() {
            return ttl;
        }

        public void setTtl(Integer ttl) {
            this.ttl = ttl;
        }

    }

    /**
     * Bean for a simplified etcd answer (just for GET)
     */
    public static class EtcdResult {
        // General values
        private String action;
        private EtcdNode node;

        // For errors
        private Integer errorCode;
        private String message;
        private String cause;
        private int errorIndex;

        public EtcdResult() {
            super();
        }

        public String getAction() {
            return action;
        }

        public void setAction(String action) {
            this.action = action;
        }

        public EtcdNode getNode() {
            return node;
        }

        public void setNode(EtcdNode node) {
            this.node = node;
        }

        public Integer getErrorCode() {
            return errorCode;
        }

        public void setErrorCode(Integer errorCode) {
            this.errorCode = errorCode;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public String getCause() {
            return cause;
        }

        public void setCause(String cause) {
            this.cause = cause;
        }

        public int getErrorIndex() {
            return errorIndex;
        }

        public void setErrorIndex(int errorIndex) {
            this.errorIndex = errorIndex;
        }

    }

    /**
     *
     */
    public EtcdKVStore() {
        super();
    }

    /**
     * Get a key value from etcd. Returns the key value and the etcd modification index as version
     * 
     * @param KeyURI URI of the key in the form etcd://ipaddr:port/path for an etcd cluster you can
     *        use etcd://[ipaddr1:port1, ipaddr:port2,...]:/path
     * @return a KeyValue object
     * @throws EmbeddedJmxTransException
     * 
     * @see org.jmxtrans.embedded.config.KVStore#getKeyValue(java.lang.String)
     */
    public KeyValue getKeyValue(String KeyURI) throws EmbeddedJmxTransException {

        String etcdURI = KeyURI.substring(0, KeyURI.indexOf("/", 7));
        String key = KeyURI.substring(KeyURI.indexOf("/", 7));
        try {

            return getFromEtcd(makeEtcdBaseUris(etcdURI), key);

        } catch (Throwable t) {
            throw new EmbeddedJmxTransException("Exception reading etcd key '" + KeyURI + "': " + t.getMessage(),
                    t);
        }
    }

    private URL[] makeEtcdBaseUris(String etcdURI) throws EmbeddedJmxTransException {
        String serverList = null;
        try {
            if (etcdURI.indexOf("[") > 0) {
                serverList = etcdURI.substring(etcdURI.indexOf("[") + 1, etcdURI.indexOf("]"));
            } else {
                serverList = etcdURI.substring(7, etcdURI.indexOf("/", 7));
            }

            StringTokenizer st = new StringTokenizer(serverList, ",");
            URL[] result = new URL[st.countTokens()];
            int k = 0;
            while (st.hasMoreTokens()) {
                result[k] = new URL("http://" + st.nextToken().trim());
                k++;
            }

            return result;

        } catch (Exception e) {
            throw new EmbeddedJmxTransException(
                    "Exception buildind etcd server list from: '" + etcdURI + "': " + e.getMessage(), e);
        }

    }

    private KeyValue getFromEtcd(URL[] baseUris, String key) throws EmbeddedJmxTransException {

        String json = null;
        int k = -1;
        while (k < baseUris.length - 1) {
            k++;
            String httpResponse = httpGET(baseUris[k], key);
            if (httpResponse == null) {
                // key not found on etcd server; since it's a cluster no need to try another one
                return null;
            }
            if (!HTTP_ERR.equals(httpResponse)) {
                json = httpResponse;
                break;
            }

        }

        if (json == null) {
            // couldn't get the key from etcd
            return null;
        }

        EtcdResult res = null;
        try {
            res = mapper.readValue(json, EtcdResult.class);
        } catch (Exception e) {
            throw new EmbeddedJmxTransException(
                    "Exception parsing etcd response: '" + json + "': " + e.getMessage(), e);
        }

        if (res.errorCode == null) {
            return new KeyValue(res.node.value, Long.toString(res.node.modifiedIndex));
        } else if (res.errorCode == 100) {
            // key not found
            return null;
        } else {
            throw new EmbeddedJmxTransException("Etcd error reading etcd key '" + key + "': " + res.errorCode);
        }

    }

    private String httpGET(URL base, String key) {

        InputStream is = null;
        HttpURLConnection conn = null;
        String json = null;
        try {
            URL url = new URL(base + "/v2/keys/" + key);
            conn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(2000);
            conn.setReadTimeout(2000);

            conn.connect();
            int respCode = conn.getResponseCode();

            if (respCode == 404) {
                return null;
            } else if (respCode > 400) {
                return HTTP_ERR;
            }

            is = conn.getInputStream();
            String contentEncoding = conn.getContentEncoding() != null ? conn.getContentEncoding() : "UTF-8";
            json = IOUtils.toString(is, contentEncoding);
        } catch (MalformedURLException e) {
            json = HTTP_ERR;
            // nothing to do, try next server
        } catch (ProtocolException e) {
            // nothing to do, try next server
            json = HTTP_ERR;
        } catch (IOException e) {
            // nothing to do, try next server
            json = HTTP_ERR;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // nothing to do, try next server
                }
            }
            if (conn != null) {
                conn.disconnect();
            }
        }

        return json;
    }
}