1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.mortbay.cometd.client;
16
17 import java.io.IOException;
18 import java.net.URLEncoder;
19 import java.util.ArrayList;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Queue;
24 import java.util.Timer;
25 import java.util.TimerTask;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.CopyOnWriteArrayList;
28
29 import javax.servlet.http.Cookie;
30
31 import org.cometd.Bayeux;
32 import org.cometd.Client;
33 import org.cometd.ClientListener;
34 import org.cometd.Extension;
35 import org.cometd.Message;
36 import org.cometd.MessageListener;
37 import org.cometd.RemoveListener;
38 import org.mortbay.cometd.MessageImpl;
39 import org.mortbay.cometd.MessagePool;
40 import org.mortbay.component.AbstractLifeCycle;
41 import org.mortbay.io.Buffer;
42 import org.mortbay.io.ByteArrayBuffer;
43 import org.mortbay.jetty.HttpHeaders;
44 import org.mortbay.jetty.HttpSchemes;
45 import org.mortbay.jetty.HttpURI;
46 import org.mortbay.jetty.client.Address;
47 import org.mortbay.jetty.client.ContentExchange;
48 import org.mortbay.jetty.client.HttpClient;
49 import org.mortbay.jetty.client.HttpExchange;
50 import org.mortbay.log.Log;
51 import org.mortbay.util.ArrayQueue;
52 import org.mortbay.util.LazyList;
53 import org.mortbay.util.QuotedStringTokenizer;
54 import org.mortbay.util.ajax.JSON;
55
56
57
58
59
60
61
62
63
64
65
66 public class BayeuxClient extends AbstractLifeCycle implements Client
67 {
68 protected HttpClient _httpClient;
69
70 protected MessagePool _msgPool = new MessagePool();
71 private Queue<Message> _inQ = new ArrayQueue<Message>();
72 private Queue<Message> _outQ = new ArrayQueue<Message>();
73 protected Address _cometdAddress;
74 private Exchange _pull;
75 private Exchange _push;
76 private String _path = "/cometd";
77 private boolean _initialized = false;
78 private boolean _disconnecting = false;
79 private boolean _handshook = false;
80 private String _clientId;
81 private org.cometd.Listener _listener;
82 private List<RemoveListener> _rListeners;
83 private List<MessageListener> _mListeners;
84 private int _batch;
85 private boolean _formEncoded;
86 private Map<String, Cookie> _cookies = new ConcurrentHashMap<String, Cookie>();
87 private Advice _advice;
88 private Timer _timer;
89 private int _backoffInterval = 1000;
90 private int _backoffMaxRetries = 60;
91 private Buffer _scheme;
92 protected Extension[] _extensions;
93
94
95 public BayeuxClient(HttpClient client, String url)
96 {
97 this(client,url,null);
98 }
99
100
101 public BayeuxClient(HttpClient client, String url, Timer timer)
102 {
103 HttpURI uri = new HttpURI(url);
104 _httpClient = client;
105 _cometdAddress = new Address(uri.getHost(),uri.getPort());
106 _path=uri.getPath();
107
108 _timer = timer;
109 if (_timer == null)
110 _timer = new Timer("DefaultBayeuxClientTimer",true);
111
112 _scheme = (HttpSchemes.HTTPS.equals(uri.getScheme()))?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER;
113 }
114
115
116 public BayeuxClient(HttpClient client, Address address, String path, Timer timer)
117 {
118 _httpClient = client;
119 _cometdAddress = address;
120 _path = path;
121
122 _timer = timer;
123 if (_timer == null)
124 _timer = new Timer("DefaultBayeuxClientTimer",true);
125 }
126
127
128 public BayeuxClient(HttpClient client, Address address, String uri)
129 {
130 this(client,address,uri,null);
131 }
132
133
134 public void addExtension(Extension ext)
135 {
136 _extensions = (Extension[])LazyList.addToArray(_extensions,ext,Extension.class);
137 }
138
139
140 Extension[] getExtensions()
141 {
142 return _extensions;
143 }
144
145
146
147
148
149
150
151
152
153 public void setBackOffInterval(int interval)
154 {
155 _backoffInterval = interval;
156 }
157
158
159 public int getBackoffInterval()
160 {
161 return _backoffInterval;
162 }
163
164
165 public void setBackoffMaxRetries(int retries)
166 {
167 _backoffMaxRetries = retries;
168 }
169
170
171 public int getBackoffMaxRetries()
172 {
173 return _backoffMaxRetries;
174 }
175
176
177
178
179
180
181
182 public String getId()
183 {
184 return _clientId;
185 }
186
187
188 protected void doStart() throws Exception
189 {
190 super.doStart();
191 synchronized (_outQ)
192 {
193 if (!_initialized && _pull == null)
194 {
195 _pull = new Handshake();
196 send((Exchange)_pull,false);
197 }
198 }
199 }
200
201
202 protected void doStop() throws Exception
203 {
204 remove();
205 super.doStop();
206 _pull = null;
207 _push = null;
208 }
209
210
211 public boolean isPolling()
212 {
213 synchronized (_outQ)
214 {
215 return isRunning() && (_pull != null);
216 }
217 }
218
219
220
221
222
223 public void deliver(Client from, Message message)
224 {
225 if (!isRunning())
226 throw new IllegalStateException("Not running");
227
228 synchronized (_inQ)
229 {
230 if (_mListeners == null)
231 _inQ.add(message);
232 else
233 {
234 for (MessageListener l : _mListeners)
235 l.deliver(from,this,message);
236 }
237 }
238 }
239
240
241
242
243
244
245
246
247 public void deliver(Client from, String toChannel, Object data, String id)
248 {
249 if (!isRunning())
250 throw new IllegalStateException("Not running");
251
252 Message message = new MessageImpl();
253
254 message.put(Bayeux.CHANNEL_FIELD,toChannel);
255 message.put(Bayeux.DATA_FIELD,data);
256 if (id != null)
257 message.put(Bayeux.ID_FIELD,id);
258
259 synchronized (_inQ)
260 {
261 if (_mListeners == null)
262 _inQ.add(message);
263 else
264 {
265 for (MessageListener l : _mListeners)
266 l.deliver(from,this,message);
267 }
268 }
269 }
270
271
272
273
274
275 public org.cometd.Listener getListener()
276 {
277 synchronized (_inQ)
278 {
279 return _listener;
280 }
281 }
282
283
284
285
286
287
288
289 public boolean hasMessages()
290 {
291 synchronized (_inQ)
292 {
293 return _inQ.size() > 0;
294 }
295 }
296
297
298
299
300
301
302
303 public boolean isLocal()
304 {
305 return false;
306 }
307
308
309
310
311
312
313
314 private void publish(Message msg)
315 {
316 if (!isRunning())
317 throw new IllegalStateException("Not running");
318
319 synchronized (_outQ)
320 {
321 _outQ.add(msg);
322
323 if (_batch == 0 && _initialized && _push == null)
324 {
325 _push = new Publish();
326 try
327 {
328 send(_push);
329 }
330 catch (Exception e)
331 {
332 metaPublishFail(e,((Publish)_push).getOutboundMessages());
333 }
334 }
335 }
336 }
337
338
339
340
341
342
343
344
345 public void publish(String toChannel, Object data, String msgId)
346 {
347 if (!isRunning())
348 throw new IllegalStateException("Not running");
349
350 Message msg = new MessageImpl();
351 msg.put(Bayeux.CHANNEL_FIELD,toChannel);
352 msg.put(Bayeux.DATA_FIELD,data);
353 if (msgId != null)
354 msg.put(Bayeux.ID_FIELD,msgId);
355 publish(msg);
356 }
357
358
359
360
361
362
363
364 public void subscribe(String toChannel)
365 {
366 if (!isRunning())
367 throw new IllegalStateException("Not running");
368
369 Message msg = new MessageImpl();
370 msg.put(Bayeux.CHANNEL_FIELD,Bayeux.META_SUBSCRIBE);
371 msg.put(Bayeux.SUBSCRIPTION_FIELD,toChannel);
372 publish(msg);
373 }
374
375
376
377
378
379
380
381 public void unsubscribe(String toChannel)
382 {
383 if (!isRunning())
384 throw new IllegalStateException("Not running");
385
386 Message msg = new MessageImpl();
387 msg.put(Bayeux.CHANNEL_FIELD,Bayeux.META_UNSUBSCRIBE);
388 msg.put(Bayeux.SUBSCRIPTION_FIELD,toChannel);
389 publish(msg);
390 }
391
392
393
394
395
396
397 public void remove()
398 {
399 disconnect();
400 }
401
402
403
404
405
406 public void disconnect()
407 {
408 if (isStopped())
409 throw new IllegalStateException("Not running");
410
411 Message msg = new MessageImpl();
412 msg.put(Bayeux.CHANNEL_FIELD,Bayeux.META_DISCONNECT);
413
414 synchronized (_outQ)
415 {
416 _outQ.add(msg);
417 _disconnecting = true;
418 if (_batch == 0 && _initialized && _push == null)
419 {
420 _push = new Publish();
421 try
422 {
423 send(_push);
424 }
425 catch (IOException e)
426 {
427 Log.warn(e);
428 send(_push,true);
429 }
430 }
431 _initialized = false;
432 }
433 }
434
435
436
437
438
439 public void setListener(org.cometd.Listener listener)
440 {
441 synchronized (_inQ)
442 {
443 if (_listener != null)
444 removeListener(_listener);
445 _listener = listener;
446 if (_listener != null)
447 addListener(_listener);
448 }
449 }
450
451
452
453
454
455
456
457
458 public List<Message> takeMessages()
459 {
460 synchronized (_inQ)
461 {
462 LinkedList<Message> list = new LinkedList<Message>(_inQ);
463 _inQ.clear();
464 return list;
465 }
466 }
467
468
469
470
471
472
473
474 public void endBatch()
475 {
476 if (!isRunning())
477 throw new IllegalStateException("Not running");
478 synchronized (_outQ)
479 {
480 if (--_batch <= 0)
481 {
482 _batch = 0;
483 if ((_initialized || _disconnecting) && _push == null && _outQ.size() > 0)
484 {
485 _push = new Publish();
486 try
487 {
488 send(_push);
489 }
490 catch (IOException e)
491 {
492 metaPublishFail(e,((Publish)_push).getOutboundMessages());
493 }
494 }
495 }
496 }
497 }
498
499
500
501
502
503
504
505 public void startBatch()
506 {
507 if (!isRunning())
508 throw new IllegalStateException("Not running");
509
510 synchronized (_outQ)
511 {
512 _batch++;
513 }
514 }
515
516
517
518
519
520
521
522 protected void customize(HttpExchange exchange)
523 {
524 StringBuilder buf = null;
525 for (Cookie cookie : _cookies.values())
526 {
527 if (buf == null)
528 buf = new StringBuilder();
529 else
530 buf.append("; ");
531 buf.append(cookie.getName());
532 buf.append("=");
533 buf.append(cookie.getValue());
534 }
535 if (buf != null)
536 exchange.addRequestHeader(HttpHeaders.COOKIE,buf.toString());
537
538 if (_scheme!=null)
539 exchange.setScheme(_scheme);
540 }
541
542
543 public void setCookie(Cookie cookie)
544 {
545 _cookies.put(cookie.getName(),cookie);
546 }
547
548
549
550
551
552 protected class Exchange extends ContentExchange
553 {
554 Message[] _responses;
555 int _connectFailures;
556 int _backoffRetries = 0;
557 String _jsonOut;
558
559
560 Exchange(String info)
561 {
562 setMethod("POST");
563 setScheme(HttpSchemes.HTTP_BUFFER);
564 setAddress(_cometdAddress);
565 setURI(_path + "/" + info);
566 setRequestContentType(_formEncoded?"application/x-www-form-urlencoded;charset=utf-8":"text/json;charset=utf-8");
567 }
568
569
570 public int getBackoffRetries()
571 {
572 return _backoffRetries;
573 }
574
575
576 public void incBackoffRetries()
577 {
578 ++_backoffRetries;
579 }
580
581
582 protected void setMessage(String message)
583 {
584 message=extendOut(message);
585 setJsonOut(message);
586 }
587
588
589 protected void setMessages(Queue<Message> messages)
590 {
591 for (Message msg : messages)
592 {
593 msg.put(Bayeux.CLIENT_FIELD,_clientId);
594 extendOut(msg);
595 }
596 setJsonOut(JSON.toString(messages));
597 }
598
599
600 protected void setJsonOut(String json)
601 {
602 try
603 {
604 _jsonOut = json;
605
606 if (_formEncoded)
607 setRequestContent(new ByteArrayBuffer("message=" + URLEncoder.encode(_jsonOut,"utf-8")));
608 else
609 setRequestContent(new ByteArrayBuffer(_jsonOut,"utf-8"));
610 }
611 catch (Exception e)
612 {
613 Log.warn(e);
614 setRequestContent(new ByteArrayBuffer(_jsonOut));
615 }
616 }
617
618
619 protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
620 {
621 super.onResponseStatus(version,status,reason);
622 }
623
624
625 protected void onResponseHeader(Buffer name, Buffer value) throws IOException
626 {
627 super.onResponseHeader(name,value);
628 if (!isRunning())
629 return;
630
631 if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.SET_COOKIE_ORDINAL)
632 {
633 String cname = null;
634 String cvalue = null;
635
636 QuotedStringTokenizer tok = new QuotedStringTokenizer(value.toString(),"=;",false,false);
637 tok.setSingle(false);
638
639 if (tok.hasMoreElements())
640 cname = tok.nextToken();
641 if (tok.hasMoreElements())
642 cvalue = tok.nextToken();
643
644 Cookie cookie = new Cookie(cname,cvalue);
645
646 while (tok.hasMoreTokens())
647 {
648 String token = tok.nextToken();
649 if ("Version".equalsIgnoreCase(token))
650 cookie.setVersion(Integer.parseInt(tok.nextToken()));
651 else if ("Comment".equalsIgnoreCase(token))
652 cookie.setComment(tok.nextToken());
653 else if ("Path".equalsIgnoreCase(token))
654 cookie.setPath(tok.nextToken());
655 else if ("Domain".equalsIgnoreCase(token))
656 cookie.setDomain(tok.nextToken());
657 else if ("Expires".equalsIgnoreCase(token))
658 {
659 tok.nextToken();
660
661 }
662 else if ("Max-Age".equalsIgnoreCase(token))
663 {
664 tok.nextToken();
665
666 }
667 else if ("Secure".equalsIgnoreCase(token))
668 cookie.setSecure(true);
669 }
670
671 BayeuxClient.this.setCookie(cookie);
672 }
673 }
674
675
676 protected void onResponseComplete() throws IOException
677 {
678 if (!isRunning())
679 return;
680
681 super.onResponseComplete();
682
683 if (getResponseStatus() == 200)
684 {
685 String content = getResponseContent();
686
687 if (content == null || content.length() == 0)
688 throw new IllegalStateException();
689 _responses = _msgPool.parse(content);
690
691 if (_responses!=null)
692 for (int i=0;i<_responses.length;i++)
693 extendIn(_responses[i]);
694 }
695 }
696
697
698 protected void resend(boolean backoff)
699 {
700 if (!isRunning())
701 return;
702 setJsonOut(_jsonOut);
703 if (!send(this,backoff))
704 Log.warn("Retries exhausted");
705 }
706
707
708 protected void recycle()
709 {
710 if (_responses!=null)
711 for (Message msg:_responses)
712 if (msg instanceof MessageImpl)
713 ((MessageImpl)msg).decRef();
714 _responses=null;
715 }
716 }
717
718
719
720
721
722
723
724 protected class Handshake extends Exchange
725 {
726 public final static String __HANDSHAKE = "[{" + "\"channel\":\"/meta/handshake\"," + "\"version\":\"0.9\"," + "\"minimumVersion\":\"0.9\"" + "}]";
727
728 Handshake()
729 {
730 super("handshake");
731 setMessage(__HANDSHAKE);
732 }
733
734
735
736
737
738
739
740
741 protected void onResponseComplete() throws IOException
742 {
743 super.onResponseComplete();
744
745 if (!isRunning())
746 return;
747
748 if (getResponseStatus() == 200 && _responses != null && _responses.length > 0)
749 {
750 Message response = _responses[0];
751 Boolean successful = (Boolean)response.get(Bayeux.SUCCESSFUL_FIELD);
752
753
754 Map adviceField = (Map)response.get(Bayeux.ADVICE_FIELD);
755 if (adviceField != null)
756 _advice = new Advice(adviceField);
757
758 if (successful != null && successful.booleanValue())
759 {
760 metaHandshake(true,_handshook,response);
761 _handshook = true;
762 if (Log.isDebugEnabled())
763 Log.debug("Successful handshake, sending connect");
764 _clientId = (String)response.get(Bayeux.CLIENT_FIELD);
765 _pull = new Connect();
766 send(_pull,false);
767 }
768 else
769 {
770 metaHandshake(false,false,response);
771 _handshook = false;
772 if (_advice != null && _advice.isReconnectNone())
773 throw new IOException("Handshake failed with advice reconnect=none :" + _responses[0]);
774 else if (_advice != null && _advice.isReconnectHandshake())
775 {
776 _pull = new Handshake();
777 if (!send(_pull,true))
778 throw new IOException("Handshake, retries exhausted");
779 }
780 else
781
782 {
783 _pull = new Connect();
784 if (!send(_pull,true))
785 throw new IOException("Connect after handshake, retries exhausted");
786 }
787 }
788 }
789 else
790 {
791 Message error=_msgPool.newMessage();
792 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
793 error.put("status",new Integer(getResponseStatus()));
794 error.put("content",getResponseContent());
795
796 metaHandshake(false,false,error);
797 resend(true);
798 }
799
800 recycle();
801 }
802
803
804 protected void onExpire()
805 {
806
807 Message error=_msgPool.newMessage();
808 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
809 error.put("failure","expired");
810 metaHandshake(false,false,error);
811 resend(true);
812 }
813
814
815 protected void onConnectionFailed(Throwable ex)
816 {
817
818 Message error=_msgPool.newMessage();
819 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
820 error.put("failure",ex.toString());
821 error.put("exception",ex);
822 metaHandshake(false,false,error);
823 resend(true);
824 }
825
826
827 protected void onException(Throwable ex)
828 {
829
830 Message error=_msgPool.newMessage();
831 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
832 error.put("failure",ex.toString());
833 error.put("exception",ex);
834 metaHandshake(false,false,error);
835 resend(true);
836 }
837 }
838
839
840
841
842
843
844 protected class Connect extends Exchange
845 {
846 String _connectString;
847
848 Connect()
849 {
850 super("connect");
851 _connectString = "{" + "\"channel\":\"/meta/connect\"," + "\"clientId\":\"" + _clientId + "\"," + "\"connectionType\":\"long-polling\"" + "}";
852 setMessage(_connectString);
853 }
854
855 protected void onResponseComplete() throws IOException
856 {
857 super.onResponseComplete();
858 if (!isRunning())
859 return;
860
861 if (getResponseStatus() == 200 && _responses != null && _responses.length > 0)
862 {
863 try
864 {
865 startBatch();
866
867 for (int i = 0; i < _responses.length; i++)
868 {
869 Message msg = _responses[i];
870
871
872 Map adviceField = (Map)msg.get(Bayeux.ADVICE_FIELD);
873 if (adviceField != null)
874 _advice = new Advice(adviceField);
875
876 if (Bayeux.META_CONNECT.equals(msg.get(Bayeux.CHANNEL_FIELD)))
877 {
878 Boolean successful = (Boolean)msg.get(Bayeux.SUCCESSFUL_FIELD);
879 if (successful != null && successful.booleanValue())
880 {
881 if (!isInitialized())
882 {
883 setInitialized(true);
884 synchronized (_outQ)
885 {
886 if (_outQ.size() > 0)
887 {
888 _push = new Publish();
889 send(_push);
890 }
891 }
892 }
893
894
895 metaConnect(true,msg);
896 _pull = new Connect();
897 send(_pull,false);
898 }
899 else
900 {
901
902
903
904
905
906
907
908
909 setInitialized(false);
910 metaConnect(false,msg);
911 if (_advice != null && _advice.isReconnectNone())
912 throw new IOException("Connect failed, advice reconnect=none");
913 else if (_advice != null && _advice.isReconnectHandshake())
914 {
915 if (Log.isDebugEnabled())
916 Log.debug("connect received success=false, advice is to rehandshake");
917 _pull = new Handshake();
918 send(_pull,true);
919 }
920 else
921
922 {
923 if (Log.isDebugEnabled())
924 Log.debug("Assuming retry=reconnect");
925 resend(true);
926 }
927 }
928 }
929 deliver(null,msg);
930 }
931 }
932 finally
933 {
934 endBatch();
935 }
936 }
937 else
938 {
939 Message error=_msgPool.newMessage();
940 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
941 error.put("status",getResponseStatus());
942 error.put("content",getResponseContent());
943 metaConnect(false,error);
944 resend(true);
945 }
946
947 recycle();
948 }
949
950
951 protected void onExpire()
952 {
953
954 setInitialized(false);
955 Message error=_msgPool.newMessage();
956 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
957 error.put("failure","expired");
958 metaConnect(false,error);
959 resend(true);
960 }
961
962
963 protected void onConnectionFailed(Throwable ex)
964 {
965
966 setInitialized(false);
967 Message error=_msgPool.newMessage();
968 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
969 error.put("failure",ex.toString());
970 error.put("exception",ex);
971 metaConnect(false,error);
972 resend(true);
973 }
974
975
976 protected void onException(Throwable ex)
977 {
978
979 setInitialized(false);
980 Message error=_msgPool.newMessage();
981 error.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
982 error.put("failure",ex.toString());
983 error.put("exception",ex);
984 metaConnect(false,error);
985 resend(true);
986 }
987 }
988
989
990
991
992
993
994 protected class Publish extends Exchange
995 {
996 Publish()
997 {
998 super("publish");
999 synchronized (_outQ)
1000 {
1001 if (_outQ.size() == 0)
1002 return;
1003 setMessages(_outQ);
1004 _outQ.clear();
1005 }
1006 }
1007
1008 protected Message[] getOutboundMessages()
1009 {
1010 try
1011 {
1012 return _msgPool.parse(_jsonOut);
1013 }
1014 catch (IOException e)
1015 {
1016 Log.warn("Error converting outbound messages");
1017 if (Log.isDebugEnabled())
1018 Log.debug(e);
1019 return null;
1020 }
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030 protected void onResponseComplete() throws IOException
1031 {
1032 if (!isRunning())
1033 return;
1034
1035 super.onResponseComplete();
1036 try
1037 {
1038 synchronized (_outQ)
1039 {
1040 startBatch();
1041 _push = null;
1042 }
1043
1044 if (getResponseStatus() == 200 && _responses != null && _responses.length > 0)
1045 {
1046 for (int i = 0; i < _responses.length; i++)
1047 {
1048 Message msg = _responses[i];
1049 deliver(null,msg);
1050 }
1051 }
1052 else
1053 {
1054 Log.warn("Publish, error=" + getResponseStatus());
1055 }
1056 }
1057 finally
1058 {
1059 endBatch();
1060 }
1061 recycle();
1062 }
1063
1064
1065 protected void onExpire()
1066 {
1067 super.onExpire();
1068 metaPublishFail(null,this.getOutboundMessages());
1069 }
1070
1071
1072 protected void onConnectionFailed(Throwable ex)
1073 {
1074 super.onConnectionFailed(ex);
1075 metaPublishFail(ex,this.getOutboundMessages());
1076 }
1077
1078
1079 protected void onException(Throwable ex)
1080 {
1081 super.onException(ex);
1082 metaPublishFail(ex,this.getOutboundMessages());
1083 }
1084 }
1085
1086
1087 public void addListener(ClientListener listener)
1088 {
1089 synchronized (_inQ)
1090 {
1091 boolean added=false;
1092 if (listener instanceof MessageListener)
1093 {
1094 added=true;
1095 if (_mListeners == null)
1096 _mListeners = new ArrayList<MessageListener>();
1097 _mListeners.add((MessageListener)listener);
1098 }
1099 if (listener instanceof RemoveListener)
1100 {
1101 added=true;
1102 if (_rListeners == null)
1103 _rListeners = new ArrayList<RemoveListener>();
1104 _rListeners.add((RemoveListener)listener);
1105 }
1106
1107 if (!added)
1108 throw new IllegalArgumentException();
1109 }
1110 }
1111
1112
1113 public void removeListener(ClientListener listener)
1114 {
1115 synchronized (_inQ)
1116 {
1117 if (listener instanceof MessageListener)
1118 {
1119 if (_mListeners != null)
1120 _mListeners.remove((MessageListener)listener);
1121 }
1122 if (listener instanceof RemoveListener)
1123 {
1124 if (_rListeners != null)
1125 _rListeners.remove((RemoveListener)listener);
1126 }
1127 }
1128 }
1129
1130
1131 public int getMaxQueue()
1132 {
1133 return -1;
1134 }
1135
1136
1137 public Queue<Message> getQueue()
1138 {
1139 return _inQ;
1140 }
1141
1142
1143 public void setMaxQueue(int max)
1144 {
1145 if (max != -1)
1146 throw new UnsupportedOperationException();
1147 }
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 protected boolean send(final Exchange exchange, final boolean backoff)
1159 {
1160 long interval = (_advice != null?_advice.getInterval():0);
1161
1162 if (backoff)
1163 {
1164 int retries = exchange.getBackoffRetries();
1165 if (Log.isDebugEnabled())
1166 Log.debug("Send with backoff, retries=" + retries + " for " + exchange);
1167 if (retries >= _backoffMaxRetries)
1168 return false;
1169
1170 exchange.incBackoffRetries();
1171 interval += (retries * _backoffInterval);
1172 }
1173
1174 if (interval > 0)
1175 {
1176 TimerTask task = new TimerTask()
1177 {
1178 public void run()
1179 {
1180 try
1181 {
1182 send(exchange);
1183 }
1184 catch (IOException e)
1185 {
1186 Log.warn("Delayed send, retry: ",e);
1187 send(exchange,true);
1188 }
1189 }
1190 };
1191 if (Log.isDebugEnabled())
1192 Log.debug("Delay " + interval + " send of " + exchange);
1193 _timer.schedule(task,interval);
1194 }
1195 else
1196 {
1197 try
1198 {
1199 send(exchange);
1200 }
1201 catch (IOException e)
1202 {
1203 Log.warn("Send, retry on fail: ",e);
1204 return send(exchange,true);
1205 }
1206 }
1207 return true;
1208
1209 }
1210
1211
1212
1213
1214
1215
1216
1217
1218 protected void send(HttpExchange exchange) throws IOException
1219 {
1220 exchange.reset();
1221 customize(exchange);
1222 if (Log.isDebugEnabled())
1223 Log.debug("Send: using any connection=" + exchange);
1224 _httpClient.send(exchange);
1225 }
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236 protected void setInitialized(boolean b)
1237 {
1238 _initialized = b;
1239 }
1240
1241
1242 protected boolean isInitialized()
1243 {
1244 return _initialized;
1245 }
1246
1247
1248
1249
1250
1251
1252 protected void metaConnect(boolean success, Message message)
1253 {
1254 if (!success)
1255 Log.warn(this.toString()+" "+message.toString());
1256 }
1257
1258
1259
1260
1261
1262
1263
1264 protected void metaHandshake(boolean success, boolean reestablish, Message message)
1265 {
1266 if (!success)
1267 Log.warn(this.toString()+" "+message.toString());
1268 }
1269
1270
1271
1272
1273
1274 protected void metaPublishFail(Throwable e, Message[] messages)
1275 {
1276 Log.warn(this.toString(),e);
1277 }
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291 protected String extendOut(String msg)
1292 {
1293 if (_extensions==null)
1294 return msg;
1295
1296 try
1297 {
1298 Message[] messages = _msgPool.parse(msg);
1299 for (int i=0; i<messages.length; i++)
1300 extendOut(messages[i]);
1301 if (messages.length==1 && msg.charAt(0)=='{')
1302 return _msgPool.getMsgJSON().toJSON(messages[0]);
1303 return _msgPool.getMsgJSON().toJSON(messages);
1304 }
1305 catch(IOException e)
1306 {
1307 Log.warn(e);
1308 return msg;
1309 }
1310 }
1311
1312
1313
1314
1315
1316
1317
1318 protected void extendOut(Message message)
1319 {
1320 if (_extensions!=null)
1321 {
1322 Message m = message;
1323 if (m.getChannel().startsWith(Bayeux.META_SLASH))
1324 for (int i=0;m!=null && i<_extensions.length;i++)
1325 m=_extensions[i].sendMeta(this,m);
1326 else
1327 for (int i=0;m!=null && i<_extensions.length;i++)
1328 m=_extensions[i].send(this,m);
1329
1330 if (message!=m)
1331 {
1332 message.clear();
1333 if (m!=null)
1334 for (Map.Entry<String,Object> entry:m.entrySet())
1335 message.put(entry.getKey(),entry.getValue());
1336 }
1337 }
1338 }
1339
1340
1341
1342
1343
1344
1345
1346 protected void extendIn(Message message)
1347 {
1348 if (_extensions!=null)
1349 {
1350 Message m = message;
1351 if (m.getChannel().startsWith(Bayeux.META_SLASH))
1352 for (int i=_extensions.length;m!=null && i-->0;)
1353 m=_extensions[i].rcvMeta(this,m);
1354 else
1355 for (int i=_extensions.length;m!=null && i-->0;)
1356 m=_extensions[i].rcv(this,m);
1357
1358 if (message!=m)
1359 {
1360 message.clear();
1361 if (m!=null)
1362 for (Map.Entry<String,Object> entry:m.entrySet())
1363 message.put(entry.getKey(),entry.getValue());
1364 }
1365 }
1366 }
1367
1368
1369
1370 }