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