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
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
118 MultiMap shard2users = new MultiMap();
119 for (String userId:toUsers)
120 {
121 ShardLocation shard = userId2Shard(userId);
122 shard2users.add(shard,userId);
123 }
124
125
126 for (Map.Entry<ShardLocation,Object> entry : (Set<Map.Entry<ShardLocation,Object>>)shard2users.entrySet())
127 {
128
129
130
131
132
133
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
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 }