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

org.cometd.oort.Seti Maven / Gradle / Ivy

There is a newer version: 8.0.6
Show newest version
package org.cometd.oort;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.cometd.bayeux.Message;
import org.cometd.bayeux.client.ClientSessionChannel;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.authorizer.GrantAuthorizer;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.ajax.JSON;
import org.eclipse.jetty.util.log.Log;


/* ------------------------------------------------------------ */
/** The Search for Extra Terrestial Intelligence.
 *
 * Well in this case, just the search for a user logged onto an
 * Cometd node in an Oort cluster.
 * 

* Seti allows an application to maintain a mapping from userId to * comet client ID using the {@link #associate(String, Client)} and * {@link #disassociate(String)} methods. Each cometd node keeps its * own associate mapping for clients connected to it. *

* The {@link #sendMessage(Collection, String, Object)} and * {@link #sendMessage(String, String, Object)} methods may be * used to send a message to user(s) anywhere in the Oort cluster * and Seti organizes the search of the distributed associate * maps in order to locate the user(s) *

* If users can be directed to shards of cometd servers, then * each Seti instance must be told it's shard ID and the {@link #userId2Shard(String)} * method must be extended to map users to shards. * */ public class Seti { public final static String SETI_ATTRIBUTE="org.cometd.oort.Seti"; public final static String SETI_SHARD="seti.shard"; final String _setiId; final String _shardId; final Oort _oort; final LocalSession _session; final ShardLocation _allShardLocation; final ServerChannel _setiIdChannel; final ServerChannel _setiAllChannel; final ServerChannel _setiShardChannel; final ConcurrentMap _uid2Location = new ConcurrentHashMap(); /* ------------------------------------------------------------ */ public Seti(Oort oort, String shardId) { _oort=oort; BayeuxServer bayeux = _oort.getBayeux(); _session = bayeux.newLocalSession("seti"); _setiId=_oort.getURL().replace("://","_").replace("/","_").replace(":","_"); _shardId=shardId; bayeux.createIfAbsent("/seti/**", new ConfigurableServerChannel.Initializer() { public void configureChannel(ConfigurableServerChannel channel) { channel.addAuthorizer(GrantAuthorizer.GRANT_ALL); } }); String channel = "/seti/" + _setiId; bayeux.createIfAbsent(channel); _setiIdChannel= bayeux.getChannel(channel); _setiIdChannel.setPersistent(true); channel = "/seti/ALL"; bayeux.createIfAbsent(channel); _setiAllChannel= bayeux.getChannel(channel); _setiAllChannel.setPersistent(true); channel = "/seti/"+shardId; bayeux.createIfAbsent(channel); _setiShardChannel= bayeux.getChannel(channel); _setiShardChannel.setPersistent(true); _allShardLocation = new ShardLocation("ALL"); try { _session.handshake(); } catch(Exception e) { throw new RuntimeException(e); } _oort.observeChannel(_setiIdChannel.getId()); _session.getChannel(_setiIdChannel.getId()).subscribe(new ClientSessionChannel.MessageListener() { public void onMessage(ClientSessionChannel channel, Message message) { receive(message); } }); _oort.observeChannel(_setiAllChannel.getId()); _session.getChannel(_setiAllChannel.getId()).subscribe(new ClientSessionChannel.MessageListener() { public void onMessage(ClientSessionChannel channel, Message message) { receive(message); } }); _oort.observeChannel(_setiShardChannel.getId()); _session.getChannel(_setiShardChannel.getId()).subscribe(new ClientSessionChannel.MessageListener() { public void onMessage(ClientSessionChannel channel, Message message) { receive(message); } }); } /* ------------------------------------------------------------ */ public void associate(final String userId,final ServerSession session) { _uid2Location.put(userId,new LocalLocation(session)); userId2Shard(userId).associate(userId); } /* ------------------------------------------------------------ */ public void disassociate(final String userId) { _uid2Location.remove(userId); userId2Shard(userId).disassociate(userId); } /* ------------------------------------------------------------ */ public void sendMessage(final String toUser,final String toChannel,final Object data) { Location location = _uid2Location.get(toUser); if (location==null) location = userId2Shard(toUser); location.sendMessage(toUser,toChannel,data); } /* ------------------------------------------------------------ */ public void sendMessage(final Collection toUsers,final String toChannel,final Object message) { // break toUsers in to shards MultiMap shard2users = new MultiMap(); for (String userId:toUsers) { ShardLocation shard = userId2Shard(userId); shard2users.add(shard,userId); } // for each shard for (Map.Entry entry : (Set>)shard2users.entrySet()) { // TODO, we could look at all users in shard to see if we // know a setiId for each, and if so, break the user list // up into a message for each seti-id. BUT it is probably // more efficient just to send to the entire shard (unless // the number of nodes in the shard is greater than the // number of users). ShardLocation shard = entry.getKey(); Object lazyUsers = entry.getValue(); if (LazyList.size(lazyUsers)==1) shard.sendMessage((String)lazyUsers,toChannel,message); else shard.sendMessage((List)lazyUsers,toChannel,message); } } /* ------------------------------------------------------------ */ protected ShardLocation userId2Shard(final String userId) { return _allShardLocation; } /* ------------------------------------------------------------ */ protected void receive(final Message msg) { if (Log.isDebugEnabled()) Log.debug("SETI "+_oort+":: "+msg); if (!(msg.getData() instanceof Map)) return; // extract the message details Map data = (Map)msg.getData(); final String toUid=(String)data.get("to"); final String fromUid=(String)data.get("from"); final Object message = data.get("message"); final String on = (String)data.get("on"); // Handle any client locations contained in the message if (fromUid!=null) { if (on!=null) { if (Log.isDebugEnabled()) Log.debug(_oort+":: "+fromUid+" on "+on); _uid2Location.put(fromUid,new SetiLocation("/seti/"+on)); } else { final String off = (String)data.get("off"); if (off!=null) { if (Log.isDebugEnabled()) Log.debug(_oort+":: "+fromUid+" off "); _uid2Location.remove(fromUid,new SetiLocation("/seti/"+off)); } } } // deliver message if (message!=null && toUid!=null) { final String toChannel=(String)data.get("channel"); Location location=_uid2Location.get(toUid); if (location==null && _setiIdChannel.getId().equals(msg.getChannel())) // was sent to this node, so escalate to the shard. location =userId2Shard(toUid); if (location!=null) location.receive(toUid,toChannel,message); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ private interface Location { public void sendMessage(String toUser,String toChannel,Object data); public void receive(String toUser,String toChannel,Object data); } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class LocalLocation implements Location { ServerSession _session; LocalLocation(ServerSession session) { _session=session; } public void sendMessage(String toUser, String toChannel, Object data) { System.err.println("SETI LocalLocation.send "+toUser+","+toChannel+","+data); _session.deliver(_session,toChannel,data,null); } public void receive(String toUser, String toChannel, Object data) { System.err.println("SETI LocalLocation.recieve "+toUser+","+toChannel+","+data); _session.deliver(_session,toChannel,data,null); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class SetiLocation implements Location { ClientSessionChannel _channel; SetiLocation(String channelId) { _channel=_session.getChannel(channelId); } public void sendMessage(String toUser, String toChannel, Object message) { _channel.publish(new SetiMessage(toUser,toChannel,message)); } public void receive(String toUser, String toChannel, Object message) { } public boolean equals(Object o) { return o instanceof SetiLocation && ((SetiLocation)o)._channel.equals(_channel); } public int hashCode() { return _channel.hashCode(); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class ShardLocation implements Location { ClientSessionChannel _channel; ShardLocation(String shardId) { _channel=_session.getChannel("/seti/"+shardId); } public void sendMessage(final Collection toUsers, final String toChannel, final Object message) { _channel.publish(new SetiMessage(toUsers,toChannel,message)); } public void sendMessage(String toUser, String toChannel, Object message) { _channel.publish(new SetiMessage(toUser,toChannel,message)); } public void receive(String toUser, String toChannel, Object message) { } public void associate(final String user) { _channel.publish(new SetiPresence(user,true)); } public void disassociate(final String user) { _channel.publish(new SetiPresence(user,false)); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class SetiMessage implements JSON.Convertible { String _toUser; Collection _toUsers; String _toChannel; Object _message; SetiMessage(String toUser,String toChannel, Object message) { _toUser=toUser; _toChannel=toChannel; _message=message; } SetiMessage(Collection toUsers,String toChannel, Object message) { _toUsers=toUsers; _toChannel=toChannel; _message=message; } public void fromJSON(Map object) { throw new UnsupportedOperationException(); } public void toJSON(JSON.Output out) { if (_toUser!=null) out.add("to",_toUser); else if (_toUsers!=null) out.add("to",_toUsers); out.add("channel",_toChannel); out.add("from",_setiId); out.add("message",_message); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ class SetiPresence implements JSON.Convertible { String _user; boolean _on; SetiPresence(String user,boolean on) { _user=user; _on=on; } public void fromJSON(Map object) { throw new UnsupportedOperationException(); } public void toJSON(JSON.Output out) { out.add("from",_user); out.add(_on?"on":"off",_setiId); } } public void disconnect() { _session.disconnect(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy