All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.atmosphere.kafka.KafkaBroadcaster Maven / Gradle / Ivy

/*
 * Copyright 2017 Async-IO.org
 *
 * 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 org.atmosphere.kafka;

import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.atmosphere.cpr.AtmosphereConfig;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.util.AbstractBroadcasterProxy;
import org.atmosphere.util.ExecutorsFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Kafka Support via a {@link Broadcaster}
 *
 * @author Jeanfrancois Arcand.
 */
public class KafkaBroadcaster extends AbstractBroadcasterProxy {

    private final Logger logger = LoggerFactory.getLogger(KafkaBroadcaster.class);

    public final static String PROPERTIES_FILE = "org.atmosphere.kafka.propertiesFile";
    private String topic;

    // using kafka 0.9+ API
    private KafkaProducer producer;
    private KafkaConsumer consumer;
    private final Serializer stringSerializer = new StringSerializer();
    private final Deserializer stringDeserializer = new StringDeserializer();
    private final AtomicBoolean closed = new AtomicBoolean(false);

    @Override
    public Broadcaster initialize(String id, URI uri, final AtmosphereConfig config) {
        super.initialize(id, uri, config);

        topic = id.equals(ROOT_MASTER) ? "atmosphere" : id.replaceAll("[^a-zA-Z0-9\\s]", "");
        // We are thread-safe
        producer = (KafkaProducer) config.properties().get("producer");
        Set topics = (Set) config.properties().get("topics");
        if (topics == null) {
            topics = new HashSet();
            config.properties().put("topics", topics);
        }
        // create a new producer and consumer when the topic changes
        if (topics.isEmpty() || !topics.contains(topic)) {
            String load = config.getInitParameter(PROPERTIES_FILE, null);
            Properties props = new Properties();
            // let each consumer use its own group by default so that each can receive messages
            UUID uuid = UUID.randomUUID();
            String defaultGroupId = "atmosphere-consumer-" + Long.toHexString(uuid.getMostSignificantBits() ^ uuid.getLeastSignificantBits());
            props.put("group.id", defaultGroupId);
            props.put("bootstrap.servers", "127.0.0.1:9092");
            props.put("enable.auto.commit", "true");
            props.put("auto.commit.interval.ms", "1000");
            if (load != null) {
                try {
                    props.load(config.getServletContext().getResourceAsStream(load));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (topics.isEmpty()) {
                // producer can be reused, so it is instantiated only once
                producer = new KafkaProducer(props, stringSerializer, stringSerializer);
                config.properties().put("producer", producer);
            }
            // consumer needs to be created for each topic subscription
            consumer = new KafkaConsumer(props, stringDeserializer, stringDeserializer);
            topics.add(topic);

            startConsumer();
        }



        return this;
    }

    @Override
    public synchronized void destroy() {
        closed.set(true);
        super.destroy();
    }

    void startConsumer() {

        consumer.subscribe(Arrays.asList(topic));
        ExecutorsFactory.getMessageDispatcher(config, "kafka").execute(new Runnable() {
            @Override
            public void run() {
                while (!closed.get()) {
                    ConsumerRecords records = consumer.poll(1000);
                    for (ConsumerRecord record : records) {
                        broadcastReceivedMessage(record.value());
                    }
                }
                consumer.close();
                ((Set)config.properties().get("topics")).remove(topic);
            }
        });
    }

    @Override
    public void incomingBroadcast() {
    }

    @Override
    public void outgoingBroadcast(Object message) {
        logger.trace("{} outgoingBroadcast {}", topic, message);

        // TODO: Prevent message round trip.

        producer.send(new ProducerRecord(topic, message.toString()));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy