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("/","_");
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          // TODO stop client
86      }
87      
88      /* ------------------------------------------------------------ */
89      public void associate(final String userId,final Client client)
90      {
91          System.err.println("associate "+userId+" with "+client);
92          _uid2Location.put(userId,new LocalLocation(client));
93          userId2Shard(userId).associate(userId);
94      }
95  
96      /* ------------------------------------------------------------ */
97      public void disassociate(final String userId)
98      {
99          System.err.println("disassociate "+userId);
100         _uid2Location.remove(userId);
101         userId2Shard(userId).disassociate(userId);
102     }
103 
104     /* ------------------------------------------------------------ */
105     public void sendMessage(final String toUser,final String toChannel,final Object message)
106     {
107         Location location = _uid2Location.get(toUser);
108         if (location==null)
109             location = userId2Shard(toUser);
110         
111         location.sendMessage(toUser,toChannel,message);
112     }
113 
114     /* ------------------------------------------------------------ */
115     public void sendMessage(final Collection<String> toUsers,final String toChannel,final Object message)
116     {
117         // break toUsers in to shards
118         MultiMap shard2users = new MultiMap();
119         for (String userId:toUsers)
120         {       
121             ShardLocation shard = userId2Shard(userId);
122             shard2users.add(shard,userId);
123         }
124         
125         // for each shard
126         for (Map.Entry<ShardLocation,Object> entry : (Set<Map.Entry<ShardLocation,Object>>)shard2users.entrySet())
127         {
128             // TODO, we could look at all users in shard to see if we
129             // know a setiId for each, and if so, break the user list
130             // up into a message for each seti-id. BUT it is probably 
131             // more efficient just to send to the entire shard (unless
132             // the number of nodes in the shard is greater than the
133             // number of users).
134             
135             ShardLocation shard = entry.getKey();
136             Object lazyUsers = entry.getValue();
137             
138             if (LazyList.size(lazyUsers)==1)
139                 shard.sendMessage((String)lazyUsers,toChannel,message);
140             else
141                 shard.sendMessage((List<String>)lazyUsers,toChannel,message);
142         }
143     }
144     
145     /* ------------------------------------------------------------ */
146     protected ShardLocation userId2Shard(final String userId)
147     {
148         return _allShardLocation;
149     }
150 
151     /* ------------------------------------------------------------ */
152     protected void receive(final Client from, final Client to, final Message msg)
153     {
154         System.err.println("SETI "+_oort+":: "+msg);
155 
156         if (!(msg.getData() instanceof Map))
157             return;
158         
159         Map<String,Object> data = (Map<String,Object>)msg.getData();
160 
161         final String toUid=(String)data.get("to");
162         final String fromUid=(String)data.get("from");
163         Object message = data.get("message");
164         String on = (String)data.get("on");
165         
166         if (on!=null)
167         {
168             _uid2Location.put(fromUid,new SetiLocation("/seti/"+on));
169             System.err.println(_oort+":: "+fromUid+" on "+on);
170         }
171         else 
172         {
173             String off = (String)data.get("off");
174             if (off!=null)
175             {
176                 // TODO check off value
177                 _uid2Location.remove(fromUid,new SetiLocation(off));
178                 System.err.println(_oort+":: "+fromUid+" off ");
179             }
180         }
181         
182         if (message!=null && toUid!=null)
183         {
184             final String toChannel=(String)data.get("channel");
185             final Location location=_uid2Location.get(toUid);
186             final boolean to_shard = !_setiChannelId.equals(msg.getChannel());
187             
188             if (location!=null)
189             {
190                 location.receive(toUid,toChannel,message);
191             }
192             else
193             {
194                 
195             }
196             
197         }
198         
199     }
200 
201 
202     /* ------------------------------------------------------------ */
203     /* ------------------------------------------------------------ */
204     private interface Location
205     {
206         public void sendMessage(String toUser,String toChannel,Object message);
207         public void receive(String toUser,String toChannel,Object message);
208     }
209     
210 
211     /* ------------------------------------------------------------ */
212     /* ------------------------------------------------------------ */
213     class LocalLocation implements Location
214     {
215         Client _client;
216         
217         LocalLocation(Client client)
218         {
219             _client=client;
220         }
221 
222         public void sendMessage(String toUser, String toChannel, Object message)
223         {
224             _client.deliver(Seti.this._client,toChannel,message,null);
225         }
226 
227         public void receive(String toUser, String toChannel, Object message)
228         {
229             _client.deliver(Seti.this._client,toChannel,message,null);
230         }
231     }
232 
233     /* ------------------------------------------------------------ */
234     /* ------------------------------------------------------------ */
235     class SetiLocation implements Location
236     {
237         Channel _channel;
238 
239         SetiLocation(String channelId)
240         {
241             _channel=_oort._bayeux.getChannel(channelId,true);
242         }
243         
244         SetiLocation(Channel channel)
245         {
246             _channel=channel;
247         }
248         
249         public void sendMessage(String toUser, String toChannel, Object message)
250         {
251             _channel.publish(Seti.this._client,new SetiMessage(toUser,toChannel,message),null);
252         }
253 
254         public void receive(String toUser, String toChannel, Object message)
255         {
256             
257         }
258 
259         public boolean equals(Object o)
260         {
261             return o instanceof SetiLocation &&
262             ((SetiLocation)o)._channel.equals(_channel);
263         }
264         
265         public int hashCode()
266         {
267             return _channel.hashCode();
268         }
269     }
270 
271     /* ------------------------------------------------------------ */
272     /* ------------------------------------------------------------ */
273     class ShardLocation implements Location
274     {
275         Channel _channel;
276         
277         ShardLocation(String shardId)
278         {
279             _channel=_oort._bayeux.getChannel("/seti/"+shardId,true);
280             
281         }
282         
283         ShardLocation(Channel channel)
284         {
285             _channel=channel;
286         }
287         
288         public void sendMessage(final Collection<String> toUsers, final String toChannel, final Object message)
289         {
290             _channel.publish(Seti.this._client,new SetiMessage(toUsers,toChannel,message),null);
291         }
292 
293         public void sendMessage(String toUser, String toChannel, Object message)
294         {
295             _channel.publish(Seti.this._client,new SetiMessage(toUser,toChannel,message),null);
296         }
297         
298         public void receive(String toUser, String toChannel, Object message)
299         {
300             
301         }
302         
303         public void associate(final String user)
304         {
305             _channel.publish(Seti.this._client,new SetiPresence(user,true),null);
306         }
307         
308         public void disassociate(final String user)
309         {
310             _channel.publish(Seti.this._client,new SetiPresence(user,false),null);
311         }
312     }
313 
314     /* ------------------------------------------------------------ */
315     /* ------------------------------------------------------------ */
316     class SetiMessage implements JSON.Convertible
317     {
318         String _toUser;
319         Collection<String> _toUsers;
320         String _toChannel;
321         Object _message;
322 
323         SetiMessage(String toUser,String toChannel, Object message)
324         {
325             _toUser=toUser;
326             _toChannel=toChannel;
327             _message=message;
328         }
329         
330         SetiMessage(Collection<String> toUsers,String toChannel, Object message)
331         {
332             _toUsers=toUsers;
333             _toChannel=toChannel;
334             _message=message;
335         }
336         
337         public void fromJSON(Map object)
338         {
339             throw new UnsupportedOperationException();
340         }
341 
342         public void toJSON(Output out)
343         {
344             if (_toUser!=null)
345                 out.add("to",_toUser);
346             else if (_toUsers!=null)
347                 out.add("to",_toUsers);
348             out.add("channel",_toChannel);
349             out.add("from",_setiId);
350             out.add("message",_message);
351         }   
352     }
353     
354     /* ------------------------------------------------------------ */
355     /* ------------------------------------------------------------ */
356     class SetiPresence implements JSON.Convertible
357     {
358         String _user;
359         boolean _on;
360 
361         SetiPresence(String user,boolean on)
362         {
363             _user=user;
364             _on=on;
365         }
366         
367         public void fromJSON(Map object)
368         {
369             throw new UnsupportedOperationException();
370         }
371 
372         public void toJSON(Output out)
373         {
374             out.add("from",_user);
375             out.add(_on?"on":"off",_setiId);
376         }
377     }
378     
379 }