com.arpnetworking.tsdcore.sinks.HttpPostSink.java Source code

Java tutorial

Introduction

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

Source

/**
 * Copyright 2014 Brandon Arp
 *
 * 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;

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.PoisonPill;
import akka.http.javadsl.model.HttpMethods;
import akka.http.javadsl.model.MediaTypes;
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.model.PeriodicData;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.google.common.collect.Lists;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import net.sf.oval.constraint.Min;
import net.sf.oval.constraint.NotNull;
import org.joda.time.Period;

import java.net.URI;
import java.util.Collection;
import java.util.concurrent.Executors;
import java.util.function.Function;

/**
 * Publishes to an HTTP endpoint. This class is thread safe.
 *
 * @author Brandon Arp (brandonarp at gmail dot com)
 */
public abstract class HttpPostSink extends BaseSink {

    /**
     * {@inheritDoc}
     */
    @Override
    public void recordAggregateData(final PeriodicData data) {
        _sinkActor.tell(new HttpSinkActor.EmitAggregation(data), ActorRef.noSender());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        LOGGER.info().setMessage("Closing sink").addData("sink", getName()).addData("uri", _uri).log();
        _sinkActor.tell(PoisonPill.getInstance(), ActorRef.noSender());
    }

    /**
     * Generate a Steno log compatible representation.
     *
     * @return Steno log compatible representation.
     */
    @LogValue
    @Override
    public Object toLogValue() {
        return LogValueMapFactory.builder(this).put("super", super.toLogValue()).put("actor", _sinkActor)
                .put("uri", _uri).build();
    }

    /**
     * Creates an HTTP request from a serialized data entry. Default is an <code>HttpPost</code> containing
     * serializedData as the body with content type of application/json
     *
     * @param client The http client to build the request with.
     * @param serializedData The serialized data.
     * @return <code>HttpRequest</code> to execute
     */
    protected Request createRequest(final AsyncHttpClient client, final byte[] serializedData) {
        return new RequestBuilder().setUrl(_uri.toString())
                .setHeader("Content-Type", MediaTypes.APPLICATION_JSON.toString()).setBody(serializedData)
                .setMethod(HttpMethods.POST.value()).build();
    }

    /**
     * Create HTTP requests for each serialized data entry. The list is
     * guaranteed to be non-empty.
     *
     * @param client The http client to build the request with.
     * @param periodicData The <code>PeriodicData</code> to be serialized.
     * @return The <code>HttpRequest</code> instance to execute.
     */
    protected Collection<Request> createRequests(final AsyncHttpClient client, final PeriodicData periodicData) {
        final Collection<byte[]> serializedData = serialize(periodicData);
        final Collection<Request> requests = Lists.newArrayListWithExpectedSize(serializedData.size());
        for (final byte[] serializedDatum : serializedData) {
            requests.add(createRequest(client, serializedDatum));
        }
        return requests;
    }

    /**
     * Accessor for the <code>URI</code>.
     *
     * @return The <code>URI</code>.
     */
    protected URI getUri() {
        return _uri;
    }

    /**
     * Serialize the <code>PeriodicData</code> and <code>Condition</code> instances
     * for posting.
     *
     * @param periodicData The <code>PeriodicData</code> to be serialized.
     * @return The serialized representation of <code>PeriodicData</code>.
     */
    protected abstract Collection<byte[]> serialize(final PeriodicData periodicData);

    /**
     * Protected constructor.
     *
     * @param builder Instance of <code>Builder</code>.
     */
    protected HttpPostSink(final Builder<?, ?> builder) {
        super(builder);
        _uri = builder._uri;

        _sinkActor = builder._actorSystem.actorOf(HttpSinkActor.props(CLIENT, this, builder._maximumConcurrency,
                builder._maximumQueueSize, builder._spreadPeriod));
    }

    private final URI _uri;
    private final ActorRef _sinkActor;

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpPostSink.class);
    private static final AsyncHttpClient CLIENT;

    static {
        final AsyncHttpClientConfig.Builder clientConfigBuilder = new AsyncHttpClientConfig.Builder();
        clientConfigBuilder
                .setExecutorService(Executors.newCachedThreadPool((r) -> new Thread(r, "HttpPostSinkWorker")));
        final AsyncHttpClientConfig clientConfig = clientConfigBuilder.build();
        CLIENT = new AsyncHttpClient(clientConfig);
    }

    /**
     * Implementation of abstract builder pattern for <code>HttpPostSink</code>.
     *
     * @param <B> type of the builder
     * @param <S> type of the object to be built
     * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
     */
    public abstract static class Builder<B extends BaseSink.Builder<B, S>, S extends HttpPostSink>
            extends BaseSink.Builder<B, S> {

        /**
         * The <code>URI</code> to post the aggregated data to. Cannot be null.
         *
         * @param value The <code>URI</code> to post the aggregated data to.
         * @return This instance of <code>Builder</code>.
         */
        public B setUri(final URI value) {
            _uri = value;
            return self();
        }

        /**
         * Sets the actor system to create the sink actor in. Required. Cannot be null. Injected by default.
         *
         * @param value the actor system
         * @return this builder
         */
        public B setActorSystem(final ActorSystem value) {
            _actorSystem = value;
            return self();
        }

        /**
         * Sets the maximum concurrency of the http requests. Optional. Cannot be null.
         * Default is 1. Minimum is 1.
         *
         * @param value the maximum concurrency
         * @return this builder
         */
        public B setMaximumConcurrency(final Integer value) {
            _maximumConcurrency = value;
            return self();
        }

        /**
         * Sets the maximum delay before starting to send data to the server. Optional. Cannot be null.
         * Default is 0.
         *
         * @param value the maximum delay before sending new data
         * @return this builder
         */
        public B setSpreadPeriod(final Period value) {
            _spreadPeriod = value;
            return self();
        }

        /**
         * Sets the maximum pending queue size. Optional Cannot be null.
         * Default is 25000. Minimum is 1.
         *
         * @param value the maximum pending queue size
         * @return this builder
         */
        public B setMaximumQueueSize(final Integer value) {
            _maximumQueueSize = value;
            return self();
        }

        /**
         * Protected constructor for subclasses.
         *
         * @param targetConstructor The constructor for the concrete type to be created by this builder.
         */
        protected Builder(final Function<B, S> targetConstructor) {
            super(targetConstructor);
        }

        @NotNull
        private URI _uri;
        @JacksonInject
        @NotNull
        private ActorSystem _actorSystem;
        @NotNull
        @Min(1)
        private Integer _maximumConcurrency = 1;
        @NotNull
        @Min(1)
        private Integer _maximumQueueSize = 25000;
        @NotNull
        private Period _spreadPeriod = Period.ZERO;
    }
}