/*
* JBoss, Home of Professional Open Source
* Copyright 2013, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.application.push.impl;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.richfaces.application.push.MessageException;
import org.richfaces.application.push.Session;
import org.richfaces.application.push.SessionSubscriptionEvent;
import org.richfaces.application.push.SessionUnsubscriptionEvent;
import org.richfaces.application.push.TopicEvent;
import org.richfaces.application.push.TopicKey;
/**
* @author Nick Belaevski
*/
public class TopicImpl extends AbstractTopic {
private ConcurrentMap<TopicKey, PublishingContext> sessions = new ConcurrentHashMap<TopicKey, PublishingContext>();
private TopicsContextImpl topicsContext;
public TopicImpl(TopicKey key, TopicsContextImpl topicsContext) {
super(key);
this.topicsContext = topicsContext;
}
/*
* (non-Javadoc)
*
* @see org.richfaces.application.push.AbstractTopic#publish(java.lang.Object)
*/
@Override
public void publish(Object messageData) throws MessageException {
publish(messageData, null);
}
/*
* (non-Javadoc)
*
* @see org.richfaces.application.push.AbstractTopic#publish(java.lang.Object, java.lang.String)
*/
@Override
public void publish(Object messageData, String subtopicName) throws MessageException {
String serializedData = getMessageDataSerializer().serialize(messageData);
if (serializedData != null) {
PublishingContext topicContext = getPublishingContext(getKey());
if (topicContext != null) {
topicContext.addMessage(serializedData);
}
// support publishing to contexts that are only interested in specific subtopics
if (subtopicName != null && getKey().getSubtopicName() == null) {
topicContext = getPublishingContext(new TopicKey(getKey().getTopicName(), subtopicName));
if (topicContext != null) {
topicContext.addMessage(serializedData);
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.richfaces.application.push.AbstractTopic#publishEvent(org.richfaces.application.push.TopicEvent)
*/
@Override
public void publishEvent(TopicEvent event) {
super.publishEvent(event);
if (event instanceof SessionSubscriptionEvent) {
SessionSubscriptionEvent subscriptionEvent = (SessionSubscriptionEvent) event;
getOrCreatePublishingContext(subscriptionEvent.getTopicKey()).addSession(subscriptionEvent.getSession());
} else if (event instanceof SessionUnsubscriptionEvent) {
SessionUnsubscriptionEvent unsubscriptionEvent = (SessionUnsubscriptionEvent) event;
getPublishingContext(unsubscriptionEvent.getTopicKey()).removeSession(unsubscriptionEvent.getSession());
}
}
/**
* Returns existing {@link PublishingContext} for given key or creates a null if there is no such {@link PublishingContext}.
*/
private PublishingContext getPublishingContext(TopicKey key) {
return sessions.get(key);
}
/**
* Returns existing {@link PublishingContext} for given key or creates a new one if there is no such
* {@link PublishingContext} yet.
*/
private PublishingContext getOrCreatePublishingContext(TopicKey key) {
PublishingContext result = sessions.get(key);
if (result == null) {
PublishingContext freshContext = new PublishingContext(key);
result = sessions.putIfAbsent(key, freshContext);
if (result == null) {
result = freshContext;
}
}
return result;
}
/**
* Binds a {@link TopicKey} with list of {@link Session}s subscribed to given topic.
*/
private final class PublishingContext {
private final List<Session> sessions = new CopyOnWriteArrayList<Session>();
private final Queue<String> serializedMessages = new ConcurrentLinkedQueue<String>();
private final TopicKey key;
private boolean submittedForPublishing;
public PublishingContext(TopicKey key) {
super();
this.key = key;
}
/**
* Subscribe session for listening for new messages in associated {@link TopicKey}
*/
public void addSession(Session session) {
sessions.add(session);
}
/**
* Removes session from listening for new messages in associated {@link TopicKey}
*/
public void removeSession(Session session) {
sessions.remove(session);
}
/**
* Adds new message and submits this context for publishing
*/
public void addMessage(String serializedMessageData) {
serializedMessages.add(serializedMessageData);
submitForPublishing();
}
/**
* Publishes messages that are scheduled for publishing.
*
* If there are any messages in the queue once finished publishing,
* a new round of publishing is scheduled.
*/
public void publishMessages() {
Iterator<String> itr = serializedMessages.iterator();
while (itr.hasNext()) {
String message = itr.next();
for (Session session : sessions) {
session.push(key, message);
}
itr.remove();
}
synchronized (this) {
submittedForPublishing = false;
if (!serializedMessages.isEmpty()) {
submitForPublishing();
}
}
}
private synchronized void submitForPublishing() {
if (!submittedForPublishing) {
submittedForPublishing = true;
topicsContext.getPublisherService().submit(new PublishTask(this));
}
}
}
/**
* A task used for scheduling publishing of messages on given {@link TopicsContext}.
*/
private static final class PublishTask implements Runnable {
private final PublishingContext topicContext;
public PublishTask(PublishingContext topicContext) {
super();
this.topicContext = topicContext;
}
@Override
public void run() {
topicContext.publishMessages();
}
}
}