package com.devicehive.shim.kafka.client;
/*
* #%L
* DeviceHive Shim Kafka Implementation
* %%
* Copyright (C) 2016 DataArt
* %%
* 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.
* #L%
*/
import com.devicehive.shim.api.Request;
import com.devicehive.shim.api.RequestType;
import com.devicehive.shim.api.Response;
import com.devicehive.shim.api.client.RpcClient;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
public class KafkaRpcClient implements RpcClient {
private static final Logger logger = LoggerFactory.getLogger(KafkaRpcClient.class);
private String requestTopic;
private String replyToTopic;
private Producer<String, Request> requestProducer;
private RequestResponseMatcher requestResponseMatcher;
private ServerResponseListener responseListener;
public KafkaRpcClient(String requestTopic, String replyToTopic, Producer<String, Request> requestProducer,
RequestResponseMatcher requestResponseMatcher, ServerResponseListener responseListener) {
this.requestTopic = requestTopic;
this.replyToTopic = replyToTopic;
this.requestProducer = requestProducer;
this.requestResponseMatcher = requestResponseMatcher;
this.responseListener = responseListener;
}
@Override
public void start() {
responseListener.startWorkers();
pingServer();
}
@Override
public void call(Request request, Consumer<Response> callback) {
push(request);
requestResponseMatcher.addRequestCallback(request.getCorrelationId(), callback);
}
@Override
public void push(Request request) {
if (request.getBody() == null) {
throw new NullPointerException("Request body must not be null.");
}
request.setReplyTo(replyToTopic);
requestProducer.send(new ProducerRecord<>(requestTopic, request.getPartitionKey(), request),
(recordMetadata, e) -> {
if (e != null) {
logger.error("Send request failed", e);
}
logger.debug("Request {} sent successfully", request.getCorrelationId());
//TODO [rafa] in case sending fails - we need to notify the caller using the callback passed.
});
}
@Override
public void shutdown() {
requestProducer.close();
responseListener.shutdown();
}
private void pingServer() {
Request request = Request.newBuilder().build();
request.setReplyTo(replyToTopic);
request.setType(RequestType.ping);
boolean connected = false;
int attempts = 10;
for (int i = 0; i < attempts; i++) {
logger.info("Ping RpcServer attempt {}", i);
CompletableFuture<Response> pingFuture = new CompletableFuture<>();
requestResponseMatcher.addRequestCallback(request.getCorrelationId(), pingFuture::complete);
requestProducer.send(new ProducerRecord<>(requestTopic, request.getPartitionKey(), request));
Response response = null;
try {
response = pingFuture.get(3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException e) {
logger.error("Exception occured while trying to ping RpcServer ", e);
} catch (TimeoutException e) {
logger.warn("RpcServer didn't respond to ping request");
continue;
} finally {
requestResponseMatcher.removeRequestCallback(request.getCorrelationId());
}
if (response != null && !response.isFailed()) {
connected = true;
break;
}
}
if (connected) {
logger.info("Successfully connected to RpcServer");
} else {
logger.error("Unable to reach out RpcServer in {} attempts", attempts);
throw new RuntimeException("RpcServer is not reachable");
}
}
}