com.arpnetworking.tsdcore.sinks.circonus.CirconusClient.java Source code

Java tutorial

Introduction

Here is the source code for com.arpnetworking.tsdcore.sinks.circonus.CirconusClient.java

Source

/**
 * Copyright 2014 Groupon.com
 *
 * 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 com.arpnetworking.tsdcore.sinks.circonus;

import akka.http.javadsl.model.HttpMethods;
import com.arpnetworking.commons.builder.OvalBuilder;
import com.arpnetworking.commons.jackson.databind.ObjectMapperFactory;
import com.arpnetworking.logback.annotations.LogValue;
import com.arpnetworking.steno.LogValueMapFactory;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.arpnetworking.tsdcore.sinks.circonus.api.BrokerListResponse;
import com.arpnetworking.tsdcore.sinks.circonus.api.CheckBundle;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Throwables;
import com.ning.http.client.AsyncHttpClientConfig;
import net.sf.oval.constraint.NotNull;
import play.libs.F;
import play.libs.ws.WSRequest;
import play.libs.ws.WSResponse;
import play.libs.ws.ning.NingWSClient;
import scala.concurrent.ExecutionContext;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.xml.ws.WebServiceException;

/**
 * Async Circonus API client.  Hides the implementation of the HTTP calls.
 *
 * @author Brandon Arp (brandonarp at gmail dot com)
 */
public final class CirconusClient {

    /**
     * Gets the list of brokers from the Circonus API.
     *
     * @return Future with the results.
     */
    public F.Promise<BrokerListResponse> getBrokers() {
        final WSRequest request = _client.url(_uri + BROKERS_URL).setMethod(HttpMethods.GET.value());
        LOGGER.trace().setMessage("Sending get broker request").log();
        return fireRequest(request).map(response -> handleBrokerListResponse(request, response), _executionContext);
    }

