View Javadoc

1   package org.cometd.oort;
2   
3   import java.util.Collection;
4   import java.util.List;
5   import java.util.Map;
6   import java.util.Set;
7   import java.util.concurrent.ConcurrentHashMap;
8   import java.util.concurrent.ConcurrentMap;
9   
10  import org.cometd.Channel;
11  import org.cometd.Client;
12  import org.cometd.Message;
13  import org.cometd.MessageListener;
14  import org.mortbay.cometd.ClientImpl;
15  import org.mortbay.component.AbstractLifeCycle;
16  import org.mortbay.util.LazyList;
17  import org.mortbay.util.MultiMap;
18  import org.mortbay.util.ajax.JSON;
19  import org.mortbay.util.ajax.JSON.Output;
20  
21  /* ------------------------------------------------------------ */
22  public class Seti extends AbstractLifeCycle
23  {
24      public final static String SETI_ATTRIBUTE="org.cometd.oort.Seti";
25      public final static String SETI_SHARD="seti.shard";
26      
27      final String _setiId;
28      final String _setiChannelId;
29      final String _shardId;
30      final Oort _oort;
31      final Client _client;
32      final ShardLocation _allShardLocation;
33      final Channel _setiIdChannel;
34      final Channel _setiAllChannel;
35      final Channel _setiShardChannel;
36      
37      final ConcurrentMap<String, Location> _uid2Location = new ConcurrentHashMap<String, Location>();
38      
39      /* ------------------------------------------------------------ */
40      public Seti(Oort oort, String shardId)
41      {
42          _oort=oort;
43          _client = _oort.getBayeux().newClient("seti");
44          _setiId=_oort.getURL().replace("://","_").replace("/","_").replace(":","_");
45          _shardId=shardId;
46  
47          _setiChannelId="/seti/"+_setiId;
48          _setiIdChannel=_oort.getBayeux().getChannel(_setiChannelId,true);
49          _setiIdChannel.setPersistent(true);
50          _oort.observeChannel(_setiIdChannel.getId());
51          _setiIdChannel.subscribe(_client);
52          
53          _setiAllChannel=_oort.getBayeux().getChannel("/seti/ALL",true);
54          _setiAllChannel.setPersistent(true);
55          _oort.observeChannel(_setiAllChannel.getId());
56          _setiAllChannel.subscribe(_client);
57          
58          _setiShardChannel=_oort.getBayeux().getChannel("/seti/"+shardId,true);
59          _setiShardChannel.setPersistent(true);
60          _oort.observeChannel(_setiShardChannel.getId());
61          _setiShardChannel.subscribe(_client);
62          
63          _allShardLocation = new ShardLocation(_setiAllChannel);
64           
65      }
66  
67      /* ------------------------------------------------------------ */
68      protected void doStart()
69          throws Exception
70      {
71          super.doStart();
72          _client.addListener(new MessageListener()
73          {
74              public void deliver(Client from, Client to, Message msg)
75              {
76                  receive(from,to,msg);
77              }
78          });
79      }
80      
81      /* ------------------------------------------------------------ */
82      protected void doStop()
83          throws Exception
84      {
85          _client.disconnect();
86      }
87      
88      /* ------------------------------------------------------------ */
89      public void associate(final String userId,final Client client)
90      {
91          _uid2Location.put(userId,new LocalLocation(client));
92          userId2Shard(userId).associate(userId);
93      }
94  
95      /* ------------------------------------------------------------ */
96      public void disassociate(final String userId)
97      {
98          _uid2Location.remove(userId);
99          userId2Shard(userId).disassociate(userId);
100     }
101 
102     /* ------------------------------------------------------------ */
103     public void sendMessage(final String toUser,final String toChannel,final Object message)
104     {
105         Location location = _uid2Location.get(toUser);
106         if (location==null)
107             location = userId2Shard(toUser);
108         
109         location.sendMessage(toUser,toChannel,message);
110     }
111 
112     /* ------------------------------------------------------------ */
113     public void sendMessage(final Collection<String> toUsers,final String toChannel,final Object message)
114     {
115         // break toUsers in to shards
116         MultiMap shard2users = new MultiMap();
117         for (String userId:toUsers)
118         {       
119             ShardLocation shard = userId2Shard(userId);
120             shard2users.add(shard,userId);
121         }
122         
123         // for each shard
124         for (Map.Entry<ShardLocation,Object> entry : (Set<Map.Entry<ShardLocation,Object>>)shard2users.entrySet())
125         {
126             // TODO, we could look at all users in shard to see if we
127             // know a setiId for each, and if so, break the user list
128             // up into a message for each seti-id. BUT it is probably 
129             // more efficient just to send to the entire shard (unless
130             // the number of nodes in the shard is greater than the
131             // number of users).
132             
133             ShardLocation shard = entry.getKey();
134             Object lazyUsers = entry.getValue();
135             
136             if (LazyList.size(lazyUsers)==1)
137                 shard.sendMessage((String)lazyUsers,toChannel,message);
138             else
139                 shard.sendMessage((List<String>)lazyUsers,toChannel,message);
140         }
141     }
142     
143     /* ------------------------------------------------------------ */
144     protected ShardLocation userId2Shard(final String userId)
145     {
146         return _allShardLocation;
147     }
148 
149     /* ------------------------------------------------------------ */
150     protected void receive(final Client from, final Client to, final Message msg)
151     {
152         //System.err.println("SETI "+_oort+":: "+msg);
153 
154         if (!(msg.getData() instanceof Map))
155             return;
156         
157         // extract the message details
158         Map<String,Object> data = (Map<String,Object>)msg.getData();
159         final String toUid=(String)data.get("to");
160         final String fromUid=(String)data.get("from");
161         final Object message = data.get("message");
162         final String on = (String)data.get("on");
163         
164         // Handle any client locations contained in the message
165         if (fromUid!=null)
166         {
167             if (on!=null)
168             {
169                 //System.err.println(_oort+":: "+fromUid+" on "+on);
170                 _uid2Location.put(fromUid,new SetiLocation("/seti/"+on));
171             }
172             else 
173             {
174                 final String off = (String)data.get("off");
175                 if (off!=null)
176                 {
177                     //System.err.println(_oort+":: "+fromUid+" off ");
178                     _uid2Location.remove(fromUid,new SetiLocation("/seti/"+off));
179                 }
180             }
181         }
182         
183         // deliver message
184         if (message!=null && toUid!=null)
185         {
186             final String toChannel=(String)data.get("channel");
187             Location location=_uid2Location.get(toUid);
188             
189             if (location==null && _setiChannelId.equals(msg.getChannel()))
190                 // was sent to this node, so escalate to the shard.
191                 location =userId2Shard(toUid);
192             
193             if (location!=null)
194                 location.receive(toUid,toChannel,message);
195         }
196         
197     }
198 
199 
200     /* ------------------------------------------------------------ */
201     /* ------------------------------------------------------------ */
202     private interface Location
203     {
204         public void sendMessage(String toUser,String toChannel,Object message);
205         public void receive(String toUser,String toChannel,Object message);
206     }
207     
208 
209     /* ------------------------------------------------------------ */
210     /* ------------------------------------------------------------ */
211     class LocalLocation implements Location
212     {
213         Client _client;
214         
215         LocalLocation(Client client)
216         {
217             _client=client;
218         }
219 
220         public void sendMessage(String toUser, String toChannel, Object message)
221         {
222             _client.deliver(Seti.this._client,toChannel,message,null);
223         }
224 
225         public void receive(String toUser, String toChannel, Object message)
226         {
227             _client.deliver(Seti.this._client,toChannel,message,null);
228         }
229     }
230 
231     /* ------------------------------------------------------------ */
232     /* ------------------------------------------------------------ */
233     class SetiLocation implements Location
234     {
235         Channel _channel;
236 
237         SetiLocation(String channelId)
238         {
239             _channel=_oort._bayeux.getChannel(channelId,true);
240         }
241         
242         SetiLocation(Channel channel)
243         {
244             _channel=channel;
245         }
246         
247         public void sendMessage(String toUser, String toChannel, Object message)
248         {
249             _channel.publish(Seti.this._client,new SetiMessage(toUser,toChannel,message),null);
250         }
251 
252         public void receive(String toUser, String toChannel, Object message)
253         {
254             
255         }
256 
257         public boolean equals(Object o)
258         {
259             return o instanceof SetiLocation &&
260             ((SetiLocation)o)._channel.equals(_channel);
261         }
262         
263         public int hashCode()
264         {
265             return _channel.hashCode();
266         }
267     }
268 
269     /* ------------------------------------------------------------ */
270     /* ------------------------------------------------------------ */
271     class ShardLocation implements Location
272     {
273         Channel _channel;
274         
275         ShardLocation(String shardId)
276         {
277             _channel=_oort._bayeux.getChannel("/seti/"+shardId,true);
278             
279         }
280         
281         ShardLocation(Channel channel)
282         {
283             _channel=channel;
284         }
285         
286         public void sendMessage(final Collection<String> toUsers, final String toChannel, final Object message)
287         {
288             _channel.publish(Seti.this._client,new SetiMessage(toUsers,toChannel,message),null);
289         }
290 
291         public void sendMessage(String toUser, String toChannel, Object message)
292         {
293             _channel.publish(Seti.this._client,new SetiMessage(toUser,toChannel,message),null);
294         }
295         
296         public void receive(String toUser, String toChannel, Object message)
297         {
298             
299         }
300         
301         public void associate(final String user)
302         {
303             _channel.publish(Seti.this._client,new SetiPresence(user,true),null);
304         }
305         
306         public void disassociate(final String user)
307         {
308             _channel.publish(Seti.this._client,new SetiPresence(user,false),null);
309         }
310     }
311 
312     /* ------------------------------------------------------------ */
313     /* ------------------------------------------------------------ */
314     class SetiMessage implements JSON.Convertible
315     {
316         String _toUser;
317         Collection<String> _toUsers;
318         String _toChannel;
319         Object _message;
320 
321         SetiMessage(String toUser,String toChannel, Object message)
322         {
323             _toUser=toUser;
324             _toChannel=toChannel;
325             _message=message;
326         }
327         
328         SetiMessage(Collection<String> toUsers,String toChannel, Object message)
329         {
330             _toUsers=toUsers;
331             _toChannel=toChannel;
332             _message=message;
333         }
334         
335         public void fromJSON(Map object)
336         {
337             throw new UnsupportedOperationException();
338         }
339 
340         public void toJSON(Output out)
341         {
342             if (_toUser!=null)
343                 out.add("to",_toUser);
344             else if (_toUsers!=null)
345                 out.add("to",_toUsers);
346             out.add("channel",_toChannel);
347             out.add("from",_setiId);
348             out.add("message",_message);
349         }   
350     }
351     
352     /* ------------------------------------------------------------ */
353     /* ------------------------------------------------------------ */
354     class SetiPresence implements JSON.Convertible
355     {
356         String _user;
357         boolean _on;
358 
359         SetiPresence(String user,boolean on)
360         {
361             _user=user;
362             _on=on;
363         }
364         
365         public void fromJSON(Map object)
366         {
367             throw new UnsupportedOperationException();
368         }
369 
370         public void toJSON(Output out)
371         {
372             out.add("from",_user);
373             out.add(_on?"on":"off",_setiId);
374         }
375     }
376     
377 }