/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services 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.
*
* Granite Data Services 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.client.messaging;
import java.util.concurrent.ConcurrentHashMap;
import org.granite.client.messaging.channel.MessagingChannel;
import org.granite.client.messaging.channel.ResponseMessageFuture;
import org.granite.client.messaging.events.FailureEvent;
import org.granite.client.messaging.events.IssueEvent;
import org.granite.client.messaging.events.ResultEvent;
import org.granite.client.messaging.events.TopicMessageEvent;
import org.granite.client.messaging.messages.push.TopicMessage;
import org.granite.client.messaging.messages.requests.ReplyMessage;
import org.granite.client.messaging.messages.requests.SubscribeMessage;
import org.granite.client.messaging.messages.requests.UnsubscribeMessage;
import org.granite.logging.Logger;
/**
* Consumer class that allows to receive messages from a remote pub/sub destination
*
* <pre>
* {@code
* Consumer consumer = new Consumer(messagingChannel, "myDestination", "myTopic");
* consumer.subscribe().get();
* consumer.addMessageListener(new TopicMessageListener() {
* public void onMessage(TopicMessageEvent event) {
* System.out.println("Received: " + event.getData());
* }
* });
* }
* </pre>
*
* @author Franck WOLFF
*/
public class Consumer extends AbstractTopicAgent {
private static final Logger log = Logger.getLogger(Consumer.class);
private final ConcurrentHashMap<TopicMessageListener, Boolean> listeners = new ConcurrentHashMap<TopicMessageListener, Boolean>();
private final ConcurrentHashMap<TopicSubscriptionListener, Boolean> subscriptionListeners = new ConcurrentHashMap<TopicSubscriptionListener, Boolean>();
private String subscriptionId = null;
private String selector = null;
/**
* Create a consumer for the specified channel and destination
* @param channel messaging channel
* @param destination remote destination
* @param topic subtopic to which the producer sends its messages
*/
public Consumer(MessagingChannel channel, String destination, String topic) {
super(channel, destination, topic);
}
/**
* Message selector for this consumer
* @return selector
*/
public String getSelector() {
return selector;
}
/**
* Set the message selector for this consumer
* This must be set before subscribing to the destination
* It is necessary to resubscribe to the destination after changing its value
* @param selector selector
*/
public void setSelector(String selector) {
this.selector = selector;
}
/**
* Is this consumer subscribed ?
* @return true if subscribed
*/
public boolean isSubscribed() {
return subscriptionId != null;
}
/**
* Current subscription id received from the server
* @return subscription id
*/
public String getSubscriptionId() {
return subscriptionId;
}
/**
* Subscribe to the remote destination
* @param listeners array of listeners notified when the subscription is processed
* @return future triggered when subscription is processed
*/
public ResponseMessageFuture subscribe(ResponseListener...listeners) {
return subscribe(null, listeners);
}
public ResponseMessageFuture resubscribe(ResponseListener...listeners) {
log.debug("Resubscribing consumer on channel %s with subcriptionId %s", channel.getClientId(), subscriptionId);
return subscribe(subscriptionId, listeners);
}
public ResponseMessageFuture subscribe(final String subscriptionId, ResponseListener...listeners) {
log.debug("Subscribing consumer on channel %s with subcriptionId %s", channel.getClientId(), subscriptionId);
SubscribeMessage subscribeMessage = new SubscribeMessage(destination, topic, selector);
subscribeMessage.getHeaders().putAll(defaultHeaders);
if (subscriptionId != null)
subscribeMessage.setSubscriptionId(subscriptionId);
final Consumer consumer = this;
ResponseListener listener = new ResultIssuesResponseListener() {
@Override
public void onResult(ResultEvent event) {
Consumer.this.subscriptionId = (String)event.getResult();
if (Consumer.this.subscriptionId != null) {
log.debug("Subscription successful %s: %s", consumer, event);
channel.addConsumer(consumer);
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onSubscriptionSuccess(Consumer.this, event, subscriptionId);
}
else {
log.error("Subscription failed %s: %s", consumer, event);
IssueEvent failureEvent = new FailureEvent(event.getRequest(), new IllegalStateException("Received null subscriptionId"));
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onSubscriptionFault(Consumer.this, failureEvent);
}
}
@Override
public void onIssue(IssueEvent event) {
log.error("Subscription failed %s: %s", consumer, event);
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onSubscriptionFault(Consumer.this, event);
}
};
if (listeners == null || listeners.length == 0)
listeners = new ResponseListener[]{listener};
else {
ResponseListener[] tmp = new ResponseListener[listeners.length + 1];
System.arraycopy(listeners, 0, tmp, 1, listeners.length);
tmp[0] = listener;
listeners = tmp;
}
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onSubscribing(this);
return channel.send(subscribeMessage, listeners);
}
/**
* Unubscribe from the remote destination
* @param listeners array of listeners notified when the unsubscription is processed
* @return future triggered when unsubscription is processed
*/
public ResponseMessageFuture unsubscribe(ResponseListener... listeners) {
if (!isSubscribed())
return null;
UnsubscribeMessage unsubscribeMessage = new UnsubscribeMessage(destination, topic, subscriptionId);
unsubscribeMessage.getHeaders().putAll(defaultHeaders);
final Consumer consumer = this;
ResponseListener listener = new ResultIssuesResponseListener() {
@Override
public void onResult(ResultEvent event) {
channel.removeConsumer(consumer);
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onUnsubscriptionSuccess(Consumer.this, event, subscriptionId);
subscriptionId = null;
}
@Override
public void onIssue(IssueEvent event) {
log.error("Unsubscription failed %s: %s", consumer, event);
channel.removeConsumer(consumer);
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onUnsubscriptionFault(Consumer.this, event, subscriptionId);
subscriptionId = null;
}
};
if (listeners == null || listeners.length == 0)
listeners = new ResponseListener[]{listener};
else {
ResponseListener[] tmp = new ResponseListener[listeners.length + 1];
System.arraycopy(listeners, 0, tmp, 0, listeners.length);
tmp[listeners.length] = listener;
listeners = tmp;
}
for (TopicSubscriptionListener subscriptionListener : subscriptionListeners.keySet())
subscriptionListener.onUnsubscribing(this);
return channel.send(unsubscribeMessage, listeners);
}
/**
* Register a message listener for this consumer
* @param listener message listener
*/
public void addMessageListener(TopicMessageListener listener) {
listeners.putIfAbsent(listener, Boolean.TRUE);
}
/**
* Unregister a message listener for this consumer
* @param listener message listener
* @return true if the listener was actually removed (if it was registered before)
*/
public boolean removeMessageListener(TopicMessageListener listener) {
return listeners.remove(listener) != null;
}
/**
* Register a subscription listener for this consumer
* @param listener subscription listener
*/
public void addSubscriptionListener(TopicSubscriptionListener listener) {
subscriptionListeners.putIfAbsent(listener, Boolean.TRUE);
}
/**
* Unregister a subscription listener for this consumer
* @param listener subscription listener
* @return true if the listener was actually removed (if it was registered before)
*/
public boolean removeSubscriptionListener(TopicSubscriptionListener listener) {
return subscriptionListeners.remove(listener) != null;
}
/**
* Called when the channel is disconnected
*/
public void onDisconnect() {
subscriptionId = null;
}
/**
* Called when a message is received on the channel
* @param message message received
*/
public void onMessage(TopicMessage message) {
for (TopicMessageListener listener : listeners.keySet()) {
try {
listener.onMessage(new TopicMessageEvent(this, message));
}
catch (Exception e) {
log.error(e, "Consumer listener threw an exception: ", listener);
}
}
}
/**
* Reply to a server-to-client request
* @param message incoming request message
* @param reply response to send
*/
public void reply(TopicMessage message, Object reply) {
ReplyMessage replyMessage = new ReplyMessage(destination, topic, message.getId(), reply);
replyMessage.getHeaders().putAll(message.getHeaders());
channel.send(replyMessage);
}
@Override
public String toString() {
return getClass().getName() + " {subscriptionId=" + subscriptionId +
", destination=" + destination +
", topic=" + topic +
", selector=" + selector +
"}";
}
}