    /**
     * Sends a request to store data to an http trap monitor.
     * NOTE: This method is a bit odd to have on the client.  The URI of the httptrap is likely not on the
     * API broker host at all.  However, it doesn't make sense to move this method to another utility
     * class at this time.
     *
     * TODO(vkoskela): This method needs to be renamed since it is used only to send metrics. [ISSUE-?]
     * Furthermore, the method should probably return the deserialized response instead of the ws response.
     *
     * @param data Json data to store.
     * @param httptrapURI Url of the httptrap.
     * @return Future response.
     */
    public F.Promise<WSResponse> sendToHttpTrap(final Map<String, Object> data, final URI httptrapURI) {
        try {
            LOGGER.trace().setMessage("Sending data to httptrap").log();
            final WSRequest request = _client.url(httptrapURI.toString()).setMethod("POST")
                    .setBody(OBJECT_MAPPER.writeValueAsString(data));
            return fireRequest(request).map(response -> handleMetricResponse(request, response), _executionContext);
        } catch (final JsonProcessingException e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * Gets or creates a check bundle in Circonus.
     *
     * @param request Request details.
     * @return Future with the results.
     */
    public F.Promise<CheckBundle> getOrCreateCheckBundle(final CheckBundle request) {
        final String responseBody;
        try {
            responseBody = OBJECT_MAPPER.writeValueAsString(request);
        } catch (final JsonProcessingException e) {
            throw Throwables.propagate(e);
        }

        final WSRequest requestHolder = _client.url(_uri + CHECK_BUNDLE_URL)
                .setQueryParameter("dedupe_params", "display_name,target").setMethod("POST").setBody(responseBody);
        LOGGER.trace().setMessage("Looking up check bundle").addData("cid", request.getCid()).log();
        return fireRequest(requestHolder)
                .map(response -> handleCheckBundleResponse(requestHolder, response, "create"), _executionContext);
    }

    /**
     * Gets a check bundle in Circonus.
     *
     * @param cid The check bundle's cid.
     * @return Future with the results.
     */
    public F.Promise<CheckBundle> getCheckBundle(final String cid) {
        final WSRequest requestHolder = _client.url(_uri + cid).setMethod("GET").setQueryParameter("query_broker",
                "1");
        LOGGER.trace().setMessage("Getting check bundle").addData("cid", cid).log();
        return fireRequest(requestHolder).map(response -> handleCheckBundleResponse(requestHolder, response, "get"),
                _executionContext);
    }

    /**
     * Updates a check bundle in Circonus.
     *
     * @param request Request details.
     * @return Future with the results.
     */
    public F.Promise<CheckBundle> updateCheckBundle(final CheckBundle request) {
        final String responseBody;
        try {
            responseBody = OBJECT_MAPPER.writeValueAsString(request);
        } catch (final JsonProcessingException e) {
            throw Throwables.propagate(e);
        }

        final WSRequest requestHolder = _client.url(_uri + request.getCid()).setMethod("PUT").setBody(responseBody);
        LOGGER.trace().setMessage("Updating check bundle").addData("cid", request.getCid()).log();
        return fireRequest(requestHolder)
                .map(response -> handleCheckBundleResponse(requestHolder, response, "updating"), _executionContext);
    }

    /**
     * Generate a Steno log compatible representation.
     *
     * @return Steno log compatible representation.
     */
    @LogValue
    public Object toLogValue() {
        return LogValueMapFactory.builder(this).put("uri", _uri).put("appName", _appName)
                .put("authToken", _authToken).build();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return toLogValue().toString();
    }

    private static BrokerListResponse handleBrokerListResponse(final WSRequest request, final WSResponse response)
            throws java.io.IOException {
        final String body = response.getBody();
        LOGGER.trace().setMessage("Response from get brokers").addData("response", response).addData("body", body)
                .log();
        if (response.getStatus() / 100 == 2) {
            final List<BrokerListResponse.Broker> brokers = OBJECT_MAPPER.readValue(body,
                    BROKER_LIST_TYPE_REFERENCE);
            return new BrokerListResponse(brokers);
        }
        throw new WebServiceException(String.format(
                "Received non 200 response getting broker list; request=%s, response=%s, responseBody=%s", request,
                response, response.getBody()));
    }

    private static WSResponse handleMetricResponse(final WSRequest request, final WSResponse response) {
        final String body = response.getBody();
        LOGGER.trace().setMessage("Response from posting metric data").addData("response", response)
                .addData("body", body).log();
        if (response.getStatus() / 100 == 2) {
            return response;
        }
        throw new WebServiceException(String.format(
                "Received non 200 response posting metric data; request=%s, response=%s, responseBody=%s", request,
                response, response.getBody()));
    }

    private static CheckBundle handleCheckBundleResponse(final WSRequest request, final WSResponse response,
            final String operation) throws java.io.IOException {
        final String body = response.getBody();
        LOGGER.trace().setMessage("Response from " + operation + " checkbundle").addData("response", response)
                .addData("body", body).log();
        if (response.getStatus() / 100 == 2) {
            return OBJECT_MAPPER.readValue(body, CheckBundle.class);
        }
        throw new WebServiceException(String.format(
                "Received non 200 response " + operation + " checkbundle; request=%s, response=%s, responseBody=%s",
                request, response, response.getBody()));
    }

    private F.Promise<WSResponse> fireRequest(final WSRequest request) {
        return request.setHeader("X-Circonus-Auth-Token", _authToken).setHeader("X-Circonus-App-Name", _appName)
                .setHeader("Accept", "application/json").execute();
    }

    private CirconusClient(final Builder builder) {
        _uri = builder._uri;
        _appName = builder._appName;
        _authToken = builder._authToken;
        _executionContext = builder._executionContext;

        final AsyncHttpClientConfig.Builder clientBuilder = new AsyncHttpClientConfig.Builder();
        clientBuilder.setExecutorService(Executors.newWorkStealingPool(8));
        if (!builder._safeHttps.booleanValue()) {
            clientBuilder.setAcceptAnyCertificate(true);
            clientBuilder.setHostnameVerifier(HOST_NAME_VERIFIER);
        }
        _client = new NingWSClient(clientBuilder.build());
    }

    private final NingWSClient _client;
    private final URI _uri;
    private final String _appName;
    private final String _authToken;
    private final ExecutionContext _executionContext;

    private static final String BROKERS_URL = "/v2/broker";
    private static final String CHECK_BUNDLE_URL = "/v2/check_bundle";
    private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getInstance();
    private static final TypeReference<List<BrokerListResponse.Broker>> BROKER_LIST_TYPE_REFERENCE = new ListBrokerTypeReference();
    private static final HostnameVerifier HOST_NAME_VERIFIER = new UnsafeHostnameVerifier();
    private static final Logger LOGGER = LoggerFactory.getLogger(CirconusClient.class);

    /**
     * Implementation of the builder pattern for a {@link com.arpnetworking.tsdcore.sinks.circonus.CirconusClient}.
     */
    public static final class Builder extends OvalBuilder<CirconusClient> {

        /**
         * Public constructor.
         */
        public Builder() {
            super(CirconusClient::new);
        }

        /**
         * Sets the base URI.
         *
         * @param value the base URI
         * @return this Builder
         */
        public Builder setUri(final URI value) {
            _uri = value;
            return this;
        }

        /**
         * Sets the application name.
         *
         * @param value the name of the application
         * @return this Builder
         */
        public Builder setAppName(final String value) {
            _appName = value;
            return this;
        }

        /**
         * Sets the authentication token.
         *
         * @param value the authentication token
         * @return this Builder
         */
        public Builder setAuthToken(final String value) {
            _authToken = value;
            return this;
        }

        /**
         * Sets the execution context for all callbacks.
         *
         * @param value the execution context for callbacks
         * @return this Builder
         */
        public Builder setExecutionContext(final ExecutionContext value) {
            _executionContext = value;
            return this;
        }

        /**
         * Sets the safety of HTTPS. Optional. Default is true. Setting this to false
         * will accept any certificate and disables the hostname verifier. You may also
         * need to supply the "-Djsse.enableSNIExtension=false" JVM argument to disable
         * SNI.
         *
         * @param value the authentication token
         * @return this Builder
         */
        public Builder setSafeHttps(final Boolean value) {
            _safeHttps = value;
            return this;
        }

        @NotNull
        private URI _uri;
        @NotNull
        private String _appName;
        @NotNull
        private String _authToken;
        @NotNull
        private ExecutionContext _executionContext;
        @NotNull
        private Boolean _safeHttps = true;
    }

    private static final class ListBrokerTypeReference extends TypeReference<List<BrokerListResponse.Broker>> {
    }

    private static final class UnsafeHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(final String s, final SSLSession sslSession) {
            return true;
        }
    }
}