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