com.axibase.tsd.client.PlainStreamingSender Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of atsd-api-java Show documentation
Show all versions of atsd-api-java Show documentation
The ATSD Client for Java enables Java developers to easily read
and write statistics and metadata from Axibase Time-Series Database. Build reporting,
analytics, and alerting solutions with minimal effort.
/*
* Copyright 2016 Axibase Corporation or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* https://www.axibase.com/atsd/axibase-apache-2.0.pdf
*
* or in the "license" file accompanying this file. This file 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.axibase.tsd.client;
import com.axibase.tsd.model.system.ClientConfiguration;
import com.axibase.tsd.network.MarkerCommand;
import com.axibase.tsd.network.PlainCommand;
import com.axibase.tsd.util.AtsdUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.glassfish.jersey.SslConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import static com.axibase.tsd.util.AtsdUtil.MARKER_KEYWORD;
class PlainStreamingSender extends AbstractHttpEntity implements Runnable {
private static final Logger log = LoggerFactory.getLogger(PlainStreamingSender.class);
private static final int SMALL = 64;
private String url;
private CountDownLatch latch = new CountDownLatch(1);
private CloseableHttpClient httpClient;
private BlockingQueue messages;
private ConcurrentMap> markerToMessages = new ConcurrentHashMap<>();
private volatile SenderState state = SenderState.NEW;
private final long pingTimeoutMillis;
private long lastMessageTime;
private CloseableHttpResponse response;
private final ClientConfiguration clientConfiguration;
private PoolingHttpClientConnectionManager connectionManager;
public PlainStreamingSender(ClientConfiguration clientConfiguration, PlainStreamingSender old) {
this.clientConfiguration = clientConfiguration;
this.url = clientConfiguration.getDataUrl();
this.pingTimeoutMillis = clientConfiguration.getPingTimeoutMillis();
if (old != null) {
messages = old.messages;
markerToMessages = old.markerToMessages;
log.info("Reborn network commands sender using previous messages, size: {}", messages.size());
}
}
public void send(PlainCommand plainCommand) {
try {
latch.await();
} catch (InterruptedException e) {
log.error("Initialization error:", e);
}
if (state != SenderState.WORKING) {
throw new IllegalStateException("Could not send command using incorrect sender");
}
String text = plainCommand.compose();
if (StringUtils.isBlank(text)) {
log.error("Command is empty");
return;
}
if (!text.endsWith("\n")) {
text = text + "\n";
}
messages.add(text);
log.debug("Message is added to queue, queue size = {}", messages.size());
}
@Override
public boolean isRepeatable() {
return false;
}
@Override
public boolean isChunked() {
return false;
}
@Override
public long getContentLength() {
return -1;
}
@Override
public InputStream getContent() throws IOException, IllegalStateException {
return null;
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
String marker = null;
while (state == SenderState.WORKING) {
String message = null;
try {
message = messages.poll(pingTimeoutMillis, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (state == SenderState.WORKING) {
log.error("Could not poll message from queue", e);
}
}
try {
if (message != null) {
if (!clientConfiguration.isSkipStreamingControl() &&
marker == null && !message.startsWith(MARKER_KEYWORD)
) {
MarkerCommand markerCommand = new MarkerCommand();
marker = markerCommand.getMarker();
write(outputStream, markerCommand.compose());
}
log.debug("Write message: {}", message);
write(outputStream, message);
if (!clientConfiguration.isSkipStreamingControl()) {
if (message.startsWith(MARKER_KEYWORD)) {
marker = StringUtils.removeStart(message, MARKER_KEYWORD).trim();
if (StringUtils.isBlank(marker)) {
throw new IllegalArgumentException("Bad marker message: " + message);
}
} else {
add(marker, message);
}
}
lastMessageTime = System.currentTimeMillis();
}
} catch (IOException e) {
log.error("Sender is broken, close it. Could not send message: {}", message, e);
messages.add(message);
close();
return;
}
if (lastMessageTime - System.currentTimeMillis() > pingTimeoutMillis) {
write(outputStream, AtsdUtil.PING_COMMAND);
if (!clientConfiguration.isSkipStreamingControl()) {
add(marker, AtsdUtil.PING_COMMAND);
}
lastMessageTime = System.currentTimeMillis();
}
}
}
private void write(OutputStream outputStream, String text) throws IOException {
outputStream.write(text.getBytes());
outputStream.flush();
}
private void add(String marker, String message) {
if (clientConfiguration.isSkipStreamingControl()) {
log.error("Could not add message to marker, because streaming control is skipped, marker = {}, message= {}",
marker, message);
throw new IllegalStateException("Could not add message to marker during skipped streaming control");
}
List stored = markerToMessages.get(marker);
if (stored == null) {
stored = new ArrayList();
final List prev = markerToMessages.putIfAbsent(marker, stored);
stored = prev == null ? stored : prev;
}
stored.add(message);
}
@Override
public boolean isStreaming() {
return true;
}
@Override
public void run() {
if (messages == null) {
messages = new LinkedBlockingQueue();
}
HttpPost httpPost = null;
try {
SslConfigurator sslConfig = SslConfigurator.newInstance().securityProtocol("SSL");
connectionManager = HttpClient.createConnectionManager(clientConfiguration, sslConfig);
connectionManager.setDefaultConnectionConfig(ConnectionConfig.custom().setBufferSize(SMALL).build());
httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
httpPost = new HttpPost(fullUrl());
httpPost.setHeader("Authorization", "Basic " + DatatypeConverter.printBase64Binary(
(clientConfiguration.getUsername() + ":" + clientConfiguration.getPassword()).getBytes()
));
httpPost.setEntity(new BufferedHttpEntity(this));
} catch (IOException e) {
log.error("Could not create http client: ", e);
latch.countDown();
close();
return;
}
try {
log.info("Start writing commands to {}", fullUrl());
state = SenderState.WORKING;
latch.countDown();
response = httpClient.execute(httpPost);
} catch (IOException e) {
log.error("Could not execute HTTP POST: {}", httpPost, e);
} finally {
log.info("Http post execution is finished, close sender");
close();
}
}
public void close() {
if (state == SenderState.CLOSED) {
return;
}
log.info("Stop writing commands to {}", fullUrl());
if (response != null) {
try {
response.close();
} catch (IOException e) {
log.error("Could not close response: {}", response, e);
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
log.error("Could not close client: {}", httpClient, e);
}
}
if (connectionManager != null) {
connectionManager.close();
}
state = SenderState.CLOSED;
}
private String fullUrl() {
return url + "/command";
}
public boolean isWorking() {
return state == SenderState.WORKING;
}
public boolean isClosed() {
return state == SenderState.CLOSED;
}
public Map> getMarkerToMessages() {
return markerToMessages;
}
private enum SenderState {
NEW,
WORKING,
CLOSED
}
}