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
116 MultiMap shard2users = new MultiMap();
117 for (String userId:toUsers)
118 {
119 ShardLocation shard = userId2Shard(userId);
120 shard2users.add(shard,userId);
121 }
122
123
124 for (Map.Entry<ShardLocation,Object> entry : (Set<Map.Entry<ShardLocation,Object>>)shard2users.entrySet())
125 {
126
127
128
129
130
131
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
153
154 if (!(msg.getData() instanceof Map))
155 return;
156
157
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
165 if (fromUid!=null)
166 {
167 if (on!=null)
168 {
169
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
178 _uid2Location.remove(fromUid,new SetiLocation("/seti/"+off));
179 }
180 }
181 }
182
183
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
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 }