package io.cattle.platform.hazelcast.eventing; import io.cattle.platform.eventing.impl.AbstractThreadPoolingEventService; import io.cattle.platform.eventing.model.Event; import io.cattle.platform.util.type.Named; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.util.concurrent.SettableFuture; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.ITopic; import com.hazelcast.core.Message; import com.hazelcast.core.MessageListener; public class HazelcastEventService extends AbstractThreadPoolingEventService implements Named { private static final Logger log = LoggerFactory.getLogger(HazelcastEventService.class); HazelcastInstance hazelcast; Map<String, String> registrations = new ConcurrentHashMap<String, String>(); @Override protected boolean doPublish(String name, Event event, String eventString) throws IOException { TopicName topicName = new TopicName(name); ITopic<String> topic = hazelcast.getTopic(topicName.getName()); topic.publish(topicName.encode(eventString)); return true; } @Override protected synchronized void doSubscribe(final String eventName, SettableFuture<?> future) { TopicName topicName = new TopicName(eventName); boolean success = false; Throwable t = null; try { if (registrations.containsKey(eventName)) { throw new IllegalStateException("Already subscribed to [" + eventName + "]"); } ITopic<String> topic = hazelcast.getTopic(topicName.getName()); MessageListener<String> listener = new MessageListener<String>() { @Override public void onMessage(Message<String> message) { String eventString = topicName.decode(message.getMessageObject()); if (eventString != null) { onEvent(null, eventName, eventString); } } }; String id = topic.addMessageListener(listener); log.info("Subscribing to [{}] id [{}]", eventName, id); registrations.put(eventName, id); success = true; } catch (RuntimeException e) { t = e; throw e; } finally { if (success) { future.set(null); } else { if (t == null) { t = new IllegalStateException("Failed to subscribe to [" + eventName + "]"); } future.setException(t); } } } @Override protected synchronized void doUnsubscribe(String eventName) { String id = registrations.remove(eventName); log.info("Unsubscribing from [{}] id [{}]", eventName, id); if (id != null) { ITopic<String> topic = hazelcast.getTopic(new TopicName(eventName).getName()); topic.removeMessageListener(id); if (eventName.startsWith("reply.")) { topic.destroy(); } } } @Override protected void disconnect() { } public HazelcastInstance getHazelcast() { return hazelcast; } @Inject public void setHazelcast(HazelcastInstance hazelcast) { this.hazelcast = hazelcast; } @Override public String getName() { return "EventService"; } private static class TopicName { String name; String qualifier; public TopicName(String topic) { String[] parts = StringUtils.split(topic, ";", 2); this.name = parts[0]; if (parts.length > 1) { this.qualifier = parts[1] + ":"; this.name += ";"; } } public String getName() { return name; } public String encode(String eventString) { return qualifier == null ? eventString : qualifier + eventString; } public String decode(String eventString) { if (qualifier == null) { return eventString; } if (eventString.startsWith(this.qualifier)) { return eventString.substring(this.qualifier.length()); } return null; } } }