1 package org.cometd.oort;
2
3 import java.io.IOException;
4 import java.security.SecureRandom;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Random;
11 import java.util.Set;
12 import java.util.Timer;
13 import java.util.concurrent.ConcurrentHashMap;
14 import java.util.concurrent.ConcurrentMap;
15
16 import org.cometd.Bayeux;
17 import org.cometd.Channel;
18 import org.cometd.Client;
19 import org.cometd.ClientBayeuxListener;
20 import org.cometd.Extension;
21 import org.cometd.Message;
22 import org.cometd.MessageListener;
23 import org.cometd.QueueListener;
24 import org.cometd.RemoveListener;
25 import org.mortbay.component.AbstractLifeCycle;
26 import org.mortbay.jetty.HttpURI;
27 import org.mortbay.jetty.client.HttpClient;
28 import org.mortbay.log.Log;
29 import org.mortbay.util.ajax.JSON;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 public class Oort extends AbstractLifeCycle
46 {
47 public final static String OORT_URL = "oort.url";
48 public final static String OORT_CLOUD = "oort.cloud";
49 public final static String OORT_CHANNELS = "oort.channels";
50 public final static String OORT_ATTRIBUTE = "org.cometd.oort.Oort";
51
52 protected String _url;
53 protected String _secret;
54 protected Bayeux _bayeux;
55 protected HttpClient _httpClient=new HttpClient();
56 protected Timer _timer=new Timer();
57 protected Random _random=new SecureRandom();
58 protected Client _oortClient;
59 protected List<MessageListener> _oortMessageListeners = new ArrayList<MessageListener>();
60
61 protected Map<String,OortComet> _knownCommets = new HashMap<String,OortComet>();
62 protected Set<String> _channels = new HashSet<String>();
63
64
65 Oort(String id,Bayeux bayeux)
66 {
67 _url=id;
68 _bayeux=bayeux;
69 _secret=Long.toHexString(_random.nextLong());
70
71 _oortClient=_bayeux.newClient("oort");
72 _oortClient.addListener(new RootOortClientListener());
73 _bayeux.getChannel("/oort/cloud",true).subscribe(_oortClient);
74 bayeux.addExtension(new OortExtension());
75 }
76
77
78 public Bayeux getBayeux()
79 {
80 return _bayeux;
81 }
82
83
84
85
86
87 public String getURL()
88 {
89 return _url;
90 }
91
92
93 public String getSecret()
94 {
95 return _secret;
96 }
97
98
99 protected void doStart() throws Exception
100 {
101 super.doStart();
102 _httpClient.start();
103 }
104
105
106
107
108
109
110
111
112
113
114
115 public OortComet observeComet(String cometUrl)
116 {
117 synchronized (this)
118 {
119 if (_url.equals(cometUrl))
120 return null;
121 OortComet comet = _knownCommets.get(cometUrl);
122 if (comet==null)
123 {
124 try
125 {
126 comet = new OortComet(this,cometUrl);
127 _knownCommets.put(cometUrl,comet);
128 comet.start();
129 }
130 catch(Exception e)
131 {
132 throw new IllegalStateException(e);
133 }
134 }
135 return comet;
136 }
137 }
138
139
140
141
142
143
144
145
146
147
148
149 void observedComets(Set<String> comets)
150 {
151 synchronized (this)
152 {
153 Set<String> known=getKnownComets();
154 for (String comet : comets)
155 if (!_url.equals(comet))
156 observeComet(comet);
157 known=getKnownComets();
158
159 if (!comets.containsAll(known))
160 _bayeux.getChannel("/oort/cloud",true).publish(_oortClient,known,null);
161 }
162 }
163
164
165
166
167
168 public Set<String> getKnownComets()
169 {
170 synchronized (this)
171 {
172 Set<String> comets = new HashSet<String>(_knownCommets.keySet());
173 comets.add(_url);
174 return comets;
175 }
176 }
177
178
179
180
181
182
183
184
185
186
187
188 public void observeChannel(String channelId)
189 {
190 synchronized (this)
191 {
192 if (!_channels.contains(channelId))
193 {
194 _channels.add(channelId);
195 for (OortComet comet : _knownCommets.values())
196 if (comet.isHandshook())
197 comet.subscribe(channelId);
198 }
199 }
200 }
201
202
203
204
205
206
207
208 public void addOortMessageListener(MessageListener listener)
209 {
210 synchronized (this)
211 {
212 _oortMessageListeners.add(listener);
213 }
214 }
215
216
217
218
219
220
221
222 public boolean removeOortClientListener(MessageListener listener)
223 {
224 synchronized (this)
225 {
226 return _oortMessageListeners.remove(listener);
227 }
228 }
229
230
231 public boolean isOort(Client client)
232 {
233 return client==_oortClient;
234 }
235
236
237 public String toString()
238 {
239 return _url;
240 }
241
242
243
244
245
246
247
248
249
250
251 protected void oortHandshook(String oortUrl,String oortSecret,String clientId)
252 {
253 Log.info(this+": "+clientId+" is oort "+oortUrl);
254 if (!_knownCommets.containsKey(oortUrl))
255 observeComet(oortUrl);
256
257 Client client = _bayeux.getClient(clientId);
258
259 client.addExtension(new RemoteOortClientExtension());
260 }
261
262
263
264
265
266
267
268
269 protected class OortExtension implements Extension
270 {
271 public Message rcv(Client from, Message message)
272 {
273 return message;
274 }
275
276 public Message rcvMeta(Client from, Message message)
277 {
278 return message;
279 }
280
281 public Message send(Client from, Message message)
282 {
283 return message;
284 }
285
286 public Message sendMeta(Client from, Message message)
287 {
288 if (message.getChannel().equals(Bayeux.META_HANDSHAKE) && Boolean.TRUE.equals(message.get(Bayeux.SUCCESSFUL_FIELD)))
289 {
290 Message rcv = message.getAssociated();
291 System.err.println(_url+" --> "+rcv);
292
293 Map<String,Object> rcvExt = (Map<String,Object>)rcv.get("ext");
294 if (rcvExt!=null)
295 {
296 Map<String,Object> oort = (Map<String,Object>)rcvExt.get("oort");
297 if (oort!=null)
298 {
299 String cometUrl = (String)oort.get("comet");
300 String oortUrl = (String)oort.get("oort");
301
302 if (getURL().equals(cometUrl))
303 {
304 String oortSecret = (String)oort.get("oortSecret");
305
306 oortHandshook(oortUrl,oortSecret,message.getClientId());
307
308 Object ext=message.get("ext");
309
310 Map<String,Object> sndExt = (Map<String,Object>)((ext instanceof JSON.Literal)?JSON.parse(ext.toString()):ext);
311 if (sndExt==null)
312 sndExt = new HashMap<String,Object>();
313 oort.put("cometSecret",getSecret());
314 sndExt.put("oort",oort);
315 message.put("ext",sndExt);
316 }
317 }
318 }
319 System.err.println(_url+" <-- "+message);
320 }
321 return message;
322 }
323 }
324
325
326
327
328
329
330
331 protected class RemoteOortClientExtension implements Extension
332 {
333 public boolean queueMaxed(Client from, Client client, Message message)
334 {
335
336 boolean send = from!=_oortClient || message.getChannel().startsWith("/oort/");
337 return send;
338 }
339
340 public Message rcv(Client from, Message message)
341 {
342 return message;
343 }
344
345 public Message rcvMeta(Client from, Message message)
346 {
347 return message;
348 }
349
350 public Message send(Client from, Message message)
351 {
352
353 boolean send = !isOort(from) || message.getChannel().startsWith("/oort/");
354 return send?message:null;
355 }
356
357 public Message sendMeta(Client from, Message message)
358 {
359 return message;
360 }
361 }
362
363
364
365
366
367
368 protected class RootOortClientListener implements RemoveListener, MessageListener
369 {
370 public void removed(String clientId, boolean timeout)
371 {
372
373 }
374
375 public void deliver(Client fromClient, Client toClient, Message msg)
376 {
377 String channelId = msg.getChannel();
378 if (msg.getData()!=null)
379 {
380 if (channelId.equals("/oort/cloud") && msg.getData() instanceof Object[])
381 {
382 Object[] data = (Object[])msg.getData();
383 Set<String> comets = new HashSet<String>();
384 for (Object o:data)
385 comets.add(o.toString());
386 observedComets(comets);
387 }
388 }
389 }
390
391 }
392 }