/* * Copyright © 2015 Cask Data, 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 co.cask.cdap.data2.metadata.publisher; import co.cask.cdap.api.common.Bytes; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.codec.NamespacedIdCodec; import co.cask.cdap.proto.metadata.MetadataChangeRecord; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.inject.Inject; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; import org.apache.twill.internal.kafka.client.ByteBufferEncoder; import org.apache.twill.internal.kafka.client.IntegerEncoder; import org.apache.twill.internal.kafka.client.IntegerPartitioner; import org.apache.twill.kafka.client.Compression; import org.apache.twill.kafka.client.KafkaPublisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.Properties; /** * A {@link MetadataChangePublisher} that publishes metadata changes to an Apache Kafka topic determined by the * configuration parameter <code>metadata.updates.kafka.topic</code>. External systems can get notifications of * metadata changes by subscribing to this topic. */ public class KafkaMetadataChangePublisher implements MetadataChangePublisher { private static final Logger LOG = LoggerFactory.getLogger(KafkaMetadataChangePublisher.class); private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(Id.NamespacedId.class, new NamespacedIdCodec()) .create(); private final KafkaPublisher.Ack ack; private final Supplier<Producer<Integer, ByteBuffer>> producer; private final String topic; private final String brokerList; @Inject KafkaMetadataChangePublisher(CConfiguration cConf) { this.ack = KafkaPublisher.Ack.FIRE_AND_FORGET; this.topic = cConf.get(Constants.Metadata.UPDATES_KAFKA_TOPIC); this.brokerList = cConf.get(Constants.Metadata.UPDATES_KAFKA_BROKER_LIST); this.producer = Suppliers.memoize(new Supplier<Producer<Integer, ByteBuffer>>() { @Override public Producer<Integer, ByteBuffer> get() { Properties props = new Properties(); props.put("metadata.broker.list", brokerList); props.put("serializer.class", ByteBufferEncoder.class.getName()); props.put("key.serializer.class", IntegerEncoder.class.getName()); props.put("partitioner.class", IntegerPartitioner.class.getName()); props.put("request.required.acks", Integer.toString(ack.getAck())); props.put("compression.codec", Compression.SNAPPY.getCodec()); ProducerConfig config = new ProducerConfig(props); return new Producer<>(config); } }); } @Override public void publish(MetadataChangeRecord changeRecord) { byte[] changesToPublish = Bytes.toBytes(GSON.toJson(changeRecord)); Object partitionKey = changeRecord.getPrevious().getEntityId(); @SuppressWarnings("ConstantConditions") ByteBuffer message = ByteBuffer.wrap(changesToPublish); try { producer.get().send(new KeyedMessage<>(topic, Math.abs(partitionKey.hashCode()), message)); } catch (Exception e) { LOG.error("Failed to send message to topic {} with broker list {}", topic, brokerList, e); } } }