/**
* Copyright 2015 Confluent Inc.
*
* 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 io.confluent.kafkarest.resources;
import java.util.List;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.UriInfo;
import io.confluent.kafkarest.AvroConsumerState;
import io.confluent.kafkarest.BinaryConsumerState;
import io.confluent.kafkarest.ConsumerManager;
import io.confluent.kafkarest.ConsumerState;
import io.confluent.kafkarest.Context;
import io.confluent.kafkarest.JsonConsumerState;
import io.confluent.kafkarest.UriUtils;
import io.confluent.kafkarest.Versions;
import io.confluent.kafkarest.entities.ConsumerInstanceConfig;
import io.confluent.kafkarest.entities.ConsumerRecord;
import io.confluent.kafkarest.entities.CreateConsumerInstanceResponse;
import io.confluent.kafkarest.entities.TopicPartitionOffset;
import io.confluent.rest.annotations.PerformanceMetric;
@Path("/consumers")
// We include embedded formats here so you can always use these headers when interacting with
// a consumers resource. The few cases where it isn't safe are overridden per-method
@Produces({Versions.KAFKA_V1_JSON_BINARY_WEIGHTED_LOW, Versions.KAFKA_V1_JSON_AVRO_WEIGHTED_LOW,
Versions.KAFKA_V1_JSON_JSON_WEIGHTED_LOW, Versions.KAFKA_V1_JSON_WEIGHTED,
Versions.KAFKA_DEFAULT_JSON_WEIGHTED, Versions.JSON_WEIGHTED})
@Consumes({Versions.KAFKA_V1_JSON_BINARY, Versions.KAFKA_V1_JSON_AVRO, Versions.KAFKA_V1_JSON_JSON,
Versions.KAFKA_V1_JSON, Versions.KAFKA_DEFAULT_JSON, Versions.JSON,
Versions.GENERIC_REQUEST})
public class ConsumersResource {
private final Context ctx;
public ConsumersResource(Context ctx) {
this.ctx = ctx;
}
@POST
@Valid
@Path("/{group}")
@PerformanceMetric("consumer.create")
public CreateConsumerInstanceResponse createGroup(
@javax.ws.rs.core.Context UriInfo uriInfo,
final @PathParam("group") String group,
@Valid ConsumerInstanceConfig config
) {
if (config == null) {
config = new ConsumerInstanceConfig();
}
String instanceId = ctx.getConsumerManager().createConsumer(group, config);
String instanceBaseUri = UriUtils.absoluteUriBuilder(ctx.getConfig(), uriInfo)
.path("instances").path(instanceId).build().toString();
return new CreateConsumerInstanceResponse(instanceId, instanceBaseUri);
}
@POST
@Path("/{group}/instances/{instance}/offsets")
@PerformanceMetric("consumer.commit")
public void commitOffsets(
final @Suspended AsyncResponse asyncResponse,
final @PathParam("group") String group,
final @PathParam("instance") String instance
) {
ctx.getConsumerManager().commitOffsets(group, instance, new ConsumerManager.CommitCallback() {
@Override
public void onCompletion(List<TopicPartitionOffset> offsets, Exception e) {
if (e != null) {
asyncResponse.resume(e);
} else {
asyncResponse.resume(offsets);
}
}
});
}
@DELETE
@Path("/{group}/instances/{instance}")
@PerformanceMetric("consumer.delete")
public void deleteGroup(
final @PathParam("group") String group,
final @PathParam("instance") String instance
) {
ctx.getConsumerManager().deleteConsumer(group, instance);
}
@GET
@Path("/{group}/instances/{instance}/topics/{topic}")
@PerformanceMetric("consumer.topic.read-binary")
@Produces({Versions.KAFKA_V1_JSON_BINARY_WEIGHTED,
Versions.KAFKA_V1_JSON_WEIGHTED,
Versions.KAFKA_DEFAULT_JSON_WEIGHTED,
Versions.JSON_WEIGHTED})
public void readTopicBinary(
final @Suspended AsyncResponse asyncResponse,
final @PathParam("group") String group,
final @PathParam("instance") String instance,
final @PathParam("topic") String topic,
@QueryParam("max_bytes") @DefaultValue("-1") long maxBytes
) {
readTopic(asyncResponse, group, instance, topic, maxBytes, BinaryConsumerState.class);
}
@GET
@Path("/{group}/instances/{instance}/topics/{topic}")
@PerformanceMetric("consumer.topic.read-json")
@Produces({Versions.KAFKA_V1_JSON_JSON_WEIGHTED_LOW})// Using low weight ensures binary is default
public void readTopicJson(
final @Suspended AsyncResponse asyncResponse,
final @PathParam("group") String group,
final @PathParam("instance") String instance,
final @PathParam("topic") String topic,
@QueryParam("max_bytes") @DefaultValue("-1") long maxBytes
) {
readTopic(asyncResponse, group, instance, topic, maxBytes, JsonConsumerState.class);
}
@GET
@Path("/{group}/instances/{instance}/topics/{topic}")
@PerformanceMetric("consumer.topic.read-avro")
@Produces({Versions.KAFKA_V1_JSON_AVRO_WEIGHTED_LOW})// Using low weight ensures binary is default
public void readTopicAvro(
final @Suspended AsyncResponse asyncResponse,
final @PathParam("group") String group,
final @PathParam("instance") String instance,
final @PathParam("topic") String topic,
@QueryParam("max_bytes") @DefaultValue("-1") long maxBytes
) {
readTopic(asyncResponse, group, instance, topic, maxBytes, AvroConsumerState.class);
}
private <KafkaKeyT, KafkaValueT, ClientKeyT, ClientValueT> void readTopic(
final @Suspended AsyncResponse asyncResponse,
final @PathParam("group") String group,
final @PathParam("instance") String instance,
final @PathParam("topic") String topic,
@QueryParam("max_bytes") @DefaultValue("-1") long maxBytes,
Class<? extends ConsumerState<KafkaKeyT, KafkaValueT, ClientKeyT, ClientValueT>>
consumerStateType
) {
maxBytes = (maxBytes <= 0) ? Long.MAX_VALUE : maxBytes;
ctx.getConsumerManager().readTopic(
group, instance, topic, consumerStateType, maxBytes,
new ConsumerManager.ReadCallback<ClientKeyT, ClientValueT>() {
@Override
public void onCompletion(
List<? extends ConsumerRecord<ClientKeyT, ClientValueT>> records,
Exception e
) {
if (e != null) {
asyncResponse.resume(e);
} else {
asyncResponse.resume(records);
}
}
}
);
}
}