/*
* Copyright 2014 Jean-Francois Arcand
*
* 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.plugin.jgroups;
import org.atmosphere.cpr.Broadcaster;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* JGroupsChannel establishes a connection to a
* JGroups cluster. It sends/receives over that and forwards
* the received messages to the appropriate Broadcaster on its
* node.
*
* Best practice would have only 1 of these per Atmosphere application.
* Each JGroupsFilter instance has a reference to the
* singleton JGroupsChannel object and registers its broadcaster via
* the addBroadcaster() method.
*
* @author westraj
*
*/
public class JGroupsChannel extends ReceiverAdapter {
private static final Logger logger = LoggerFactory
.getLogger(JGroupsChannel.class);
/** JGroups JChannel object */
private final JChannel jchannel;
/** JChannel cluster name */
private final String clusterName;
/** registers all the Broadcasters that are filtered via a JGroupsFilter */
private final Map<String, Broadcaster> broadcasters = new HashMap<String, Broadcaster>();
/** Holds original messages (not BroadcastMessage) received over a cluster broadcast */
private final ConcurrentLinkedQueue<Object> receivedMessages = new ConcurrentLinkedQueue<Object>();
/**
* Constructor
*
* @param jchannel unconnected JGroups JChannel object
* @param clusterName name of the group to connect the JChannel to
*/
public JGroupsChannel(JChannel jchannel, String clusterName) {
if (jchannel.isConnected()) throw new IllegalArgumentException("JChannel already connected");
this.jchannel = jchannel;
this.clusterName = clusterName;
}
/**
* Connect to the cluster
* @throws Exception
*/
public void init() throws Exception {
logger.info(
"Starting Atmosphere JGroups Clustering support with group name {}",
this.clusterName);
try {
this.jchannel.setReceiver(this);
this.jchannel.connect(clusterName);
this.jchannel.setDiscardOwnMessages(true);
} catch (Exception e) {
logger.warn("Failed to connect to cluster: " + this.clusterName, e);
throw e;
}
}
/**
* Shutdown the cluster.
*/
public void destroy() {
try {
Util.shutdown(jchannel);
} catch (Throwable t) {
Util.close(jchannel);
logger.warn("failed to properly shutdown jgroups channel, closing abnormally", t);
}
receivedMessages.clear();
broadcasters.clear();
}
/**
* {@inheritDoc}
*/
@Override
public void receive(final Message jgroupMessage) {
final Object payload = jgroupMessage.getObject();
if (payload == null) return;
if (BroadcastMessage.class.isAssignableFrom(payload.getClass())) {
BroadcastMessage broadcastMsg = BroadcastMessage.class.cast(payload);
// original message from the sending node's JGroupsFilter.filter() method
Object origMessage = broadcastMsg.getMessage();
// add original message to list to check re-broadcast logic in send()
receivedMessages.offer(origMessage);
String topicId = broadcastMsg.getTopic();
if (broadcasters.containsKey(topicId)) {
Broadcaster bc = broadcasters.get(topicId);
try {
bc.broadcast(origMessage).get();
} catch(Exception ex) {
logger.error("Failed to broadcast message received over the JGroups cluster "+this.clusterName, ex);
}
}
}
}
/**
* Called from a ClusterBroadcastFilter filter() method
* to send the message over to other Atmosphere cluster nodes
*
* @param topic
* @param message
*/
public void send(String topic, Object message) {
if (jchannel.isConnected()) {
// Avoid re-broadcasting to cluster by checking if the message was
// one already received from another cluster node
if (!receivedMessages.remove(message)) {
try {
BroadcastMessage broadcastMsg = new BroadcastMessage(topic, message);
Message jgroupMsg = new Message(null, null, broadcastMsg);
jchannel.send(jgroupMsg);
} catch (Exception e) {
logger.warn("Failed to send message {}", message, e);
}
}
}
}
/**
* Adds/replaces the broadcaster to the JGroupsChannel
* @param broadcaster
*/
public void addBroadcaster(Broadcaster broadcaster) {
this.broadcasters.put(broadcaster.getID(), broadcaster);
}
/**
* Removes the broadcaster from the JGroupsChannel
* @param broadcaster
*/
public void removeBroadcaster(Broadcaster broadcaster) {
this.broadcasters.remove(broadcaster.getID());
}
}