/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.kafka.common.requests;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.UnsupportedVersionException;
import org.apache.kafka.common.protocol.ApiKeys;
import org.apache.kafka.common.protocol.Errors;
import org.apache.kafka.common.protocol.types.Struct;
import org.apache.kafka.common.utils.CollectionUtils;
import org.apache.kafka.common.utils.Utils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OffsetFetchRequest extends AbstractRequest {
private static final String GROUP_ID_KEY_NAME = "group_id";
private static final String TOPICS_KEY_NAME = "topics";
// topic level field names
private static final String TOPIC_KEY_NAME = "topic";
private static final String PARTITIONS_KEY_NAME = "partitions";
// partition level field names
private static final String PARTITION_KEY_NAME = "partition";
public static class Builder extends AbstractRequest.Builder<OffsetFetchRequest> {
private static final List<TopicPartition> ALL_TOPIC_PARTITIONS = null;
private final String groupId;
private final List<TopicPartition> partitions;
public Builder(String groupId, List<TopicPartition> partitions) {
super(ApiKeys.OFFSET_FETCH);
this.groupId = groupId;
this.partitions = partitions;
}
public static Builder allTopicPartitions(String groupId) {
return new Builder(groupId, ALL_TOPIC_PARTITIONS);
}
public boolean isAllTopicPartitions() {
return this.partitions == ALL_TOPIC_PARTITIONS;
}
@Override
public OffsetFetchRequest build(short version) {
if (isAllTopicPartitions() && version < 2)
throw new UnsupportedVersionException("The broker only supports OffsetFetchRequest " +
"v" + version + ", but we need v2 or newer to request all topic partitions.");
return new OffsetFetchRequest(groupId, partitions, version);
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder();
bld.append("(type=OffsetFetchRequest, ").
append("groupId=").append(groupId).
append(", partitions=").append(Utils.join(partitions, ",")).
append(")");
return bld.toString();
}
}
private final String groupId;
private final List<TopicPartition> partitions;
public static OffsetFetchRequest forAllPartitions(String groupId) {
return new OffsetFetchRequest.Builder(groupId, null).build((short) 2);
}
// v0, v1, and v2 have the same fields.
private OffsetFetchRequest(String groupId, List<TopicPartition> partitions, short version) {
super(version);
this.groupId = groupId;
this.partitions = partitions;
}
public OffsetFetchRequest(Struct struct, short version) {
super(version);
Object[] topicArray = struct.getArray(TOPICS_KEY_NAME);
if (topicArray != null) {
partitions = new ArrayList<>();
for (Object topicResponseObj : struct.getArray(TOPICS_KEY_NAME)) {
Struct topicResponse = (Struct) topicResponseObj;
String topic = topicResponse.getString(TOPIC_KEY_NAME);
for (Object partitionResponseObj : topicResponse.getArray(PARTITIONS_KEY_NAME)) {
Struct partitionResponse = (Struct) partitionResponseObj;
int partition = partitionResponse.getInt(PARTITION_KEY_NAME);
partitions.add(new TopicPartition(topic, partition));
}
}
} else
partitions = null;
groupId = struct.getString(GROUP_ID_KEY_NAME);
}
public OffsetFetchResponse getErrorResponse(Errors error) {
return getErrorResponse(AbstractResponse.DEFAULT_THROTTLE_TIME, error);
}
public OffsetFetchResponse getErrorResponse(int throttleTimeMs, Errors error) {
short versionId = version();
Map<TopicPartition, OffsetFetchResponse.PartitionData> responsePartitions = new HashMap<>();
if (versionId < 2) {
for (TopicPartition partition : this.partitions) {
responsePartitions.put(partition, new OffsetFetchResponse.PartitionData(
OffsetFetchResponse.INVALID_OFFSET,
OffsetFetchResponse.NO_METADATA,
error));
}
}
switch (versionId) {
case 0:
case 1:
case 2:
return new OffsetFetchResponse(error, responsePartitions);
case 3:
return new OffsetFetchResponse(throttleTimeMs, error, responsePartitions);
default:
throw new IllegalArgumentException(String.format("Version %d is not valid. Valid versions for %s are 0 to %d",
versionId, this.getClass().getSimpleName(), ApiKeys.OFFSET_FETCH.latestVersion()));
}
}
@Override
public OffsetFetchResponse getErrorResponse(int throttleTimeMs, Throwable e) {
return getErrorResponse(throttleTimeMs, Errors.forException(e));
}
public String groupId() {
return groupId;
}
public List<TopicPartition> partitions() {
return partitions;
}
public static OffsetFetchRequest parse(ByteBuffer buffer, short version) {
return new OffsetFetchRequest(ApiKeys.OFFSET_FETCH.parseRequest(version, buffer), version);
}
public boolean isAllPartitions() {
return partitions == null;
}
@Override
protected Struct toStruct() {
Struct struct = new Struct(ApiKeys.OFFSET_FETCH.requestSchema(version()));
struct.set(GROUP_ID_KEY_NAME, groupId);
if (partitions != null) {
Map<String, List<Integer>> topicsData = CollectionUtils.groupDataByTopic(partitions);
List<Struct> topicArray = new ArrayList<>();
for (Map.Entry<String, List<Integer>> entries : topicsData.entrySet()) {
Struct topicData = struct.instance(TOPICS_KEY_NAME);
topicData.set(TOPIC_KEY_NAME, entries.getKey());
List<Struct> partitionArray = new ArrayList<>();
for (Integer partitionId : entries.getValue()) {
Struct partitionData = topicData.instance(PARTITIONS_KEY_NAME);
partitionData.set(PARTITION_KEY_NAME, partitionId);
partitionArray.add(partitionData);
}
topicData.set(PARTITIONS_KEY_NAME, partitionArray.toArray());
topicArray.add(topicData);
}
struct.set(TOPICS_KEY_NAME, topicArray.toArray());
} else
struct.set(TOPICS_KEY_NAME, null);
return struct;
}
}