/* * 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.errors.UnsupportedVersionException; import org.apache.kafka.common.protocol.ApiKeys; import org.apache.kafka.common.protocol.types.Struct; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class CreateTopicsRequest extends AbstractRequest { private static final String REQUESTS_KEY_NAME = "create_topic_requests"; private static final String TIMEOUT_KEY_NAME = "timeout"; private static final String VALIDATE_ONLY_KEY_NAME = "validate_only"; private static final String TOPIC_KEY_NAME = "topic"; private static final String NUM_PARTITIONS_KEY_NAME = "num_partitions"; private static final String REPLICATION_FACTOR_KEY_NAME = "replication_factor"; private static final String REPLICA_ASSIGNMENT_KEY_NAME = "replica_assignment"; private static final String REPLICA_ASSIGNMENT_PARTITION_ID_KEY_NAME = "partition_id"; private static final String REPLICA_ASSIGNMENT_REPLICAS_KEY_NAME = "replicas"; private static final String CONFIG_KEY_KEY_NAME = "config_name"; private static final String CONFIG_VALUE_KEY_NAME = "config_value"; private static final String CONFIGS_KEY_NAME = "config_entries"; public static final class TopicDetails { public final int numPartitions; public final short replicationFactor; public final Map<Integer, List<Integer>> replicasAssignments; public final Map<String, String> configs; private TopicDetails(int numPartitions, short replicationFactor, Map<Integer, List<Integer>> replicasAssignments, Map<String, String> configs) { this.numPartitions = numPartitions; this.replicationFactor = replicationFactor; this.replicasAssignments = replicasAssignments; this.configs = configs; } public TopicDetails(int partitions, short replicationFactor, Map<String, String> configs) { this(partitions, replicationFactor, Collections.<Integer, List<Integer>>emptyMap(), configs); } public TopicDetails(int partitions, short replicationFactor) { this(partitions, replicationFactor, Collections.<String, String>emptyMap()); } public TopicDetails(Map<Integer, List<Integer>> replicasAssignments, Map<String, String> configs) { this(NO_NUM_PARTITIONS, NO_REPLICATION_FACTOR, replicasAssignments, configs); } public TopicDetails(Map<Integer, List<Integer>> replicasAssignments) { this(replicasAssignments, Collections.<String, String>emptyMap()); } @Override public String toString() { StringBuilder bld = new StringBuilder(); bld.append("(numPartitions=").append(numPartitions). append(", replicationFactor=").append(replicationFactor). append(", replicasAssignments=").append(replicasAssignments). append(", configs=").append(configs). append(")"); return bld.toString(); } } public static class Builder extends AbstractRequest.Builder<CreateTopicsRequest> { private final Map<String, TopicDetails> topics; private final int timeout; private final boolean validateOnly; // introduced in V1 public Builder(Map<String, TopicDetails> topics, int timeout) { this(topics, timeout, false); } public Builder(Map<String, TopicDetails> topics, int timeout, boolean validateOnly) { super(ApiKeys.CREATE_TOPICS); this.topics = topics; this.timeout = timeout; this.validateOnly = validateOnly; } @Override public CreateTopicsRequest build(short version) { if (validateOnly && version == 0) throw new UnsupportedVersionException("validateOnly is not supported in version 0 of " + "CreateTopicsRequest"); return new CreateTopicsRequest(topics, timeout, validateOnly, version); } @Override public String toString() { StringBuilder bld = new StringBuilder(); bld.append("(type=CreateTopicsRequest"). append(", topics=").append(topics). append(", timeout=").append(timeout). append(", validateOnly=").append(validateOnly). append(")"); return bld.toString(); } } private final Map<String, TopicDetails> topics; private final Integer timeout; private final boolean validateOnly; // introduced in V1 // Set to handle special case where 2 requests for the same topic exist on the wire. // This allows the broker to return an error code for these topics. private final Set<String> duplicateTopics; public static final int NO_NUM_PARTITIONS = -1; public static final short NO_REPLICATION_FACTOR = -1; private CreateTopicsRequest(Map<String, TopicDetails> topics, Integer timeout, boolean validateOnly, short version) { super(version); this.topics = topics; this.timeout = timeout; this.validateOnly = validateOnly; this.duplicateTopics = Collections.emptySet(); } public CreateTopicsRequest(Struct struct, short version) { super(version); Object[] requestStructs = struct.getArray(REQUESTS_KEY_NAME); Map<String, TopicDetails> topics = new HashMap<>(); Set<String> duplicateTopics = new HashSet<>(); for (Object requestStructObj : requestStructs) { Struct singleRequestStruct = (Struct) requestStructObj; String topic = singleRequestStruct.getString(TOPIC_KEY_NAME); if (topics.containsKey(topic)) duplicateTopics.add(topic); int numPartitions = singleRequestStruct.getInt(NUM_PARTITIONS_KEY_NAME); short replicationFactor = singleRequestStruct.getShort(REPLICATION_FACTOR_KEY_NAME); //replica assignment Object[] assignmentsArray = singleRequestStruct.getArray(REPLICA_ASSIGNMENT_KEY_NAME); Map<Integer, List<Integer>> partitionReplicaAssignments = new HashMap<>(assignmentsArray.length); for (Object assignmentStructObj : assignmentsArray) { Struct assignmentStruct = (Struct) assignmentStructObj; Integer partitionId = assignmentStruct.getInt(REPLICA_ASSIGNMENT_PARTITION_ID_KEY_NAME); Object[] replicasArray = assignmentStruct.getArray(REPLICA_ASSIGNMENT_REPLICAS_KEY_NAME); List<Integer> replicas = new ArrayList<>(replicasArray.length); for (Object replica : replicasArray) { replicas.add((Integer) replica); } partitionReplicaAssignments.put(partitionId, replicas); } Object[] configArray = singleRequestStruct.getArray(CONFIGS_KEY_NAME); Map<String, String> configs = new HashMap<>(configArray.length); for (Object configStructObj : configArray) { Struct configStruct = (Struct) configStructObj; String key = configStruct.getString(CONFIG_KEY_KEY_NAME); String value = configStruct.getString(CONFIG_VALUE_KEY_NAME); configs.put(key, value); } TopicDetails args = new TopicDetails(numPartitions, replicationFactor, partitionReplicaAssignments, configs); topics.put(topic, args); } this.topics = topics; this.timeout = struct.getInt(TIMEOUT_KEY_NAME); if (struct.hasField(VALIDATE_ONLY_KEY_NAME)) this.validateOnly = struct.getBoolean(VALIDATE_ONLY_KEY_NAME); else this.validateOnly = false; this.duplicateTopics = duplicateTopics; } @Override public AbstractResponse getErrorResponse(int throttleTimeMs, Throwable e) { Map<String, ApiError> topicErrors = new HashMap<>(); for (String topic : topics.keySet()) { topicErrors.put(topic, ApiError.fromThrowable(e)); } short versionId = version(); switch (versionId) { case 0: case 1: return new CreateTopicsResponse(topicErrors); case 2: return new CreateTopicsResponse(throttleTimeMs, topicErrors); default: throw new IllegalArgumentException(String.format("Version %d is not valid. Valid versions for %s are 0 to %d", versionId, this.getClass().getSimpleName(), ApiKeys.CREATE_TOPICS.latestVersion())); } } public Map<String, TopicDetails> topics() { return this.topics; } public int timeout() { return this.timeout; } public boolean validateOnly() { return validateOnly; } public Set<String> duplicateTopics() { return this.duplicateTopics; } public static CreateTopicsRequest parse(ByteBuffer buffer, short version) { return new CreateTopicsRequest(ApiKeys.CREATE_TOPICS.parseRequest(version, buffer), version); } /** * Visible for testing. */ @Override public Struct toStruct() { short version = version(); Struct struct = new Struct(ApiKeys.CREATE_TOPICS.requestSchema(version)); List<Struct> createTopicRequestStructs = new ArrayList<>(topics.size()); for (Map.Entry<String, TopicDetails> entry : topics.entrySet()) { Struct singleRequestStruct = struct.instance(REQUESTS_KEY_NAME); String topic = entry.getKey(); TopicDetails args = entry.getValue(); singleRequestStruct.set(TOPIC_KEY_NAME, topic); singleRequestStruct.set(NUM_PARTITIONS_KEY_NAME, args.numPartitions); singleRequestStruct.set(REPLICATION_FACTOR_KEY_NAME, args.replicationFactor); // replica assignment List<Struct> replicaAssignmentsStructs = new ArrayList<>(args.replicasAssignments.size()); for (Map.Entry<Integer, List<Integer>> partitionReplicaAssignment : args.replicasAssignments.entrySet()) { Struct replicaAssignmentStruct = singleRequestStruct.instance(REPLICA_ASSIGNMENT_KEY_NAME); replicaAssignmentStruct.set(REPLICA_ASSIGNMENT_PARTITION_ID_KEY_NAME, partitionReplicaAssignment.getKey()); replicaAssignmentStruct.set(REPLICA_ASSIGNMENT_REPLICAS_KEY_NAME, partitionReplicaAssignment.getValue().toArray()); replicaAssignmentsStructs.add(replicaAssignmentStruct); } singleRequestStruct.set(REPLICA_ASSIGNMENT_KEY_NAME, replicaAssignmentsStructs.toArray()); // configs List<Struct> configsStructs = new ArrayList<>(args.configs.size()); for (Map.Entry<String, String> configEntry : args.configs.entrySet()) { Struct configStruct = singleRequestStruct.instance(CONFIGS_KEY_NAME); configStruct.set(CONFIG_KEY_KEY_NAME, configEntry.getKey()); configStruct.set(CONFIG_VALUE_KEY_NAME, configEntry.getValue()); configsStructs.add(configStruct); } singleRequestStruct.set(CONFIGS_KEY_NAME, configsStructs.toArray()); createTopicRequestStructs.add(singleRequestStruct); } struct.set(REQUESTS_KEY_NAME, createTopicRequestStructs.toArray()); struct.set(TIMEOUT_KEY_NAME, timeout); if (version >= 1) struct.set(VALIDATE_ONLY_KEY_NAME, validateOnly); return struct; } }